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

PDFBox中BufferedImage与ImageXObject分辨率差异及提取方案咨询

问题:PDFBox提取JPEG2000图片时BufferedImage与ImageXObject分辨率不一致

在使用PDFBox提取书籍中的JPEG 2000图片时,发现BufferedImage与ImageXObject的分辨率不一致。用于打印分辨率的代码如下:

static boolean savePDImage(String name, PDImageXObject image) throws Exception {
    if (null != image && null != image.getImage()) {

        BufferedImage bufferImage = image.getImage();
        String rawSuffix = image.getSuffix().toLowerCase();
        String suffix = image.getSuffix().toLowerCase();
        File imgFile = new File(name + "." + rawSuffix);

        System.out.println(String.format("BufferImage %dx%d, 	ImageXObject %dx%d"
                    , bufferImage.getWidth(), bufferImage.getHeight(), image.getWidth(), image.getHeight()));
        return true;
        }
return false;
}

控制台输出结果如下:

BufferImage 983x1336,   ImageXObject 983x1336
BufferImage 1965x2672,  ImageXObject 491x668
BufferImage 980x1334,   ImageXObject 980x1334
BufferImage 1959x2668,  ImageXObject 490x667
BufferImage 274x218,    ImageXObject 274x218
BufferImage 98x134,     ImageXObject 98x134
BufferImage 180x224,    ImageXObject 180x224
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 958x1328,   ImageXObject 958x1328
BufferImage 1923x2665,  ImageXObject 479x664
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 731x60,     ImageXObject 731x60
BufferImage 954x1325,   ImageXObject 954x1325
BufferImage 1914x2659,  ImageXObject 477x662
BufferImage 954x1325,   ImageXObject 954x1325

请问二者分辨率不同的原因是什么?如何提取与ImageXObject分辨率一致的图片?尝试过使用bufferImage.getScaledInstance( image.getWidth(), image.getHeight(), Image.SCALE_SMOOTH),但效果不理想。


原因与解决方案

原因分析

  • ImageXObject.getWidth()/getHeight()返回的是PDF页面坐标系中的显示尺寸,单位为用户空间单位(默认1单位=1/72英寸),代表图片在PDF中实际显示的物理尺寸对应的逻辑单位。
  • BufferedImage的尺寸是图片的原始像素尺寸,PDFBox解码JPEG2000时会直接读取图片的原始像素数据,不会自动缩放。当原始图片的DPI(比如288DPI)远高于PDF默认的72DPI时,就会出现像素尺寸是ImageXObject尺寸的4倍(288/72=4),也就是你看到的差异。

解决方案

方法1:保留原始像素,设置匹配的DPI(推荐)

不需要缩放像素,只需在保存图片时添加DPI元数据,让图片的物理显示尺寸和PDF中的一致:

static boolean savePDImage(String name, PDImageXObject image) throws Exception {
    if (null != image && null != image.getImage()) {
        BufferedImage bufferImage = image.getImage();
        String suffix = image.getSuffix().toLowerCase();
        File imgFile = new File(name + "." + suffix);

        // 计算匹配PDF显示尺寸的DPI:(像素数) / (PDF尺寸单位 * 1/72英寸)
        float dpiX = (bufferImage.getWidth() * 72f) / image.getWidth();
        float dpiY = (bufferImage.getHeight() * 72f) / image.getHeight();

        // 获取图片写入器并配置元数据
        ImageWriter writer = ImageIO.getImageWritersBySuffix(suffix).next();
        ImageWriteParam param = writer.getDefaultWriteParam();
        IIOMetadata metadata = writer.getDefaultImageMetadata(new ImageTypeSpecifier(bufferImage), param);

        // 为JPEG/JP2设置DPI元数据(单位转换:25.4mm=1英寸)
        if ("jp2".equals(suffix) || "jpeg".equals(suffix) || "jpg".equals(suffix)) {
            IIOMetadataNode root = new IIOMetadataNode("javax_imageio_jpeg_image_1.0");
            IIOMetadataNode jpegVariety = new IIOMetadataNode("JPEGvariety");
            IIOMetadataNode imageMeta = new IIOMetadataNode("ImageMetadata");
            IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
            
            IIOMetadataNode horizontalDPI = new IIOMetadataNode("HorizontalPixelSize");
            horizontalDPI.setAttribute("value", String.valueOf(25.4 / dpiX));
            IIOMetadataNode verticalDPI = new IIOMetadataNode("VerticalPixelSize");
            verticalDPI.setAttribute("value", String.valueOf(25.4 / dpiY));
            
            dimension.appendChild(horizontalDPI);
            dimension.appendChild(verticalDPI);
            imageMeta.appendChild(dimension);
            jpegVariety.appendChild(imageMeta);
            root.appendChild(jpegVariety);
            metadata.mergeTree("javax_imageio_jpeg_image_1.0", root);
        }

        // 写入图片
        try (ImageOutputStream ios = ImageIO.createImageOutputStream(imgFile)) {
            writer.setOutput(ios);
            writer.write(metadata, new IIOImage(bufferImage, null, metadata), param);
        }
        writer.dispose();

        System.out.println(String.format("Saved image with DPI: %.2fx%.2f, matching PDF size %dx%d",
                dpiX, dpiY, image.getWidth(), image.getHeight()));
        return true;
    }
    return false;
}

方法2:高质量缩放至目标像素尺寸

如果必须得到像素数与ImageXObject一致的图片,使用Java 2D的高质量渲染替代getScaledInstance:

static BufferedImage scaleToTargetSize(BufferedImage source, int targetWidth, int targetHeight) {
    BufferedImage scaledImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB);
    Graphics2D g2d = scaledImage.createGraphics();
    
    // 开启高质量渲染参数
    g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    
    g2d.drawImage(source, 0, 0, targetWidth, targetHeight, null);
    g2d.dispose();
    return scaledImage;
}

使用时将bufferImage替换为scaleToTargetSize(bufferImage, image.getWidth(), image.getHeight())再保存即可。


内容的提问来源于stack exchange,提问作者tumuyan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 14:14:56