如何更改 PDF 文件中图像的过滤器

How to change the filter of an image in a PDF file

我正在构建一个压缩 PDF 文件的工具,并使用 pdfbox。 我有一些带有 DCTDecode + FlateDecode 滤镜的图像,我想尝试使用 JPXDecode 滤镜看看它是否占用更少 space.

我看过一些使用 iText 的代码,但是如何使用 pdfbox 来实现呢?我没有找到如何执行此操作的文档。

使用 pdfbox 可以压缩所有图像,方法是使用处理所有图像流并使用 JPXDecode 过滤器重新编码的自定义 COSWriterpdfbox 无法这样做,但带有插件的 JAI 库可以生成 JPEG2000 图像。压缩因子是可配置的,可以在不损失太多质量的情况下实现高压缩比。

通过另外使用 FlateDecode 过滤器,可以在没有质量损失的情况下获得更多的压缩。

此代码无需更改 COSWriter 即可替换图像流(这听起来很可怕),但是我尝试使用 PDF 的经验是编码图像不正确,即 JPEG 2000 编码器中存在错误,所以检查你的结果 PDF。

public class SO57972743
{
    public static void main(String[] args) throws IOException
    {
        System.out.println("supported formats: " + Arrays.toString(ImageIO.getReaderFormatNames()));

        try (PDDocument doc = PDDocument.load(new File("test.pdf")))
        {
            // get 1st level images only here (there may be more in form XObjects!)
            PDResources res = doc.getPage(0).getResources();
            for (COSName name : res.getXObjectNames())
            {
                PDXObject xObject = res.getXObject(name);
                if (xObject instanceof PDImageXObject)
                {
                    replaceImageWithJPX(xObject);
                }
            }
            doc.save("test-result.pdf");
        }
    }

    private static void replaceImageWithJPX(PDXObject xObject) throws IOException
    {
        PDImageXObject img = (PDImageXObject) xObject;
        BufferedImage bim = img.getOpaqueImage(); // the mask (if there) won't be touched
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        boolean written = ImageIO.write(bim, "JPEG2000", baos);
        if (!written)
        {
            System.err.println("write failed");
            return;
        }
        // replace image stream
        try (OutputStream os = img.getCOSObject().createRawOutputStream())
        {
            os.write(baos.toByteArray());
        }
        img.getCOSObject().setItem(COSName.FILTER, COSName.JPX_DECODE); // replace filter
        img.getCOSObject().removeItem(COSName.COLORSPACE); // use the colorspace in the image itself
    }
}