package com.aspose.words.examples.programming_documents.images; import com.aspose.words.*; import com.aspose.words.Shape; import com.aspose.words.examples.Utils; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; import java.awt.*; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.text.MessageFormat; import java.util.Iterator; public class CompressImages { public static void main(String[] args) throws Exception { // The path to the documents directory. String dataDir = Utils.getDataDir(CompressImages.class); String srcFileName = dataDir + "Test.docx"; System.out.println(MessageFormat.format("Loading {0}. Size {1}.", srcFileName, getFileSize(srcFileName))); Document doc = new Document(srcFileName); // 220ppi Print - said to be excellent on most printers and screens. // 150ppi Screen - said to be good for web pages and projectors. // 96ppi Email - said to be good for minimal document size and sharing. final int desiredPpi = 150; // In Java this seems to be a good compression / quality setting. final int jpegQuality = 90; // Resample images to desired ppi and save. int count = Resampler.resample(doc, desiredPpi, jpegQuality); System.out.println(MessageFormat.format("Resampled {0} images.", count)); if (count != 1) System.out.println("We expected to have only 1 image resampled in this test document!"); String dstFileName = srcFileName + ".Resampled Out.docx"; doc.save(dstFileName); System.out.println(MessageFormat.format("Saving {0}. Size {1}.", dstFileName, getFileSize(dstFileName))); // Verify that the first image was compressed by checking the new Ppi. doc = new Document(dstFileName); com.aspose.words.Shape shape = (com.aspose.words.Shape)doc.getChild(NodeType.SHAPE, 0, true); double imagePpi = shape.getImageData().getImageSize().getWidthPixels() / ConvertUtil.pointToInch(shape.getSizeInPoints().getX()); assert (imagePpi < 150) : "Image was not resampled successfully."; } public static int getFileSize(String fileName) throws Exception { File file = new File(fileName); return (int)file.length(); } } class Resampler { /** * Resamples all images in the document that are greater than the specified PPI (pixels per inch) to the specified PPI * and converts them to JPEG with the specified quality setting. * * @param doc The document to process. * @param desiredPpi Desired pixels per inch. 220 high quality. 150 screen quality. 96 email quality. * @param jpegQuality 0 - 100% JPEG quality. */ public static int resample(Document doc, int desiredPpi, int jpegQuality) throws Exception { int count = 0; // Convert VML shapes. for (Shape vmlShape : (Iterable<Shape>) doc.getChildNodes(NodeType.SHAPE, true, false)) { // It is important to use this method to correctly get the picture shape size in points even if the picture is inside a group shape. Point2D.Float shapeSizeInPoints = vmlShape.getSizeInPoints(); if (resampleCore(vmlShape.getImageData(), shapeSizeInPoints, desiredPpi, jpegQuality)) count++; } // Convert DrawingML shapes. for (com.aspose.words.Shape dmlShape : (Iterable<com.aspose.words.Shape>) doc.getChildNodes(NodeType.SHAPE, true, false)) { // In MS Word the size of a DrawingML shape is always in points at the moment. Point2D.Float shapeSizeInPoints = dmlShape.getSizeInPoints(); if (resampleCore(dmlShape.getImageData(), shapeSizeInPoints, desiredPpi, jpegQuality)) count++; } return count; } /** * Resamples one VML or DrawingML image */ private static boolean resampleCore(ImageData imageData, Point2D.Float shapeSizeInPoints, int ppi, int jpegQuality) throws Exception { // The are actually several shape types that can have an image (picture, ole object, ole control), let's skip other shapes. if (imageData == null) return false; // An image can be stored in the shape or linked from somewhere else. Let's skip images that do not store bytes in the shape. byte[] originalBytes = imageData.getImageBytes(); if (originalBytes == null) return false; // Ignore metafiles, they are vector drawings and we don't want to resample them. int imageType = imageData.getImageType(); if ((imageType == ImageType.WMF) || (imageType == ImageType.EMF)) return false; try { double shapeWidthInches = ConvertUtil.pointToInch(shapeSizeInPoints.getX()); double shapeHeightInches = ConvertUtil.pointToInch(shapeSizeInPoints.getY()); // Calculate the current PPI of the image. ImageSize imageSize = imageData.getImageSize(); double currentPpiX = imageSize.getWidthPixels() / shapeWidthInches; double currentPpiY = imageSize.getHeightPixels() / shapeHeightInches; System.out.print(MessageFormat.format("Image PpiX:{0}, PpiY:{1}. ", (int) currentPpiX, (int) currentPpiY)); // Let's resample only if the current PPI is higher than the requested PPI (e.g. we have extra data we can get rid of). if ((currentPpiX <= ppi) || (currentPpiY <= ppi)) { System.out.println("Skipping."); return false; } BufferedImage srcImage = imageData.toImage(); // Create a new image of such size that it will hold only the pixels required by the desired ppi. int dstWidthPixels = (int)(shapeWidthInches * ppi); int dstHeightPixels = (int)(shapeHeightInches * ppi); BufferedImage dstImage = new BufferedImage(dstWidthPixels, dstHeightPixels, getResampledImageType(srcImage.getType())); // Drawing the source image to the new image scales it to the new size. Graphics2D g = (Graphics2D)dstImage.getGraphics(); try { // Setting any other interpolation or rendering value can increase the time taken extremely. g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.drawImage( srcImage, 0, 0, dstWidthPixels, dstHeightPixels, 0, 0, srcImage.getWidth(), srcImage.getHeight(), null); } finally { g.dispose(); } // Create JPEG encoder parameters with the quality setting. Iterator writers = ImageIO.getImageWritersByFormatName("jpeg"); ImageWriter writer = (ImageWriter)writers.next(); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(jpegQuality / 100.0f); // Save the image as JPEG to a memory stream. ByteArrayOutputStream dstStream = new ByteArrayOutputStream(); ImageOutputStream ios = ImageIO.createImageOutputStream(dstStream); writer.setOutput(ios); IIOImage ioImage = new IIOImage(dstImage, null, null); writer.write(null, ioImage, param); // This is required, otherwise not all data might be written to our stream. ios.flush(); // The Java documentation recommends disposing image readers and writers asap. writer.dispose(); // If the image saved as JPEG is smaller than the original, store it in the shape. System.out.println(MessageFormat.format("Original size {0}, new size {1}.", originalBytes.length, dstStream.size())); if (dstStream.size() < originalBytes.length) { imageData.setImageBytes(dstStream.toByteArray()); return true; } } catch (Exception e) { // Catch an exception, log an error and continue if cannot process one of the images for whatever reason. System.out.println("Error processing an image, ignoring. " + e.getMessage()); } return false; } private static int getResampledImageType(int srcImageType) { // In general, we want to preserve the image color model, but some things need to be taken care of. switch (srcImageType) { case BufferedImage.TYPE_CUSTOM: // I have seen some PNG images return TYPE_CUSTOM and creating a BufferedImage of this type fails, // so we fallback to a more suitable value. return BufferedImage.TYPE_INT_RGB; case BufferedImage.TYPE_BYTE_INDEXED: // This has some problems with colors if we use the BufferedImage ctor that accepts the color model, // so let's just convert the bitmap to RGB color. It is enough for the sample project. return BufferedImage.TYPE_INT_RGB; case BufferedImage.TYPE_INT_ARGB: case BufferedImage.TYPE_4BYTE_ABGR: // 32bit image with alpha channel has wrong colors if it is saved to JPEG. // JPEG doesn't support transparency so we may convert image to RGB without alpha. return BufferedImage.TYPE_INT_RGB; default: // The image format should be okay. return srcImageType; } } }