iText:降低图像质量(以减小生成PDF文件的大小)

7

在使用 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中图像的尺寸。 - mkl
每当您提供赏金时,应更清楚地指出您的期望。特别是,@Ben的答案在哪些方面不可信? - mkl
与iText的使用相比,这并不容易。生成图片的图像质量可能会更好(例如,我使用相同数量的像素在GIMP中获得更好的结果)。结果不是JPEG,因此无法达到我更小文件大小的目标。生成的格式不是由输入格式确定的。我正在寻找一个简单,易于操作且直接解决这个普遍问题的解决方案。 - ideaboxer
好的。我建议您稍微修改一下问题:iText本身不包含缩小位图图像数据的功能,它合理地期望您使用专门用于位图图像处理的软件来完成这项工作。因此,您应该重新制定问题,询问在特别缩小JPEG位图图像并使用这些提议的标签时,在Java中的图像压缩和图像处理选项。该问题不应该过多地听起来像是对软件推荐的请求,因为这些请求现在被认为是超出了Stack Overflow的主题范围。 - mkl
2个回答

6

先对图片进行缩放,然后使用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));

如需更好地缩放图像,请参阅如何使用Java调整图像大小?

如果在添加到PDF时仍需要原始大小,请将其再次缩放。

itextImage.scale(2f, 2f);

注意:此代码未经测试。

编辑,回应悬赏评论

你让我开始思考和查找。看起来iText将导入AWT图像视为原始图像。我认为它将其视为BMP相同,只是简单地使用/FlateDecode写入像素数据,这可能远不如最佳。我能想到的唯一实现您需求的方法是使用ImageIO将缩放后的图像写入文件系统或ByteArrayOutputStream作为jpeg,然后使用所得文件/字节以iText打开。

这里是使用字节数组的更新示例。如果您想在压缩级别等方面变得更加花哨,请参考此处

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));

1
谢谢,它可以工作了。两个更正:像这样调用getWidth/getHeight awtImage.getWidth(null)(传递null); 我必须从java.awt.Color中取出WHITE而不是ColorConstants - ideaboxer
糟糕,已修复。谢谢! - Ben Ingle
编辑后包括将原始图像转换为JPEG以实现更好的压缩的示例。 - Ben Ingle

1

在这个文档中列出了一种方法,它可以让您访问压缩图像和减小存储在磁盘上的整个PDF文件。希望能帮到您。

以下是代码示例:

/*
 * This example was written by Bruno Lowagie in answer to the following question:
 * http://stackoverflow.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之前,我需要实时地减小图像文件的大小。 - ideaboxer
原始答案:https://stackoverflow.com/a/55728764/4398114 - Allinone51

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接