You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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)共同决定,直接读取页面资源中的图片对象无法拿到正确显示尺寸,需要通过解析页面内容流的绘制指令捕获参数计算:

  1. 扩展PDFGraphicsStreamEngine类,重写drawImage方法,在图片绘制时获取当前CTM参数
  2. 结合页面旋转角度、有效区域尺寸,计算图片实际显示宽高,再计算面积占比

核心实现代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 15:09:02