iText:降低图像质量(用于减小生成的 PDF 大小)

Posted

技术标签:

【中文标题】iText:降低图像质量(用于减小生成的 PDF 大小)【英文标题】:iText: Reduce image quality (for reducing the resulting PDF size) 【发布时间】:2018-08-28 15:57:13 【问题描述】:

在使用iText 新创建的 PDF 文件中减小 JPEG 图像大小的最佳做法是什么? (我的目标是在图像质量和文件大小之间进行权衡。)

图像创建如下:

Image image = new Image(ImageDataFactory.create(imagePath))

我想提供一个比例因子,例如0.5,它将一行中的像素数减半。

假设我生成了一个包含单个 3 MB 图像的 PDF。我尝试了image.scale(0.5f, 0.5f),但生成的 PDF 文件仍约为 3 MB。我预计它会变得更小。

因此,我猜测嵌入在 PDF 文件中的源图像没有被触及。但这正是我需要的:应该减少存储在磁盘上的整个 PDF 文件中的总像素数。

实现这一目标的最简单/推荐的方法是什么?

【问题讨论】:

image.scale不会更改位图数据,它们只会更改图像在 PDF 中的尺寸。 每当你提供赏金时,你应该更清楚地表明你的期望。特别是@Ben的回答在哪方面不可信? 这并不容易(与 iText 的使用相比)。生成的图像的图像质量可能会更好(例如,使用 GIMP 使用相同数量的像素可以获得更好的结果)。结果不是 JPEG,因此与我的文件大小更小的目标相反。结果格式不是由输入格式决定的。我正在为一个常见的问题寻找一个简单、万无一失和直接的解决方案。 好的。不过,我建议您稍微修改一下您的问题:iText 本身不包含缩小位图图像数据的功能,它明智地希望您使用专门用于位图图像处理的软件。因此,您应该重新制定问题以在java 中询问image-compression image-processing 选项,特别是缩小jpeg bitmap 图像并使用这些建议的标签。这个问题听起来不应该太像软件建议的请求,因为现在那些被认为是堆栈溢出的题外话。 【参考方案1】:

documentations 中列出了一种方法,它使您可以压缩图像并减少存储在磁盘上的整个 PDF 文件。希望对您有所帮助。

以下为代码示例:

/*
 * This example was written by Bruno Lowagie in answer to the following question:
 * http://***.com/questions/30483622/compressing-images-in-existing-pdfs-makes-the-resulting-pdf-file-bigger-lowagie
 */
package sandbox.images;

import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PRStream;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfNumber;
import com.itextpdf.text.pdf.PdfObject;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.parser.PdfImageObject;

import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.imageio.ImageIO;
import sandbox.WrapToTest;

/**
 * @author Bruno Lowagie (iText Software)
 */
@WrapToTest
public class ReduceSize 

    public static final String SRC = "resources/pdfs/single_image.pdf";
    public static final String DEST = "results/images/single_image_reduced.pdf";
    public static final float FACTOR = 0.5f;

    public static void main(String[] args) throws DocumentException, IOException 
        File file = new File(DEST);
        file.getParentFile().mkdirs();
        new ReduceSize().manipulatePdf(SRC, DEST);
    
    public void manipulatePdf(String src, String dest) throws DocumentException, IOException 
        PdfReader reader = new PdfReader(src);
        int n = reader.getXrefSize();
        PdfObject object;
        PRStream stream;
        // Look for image and manipulate image stream
        for (int i = 0; i < n; i++) 
            object = reader.getPdfObject(i);
            if (object == null || !object.isStream())
                continue;
            stream = (PRStream)object;
            if (!PdfName.IMAGE.equals(stream.getAsName(PdfName.SUBTYPE)))
                continue;
            if (!PdfName.DCTDECODE.equals(stream.getAsName(PdfName.FILTER)))
                continue;
            PdfImageObject image = new PdfImageObject(stream);
            BufferedImage bi = image.getBufferedImage();
            if (bi == null)
                continue;
            int width = (int)(bi.getWidth() * FACTOR);
            int height = (int)(bi.getHeight() * FACTOR);
            if (width <= 0 || height <= 0)
                continue;
            BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            AffineTransform at = AffineTransform.getScaleInstance(FACTOR, FACTOR);
            Graphics2D g = img.createGraphics();
            g.drawRenderedImage(bi, at);
            ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
            ImageIO.write(img, "JPG", imgBytes);
            stream.clear();
            stream.setData(imgBytes.toByteArray(), false, PRStream.NO_COMPRESSION);
            stream.put(PdfName.TYPE, PdfName.XOBJECT);
            stream.put(PdfName.SUBTYPE, PdfName.IMAGE);
            stream.put(PdfName.FILTER, PdfName.DCTDECODE);
            stream.put(PdfName.WIDTH, new PdfNumber(width));
            stream.put(PdfName.HEIGHT, new PdfNumber(height));
            stream.put(PdfName.BITSPERCOMPONENT, new PdfNumber(8));
            stream.put(PdfName.COLORSPACE, PdfName.DEVICERGB);
        
        reader.removeUnusedObjects();
        // Save altered PDF
        PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
        stamper.setFullCompression();
        stamper.close();
        reader.close();
    

【讨论】:

谢谢。不幸的是,不是,因为我的目标之一是提高文件创建性能(减少文件创建时间)。包括预览器读取性能(预览器在文件创建后立即读取文件)。因此,在将图像写入 PDF 之前,我需要即时减小图像文件的大小。 原答案:***.com/a/55728764/4398114【参考方案2】:

先缩放图像,然后用 iText 打开缩放后的图像。

ImageDataFactory 中有一个 create 方法,它接受 AWT 图像。首先使用 AWT 工具缩放图像,然后像这样打开它:

String imagePath = "C:\\path\\to\\image.jpg";
java.awt.Image awtImage = ImageIO.read(new File(imagePath));

// scale image here
int scaledWidth = awtImage.getWidth(null) / 2;
int scaledHeight = awtImage.getHeight(null) / 2;
BufferedImage scaledAwtImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = scaledAwtImage.createGraphics();
g.drawImage(awtImage, 0, 0, scaledWidth, scaledHeight, null); 
g.dispose();

/* 
Optionally pick a color to replace with transparency.
Any pixels that match this color will be replaced by tansparency.
*/
Color bgColor = Color.WHITE;

Image itextImage = new Image(ImageDataFactory.create(scaledAwtImage, bgColor));

有关如何缩放图像的更好提示,请参阅How can I resize an image using Java?

如果您在添加到 PDF 时仍需要原始大小,只需将其重新放大即可。

itextImage.scale(2f, 2f);

注意:此代码未经测试。


编辑以回应 cmets 的赏金

你让我思考和观察。 iText 似乎将导入 AWT 图像视为原始图像。我认为它将它与 BMP 相同,它只是 writes the pixel data using /FlateDecode,这可能远不如最佳。我能想到的满足您要求的唯一方法是使用 ImageIO 将缩放图像写入文件系统或 ByteArrayOutputStream 作为 jpeg,然后使用生成的文件/字节使用 iText 打开。

这是一个使用字节数组的更新示例。如果您想更了解压缩级别等,请refer here。

String imagePath = "C:\\path\\to\\image.jpg";
java.awt.Image awtImage = ImageIO.read(new File(imagePath));

// scale image here
int scaledWidth = awtImage.getWidth(null) / 2;
int scaledHeight = awtImage.getHeight(null) / 2;
BufferedImage scaledAwtImage = new BufferedImage(scaledWidth, scaledHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g = scaledAwtImage.createGraphics();
g.drawImage(awtImage, 0, 0, scaledWidth, scaledHeight, null); 
g.dispose();

ByteArrayOutputStream bout = new ByteArrayOutputStream()
ImageIO.write(scaledAwtImage, "jpeg", bout);
byte[] imageBytes = bout.toByteArray();

Image itextImage = new Image(ImageDataFactory.create(imageBytes));

【讨论】:

谢谢,它有效。 2处更正:调用getWidth/getHeight就像awtImage.getWidth(null)(通过null);我不得不从java.awt.ColorWHITE 而不是ColorConstants 糟糕,已修复。谢谢! 已编辑以包含将原始图像转换为 jpeg 以实现更好压缩的示例。

以上是关于iText:降低图像质量(用于减小生成的 PDF 大小)的主要内容,如果未能解决你的问题,请参考以下文章

我们可以使用 iText 压缩 PDF 文件的大小吗?

减小 PDF 大小

在生成的iText PDF文档中居中对齐图像

使用Itext搜索和替换PDF

利用itext生成pdf的简单例子

如何运用Java组件itext生成pdf