Java PDFBox提取PDF图片遇反转拆分(非工具Bug),求修复方案及代码
解决方案
这类PDF的图片被拆分为多个小图块,并通过页面变换矩阵(CTM)施加了翻转操作,可通过以下步骤恢复完整图片:
核心思路
- 提取每页中所有图像块及其对应的变换矩阵(包含位置、缩放、翻转信息)
- 根据所有图块的位置计算完整图片的实际尺寸
- 将图块按坐标拼接成完整图像
- 根据变换矩阵修正图像方向
Java代码实现
首先引入PDFBox核心依赖(Maven示例):
<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.32</version> </dependency>
完整实现代码:
import org.apache.pdfbox.contentstream.PDFStreamEngine; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class PDFImageRestorer { static class ImageBlock { PDImageXObject image; AffineTransform transform; ImageBlock(PDImageXObject image, AffineTransform transform) { this.image = image; this.transform = transform; } } public static void main(String[] args) throws IOException { File pdfFile = new File("my.pdf"); try (PDDocument document = PDDocument.load(pdfFile)) { for (int pageIdx = 0; pageIdx < document.getNumberOfPages(); pageIdx++) { PDPage page = document.getPage(pageIdx); List<ImageBlock> blocks = extractImageBlocks(page); if (blocks.isEmpty()) continue; // 计算完整图片的边界范围 Rectangle totalBounds = calculateTotalBounds(blocks); BufferedImage fullImage = new BufferedImage(totalBounds.width, totalBounds.height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = fullImage.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // 转换PDF坐标系(左下为原点)到Java图像坐标系(左上为原点) for (ImageBlock block : blocks) { AffineTransform adjustedTransform = new AffineTransform(block.transform); adjustedTransform.translate(0, totalBounds.height); adjustedTransform.scale(1, -1); g2d.drawImage(block.image.getImage(), adjustedTransform, null); } g2d.dispose(); // 修正图像翻转方向 BufferedImage corrected = fixOrientation(fullImage, blocks.get(0).transform); ImageIO.write(corrected, "PNG", new File("page_" + (pageIdx + 1) + "_fixed.png")); } } } // 提取页面内所有图像块及对应的变换矩阵 private static List<ImageBlock> extractImageBlocks(PDPage page) throws IOException { List<ImageBlock> blocks = new ArrayList<>(); PDFStreamEngine engine = new PDFStreamEngine() { @Override protected void processOperator(Operator operator, List<COSBase> operands) throws IOException { if ("Do".equals(operator.getName())) { COSName imgName = (COSName) operands.get(0); PDImageXObject image = (PDImageXObject) getResources().getXObject(imgName); if (image != null) { AffineTransform ctm = getGraphicsState().getCurrentTransformationMatrix(); blocks.add(new ImageBlock(image, new AffineTransform(ctm))); } } super.processOperator(operator, operands); } }; engine.processPage(page); return blocks; } // 计算所有图像块的整体边界 private static Rectangle calculateTotalBounds(List<ImageBlock> blocks) { double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE; double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE; for (ImageBlock block : blocks) { PDImageXObject img = block.image; AffineTransform transform = block.transform; Point2D[] corners = { new Point2D.Double(0, 0), new Point2D.Double(img.getWidth(), 0), new Point2D.Double(img.getWidth(), img.getHeight()), new Point2D.Double(0, img.getHeight()) }; for (Point2D corner : corners) { transform.transform(corner, corner); minX = Math.min(minX, corner.getX()); minY = Math.min(minY, corner.getY()); maxX = Math.max(maxX, corner.getX()); maxY = Math.max(maxY, corner.getY()); } } return new Rectangle( (int) Math.floor(minX), (int) Math.floor(minY), (int) Math.ceil(maxX - minX), (int) Math.ceil(maxY - minY) ); } // 根据变换矩阵修正图像翻转方向 private static BufferedImage fixOrientation(BufferedImage image, AffineTransform transform) { boolean flipHoriz = transform.getScaleX() < 0; boolean flipVert = transform.getScaleY() < 0; BufferedImage corrected = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); Graphics2D g2d = corrected.createGraphics(); if (flipHoriz) { g2d.drawImage(image, image.getWidth(), 0, -image.getWidth(), image.getHeight(), null); } else if (flipVert) { g2d.drawImage(image, 0, image.getHeight(), image.getWidth(), -image.getHeight(), null); } else { g2d.drawImage(image, 0, 0, null); } g2d.dispose(); return corrected; } }
关键说明
- 变换矩阵解析:通过PDFBox的
getGraphicsState().getCurrentTransformationMatrix()获取图像的位置、缩放和翻转信息,这是恢复图像的核心依据。 - 坐标系转换:PDF以左下角为原点,Java图像以左上角为原点,因此需要通过平移和缩放修正坐标。
- 方向修正:通过变换矩阵的缩放因子符号判断水平/垂直翻转,若PDF存在旋转需求,可扩展代码解析变换矩阵的旋转角度。
内容的提问来源于stack exchange,提问作者biotech7
相关产品推荐
相关产品推荐

