/* * Copyright 2017 by Eduard Weissmann * * This file is part of the Sejda source code * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.sejda.core.writer.model; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageWriter; import javax.imageio.metadata.IIOMetadata; import javax.imageio.plugins.jpeg.JPEGImageWriteParam; import javax.imageio.stream.ImageOutputStream; import org.apache.commons.io.IOUtils; import org.imgscalr.Scalr; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; public class ImageOptimizer { private static final Logger LOG = LoggerFactory.getLogger(ImageOptimizer.class); /** * Takes an image and creates an optimized version of it. * * If the image is larger than maxWidthOrHeight pixels, it is downsized to fit the maxWidthOrHeight rectangle (keeping its aspect ratio). Image is saved as JPEG with specified * quality (1.0 is best/leave unchanged, 0.0 is worst). Image DPI is changed to dpi specified. */ public static File optimize(BufferedImage bufferedImage, float quality, int dpi, int width, int height, boolean gray) throws IOException { long start = System.currentTimeMillis(); File outputFile = File.createTempFile("pdfimage", ".jpeg"); outputFile.deleteOnExit(); try { int relevantDelta = 20; boolean isResizeRelevant = Math.abs(bufferedImage.getWidth() - width) > relevantDelta && Math.abs(bufferedImage.getHeight() - height) > relevantDelta; boolean isShirinking = bufferedImage.getHeight() > height || bufferedImage.getWidth() > width; if (isResizeRelevant && isShirinking) { // we resize down, we don't resize up LOG.debug("Resizing image from {}x{} to {}x{}", bufferedImage.getWidth(), bufferedImage.getHeight(), width, height); bufferedImage = Scalr.resize(bufferedImage, Scalr.Method.BALANCED, width, height); } // PNG read fix when converting to JPEG BufferedImage newImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), gray ? BufferedImage.TYPE_BYTE_GRAY : BufferedImage.TYPE_INT_RGB); Graphics2D g2d = newImage.createGraphics(); g2d.drawImage(bufferedImage, 0, 0, Color.WHITE, null); g2d.dispose(); ImageWriter imageWriter = ImageIO.getImageWritersBySuffix("jpeg").next(); ImageOutputStream ios = ImageIO.createImageOutputStream(outputFile); imageWriter.setOutput(ios); // compression JPEGImageWriteParam jpegParams = (JPEGImageWriteParam) imageWriter.getDefaultWriteParam(); jpegParams.setCompressionMode(JPEGImageWriteParam.MODE_EXPLICIT); jpegParams.setCompressionQuality(quality); IIOMetadata imageMetaData = null; try { // new metadata imageMetaData = imageWriter.getDefaultImageMetadata(new ImageTypeSpecifier(newImage), jpegParams); Element tree = (Element) imageMetaData.getAsTree("javax_imageio_jpeg_image_1.0"); Element jfif = (Element) tree.getElementsByTagName("app0JFIF").item(0); jfif.setAttribute("Xdensity", Integer.toString(dpi)); jfif.setAttribute("Ydensity", Integer.toString(dpi)); jfif.setAttribute("resUnits", "1"); imageMetaData.setFromTree("javax_imageio_jpeg_image_1.0", tree); } catch (Exception e) { LOG.warn("Failed to set DPI for image, metadata manipulation failed", e); } try { imageWriter.write(null, new IIOImage(newImage, null, imageMetaData), jpegParams); } finally { IOUtils.closeQuietly(ios); imageWriter.dispose(); } return outputFile; } finally { bufferedImage.flush(); long elapsed = System.currentTimeMillis() - start; if (elapsed > 500) LOG.trace("Optimizing image took " + elapsed + "ms"); } } }