如何使用PDFBox获取PDF中图片的显示尺寸以计算页面占比
问题背景
我正在尝试计算PDF中图片在对应页面的面积占比,现有代码如下:
PDPageTree list = document.getPages(); int pageNumber = 0; float imagePerPage = 0; for (PDPage page : list) { BufferedImage pageImage = renderer.renderImage(pageNumber, 2); float pageWidth = pageImage.getWidth(); float pageHeight = pageImage.getHeight(); PDResources pdResources = page.getResources(); int i = 1; for (COSName name : pdResources.getXObjectNames()) { PDXObject object = pdResources.getXObject(name); if (object instanceof PDImageXObject) { PDImageXObject image = (PDImageXObject) object; BufferedImage bufferedImage = image.getImage(); float imageWidth = bufferedImage.getWidth(); float imageHeight = bufferedImage.getHeight(); int sumr = 0; int sumg = 0; int sumb = 0; for (int x = 0; x < imageWidth; x++) { for (int y = 0; y < imageHeight; y++) { Color pixel = new Color(bufferedImage.getRGB(x, y)); sumr += pixel.getRed(); sumg += pixel.getGreen(); sumb += pixel.getBlue(); } } int num = image.getWidth() * image.getHeight(); Color avg = new Color(sumr / num, sumg / num, sumb / num); if (!new Color(0, 0, 0).equals(avg)) { String filename = "extracted-image-" + i + ".png"; ImageIO.write( image.getImage(), "png", new File(filename) ); imagePerPage++; } i++; } } System.out.println("Image per page ratio is: " + imagePerPage); imagePerPage = 0; pageNumber++; }
问题核心
上述代码中bufferedImage.getWidth()和bufferedImage.getHeight()返回的是图片的实际像素尺寸,我需要获取图片在PDF页面中的显示尺寸,才能计算正确的面积占比。
已尝试使用PrintImageLocations.java示例代码获取图片显示尺寸,但在实际PDF测试场景下返回结果错误。例如某测试PDF的mediabox宽为612、高为792,代码返回的图片缩放后尺寸为宽25、高16.61,该结果明显不符合实际,该图片实际宽度至少占页面总宽度的三分之一。
解决方案
PDF中图片的显示尺寸不由原始像素决定,由绘制图片时应用的CTM(当前变换矩阵)、页面旋转参数、页面有效区域(CropBox/MediaBox)共同决定,直接读取页面资源中的图片对象无法拿到正确显示尺寸,需要通过解析页面内容流的绘制指令捕获参数计算:
- 扩展
PDFGraphicsStreamEngine类,重写drawImage方法,在图片绘制时获取当前CTM参数 - 结合页面旋转角度、有效区域尺寸,计算图片实际显示宽高,再计算面积占比
核心实现代码
import org.apache.pdfbox.contentstream.PDFGraphicsStreamEngine; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; import org.apache.pdfbox.util.Matrix; import java.awt.geom.Point2D; import java.io.IOException; public class ImageSizeCalculator extends PDFGraphicsStreamEngine { private float totalImageArea = 0f; private final float pageWidth; private final float pageHeight; private final int pageRotation; protected ImageSizeCalculator(PDPage page) { super(page); // 获取页面有效宽高和旋转角度 this.pageWidth = page.getCropBox().getWidth(); this.pageHeight = page.getCropBox().getHeight(); this.pageRotation = page.getRotation(); } @Override public void drawImage(PDImageXObject pdImage) throws IOException { Matrix ctm = getGraphicsState().getCurrentTransformationMatrix(); // 计算图片显示宽高,取矩阵缩放参数的绝对值避免翻转导致的负数 float displayWidth = Math.abs(ctm.getScaleX()); float displayHeight = Math.abs(ctm.getScaleY()); // 处理页面旋转90/270度的情况,宽高互换 if (pageRotation == 90 || pageRotation == 270) { float temp = displayWidth; displayWidth = displayHeight; displayHeight = temp; } // 过滤1x1像素的占位图、尺寸过小的无效图片 if (displayWidth < 1 || displayHeight < 1) { return; } // 可插入原有计算像素平均值的逻辑,全黑图跳过面积计算 totalImageArea += displayWidth * displayHeight; } // 计算面积占比 public float getImageAreaRatio() { float pageArea = pageWidth * pageHeight; return pageArea == 0 ? 0 : totalImageArea / pageArea; } // 父类抽象方法空实现,无需自定义逻辑 @Override public void appendRectangle(Point2D p0, Point2D p1, Point2D p2, Point2D p3) throws IOException {} @Override public void drawForm(PDXObject form) throws IOException {} @Override public void endPath() throws IOException {} @Override public void fillAndStrokePath(int windingRule) throws IOException {} @Override public void fillPath(int windingRule) throws IOException {} @Override public Point2D getCurrentPoint() throws IOException { return null; } @Override public void lineTo(float x, float y) throws IOException {} @Override public void moveTo(float x, float y) throws IOException {} @Override public void closePath() throws IOException {} @Override public void curveTo(float x1, float y1, float x2, float y2, float x3, float y3) throws IOException {} @Override public void curveTo1(float x2, float y2, float x3, float y3) throws IOException {} @Override public void curveTo2(float x1, float y1, float x3, float y3) throws IOException {} @Override public void shadingFill(COSName shadingName) throws IOException {} @Override public void strokePath() throws IOException {} }
调用方式
在原有遍历页面的逻辑中替换图片尺寸计算部分即可:
for (PDPage page : list) { ImageSizeCalculator calculator = new ImageSizeCalculator(page); calculator.processPage(page); float ratio = calculator.getImageAreaRatio(); System.out.printf("当前页图片面积占比:%.2f%%%n", ratio * 100); }
注:如果图片存在斜切变换(CTM的b/c参数不为0),可以通过计算CTM的仿射变换包围盒面积获得更精准的结果,普通场景下直接取缩放参数计算即可覆盖绝大多数需求。
内容的提问来源于stack exchange,提问作者Eszter
相关产品推荐
相关产品推荐

