Reputation: 4091
What is the best practice for reducing the size of JPEG images in a PDF file, newly created using iText? (My objective is a trade-off between image quality and file size.)
The images are created as follows:
Image image = new Image(ImageDataFactory.create(imagePath))
I would like to provide a scale factor, for instance 0.5
, which halves the number of pixels in a row.
Say I generate a PDF with a single 3 MB image. I tried image.scale(0.5f, 0.5f)
, but the resulting PDF file is still roughly 3 MB. I expected it to become much smaller.
Thus I guess the source image, embedded in the PDF file, is not touched. But that is what I need: The total number of pixels in the entire PDF file stored on disk should be reduced.
What is the easiest/recommended way to achieve this?
Upvotes: 7
Views: 10391
Reputation: 672
There is a way that listed in this documentations, that its give you access to compressing the image and reducing the entire PDF file stored on disk. hope it helps.
Below for the example of the code:
/*
* 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();
}
}
Upvotes: 1
Reputation: 585
Scale the image first, then open the scaled image with iText.
There is a create method in ImageDataFactory that accepts an AWT image. Scale the image using AWT tools first, then open it like this:
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));
For better tips on how to scale an image, see How can I resize an image using Java?
If you still need the original size when adding to PDF, just scale it back up again.
itextImage.scale(2f, 2f);
Note: This code is untested.
EDIT in response to comments on bounty
You got me thinking and looking. It appears iText treats importing an AWT image as a raw image. I presume it treats it the same as a BMP, which simply writes the pixel data using /FlateDecode, which is probably significantly less than optimal. The only way I can think of to achieve your requirement would be to use ImageIO to write the scaled image to the file system or a ByteArrayOutputStream as a jpeg, then use the resultant file/bytes to open with iText.
Here's an updated example using byte arrays. If you want to get any more fancy with compression levels and such, 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));
Upvotes: 6