package com.n11.imic; import org.imgscalr.Scalr; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.*; import javax.imageio.event.IIOReadWarningListener; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import java.awt.*; import java.awt.image.BufferedImage; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Iterator; public class ImageScaler { private static final Logger LOGGER = LoggerFactory.getLogger(ImageScaler.class); private ImageScaler() { } public static BufferedImage readImage(InputStream inputImage, String mimeContentType, ScalerParam scalerParam) throws IOException, ImageConverterException { BufferedImage img = null; if (mimeContentType.endsWith("jpeg") || mimeContentType.endsWith("jpg")) { img = new JpegReader().readImage(inputImage); } if (img != null) { return img; } ImageInputStream imgis = ImageIO.createImageInputStream(new BufferedInputStream(inputImage)); Iterator<ImageReader> iter = ImageIO.getImageReaders(imgis); Exception lastException = null; while (iter.hasNext()) { ImageReader reader = null; try { reader = iter.next(); ImageReadParam param = reader.getDefaultReadParam(); reader.setInput(imgis, true, true); reader.addIIOReadWarningListener(new IIOReadWarningListener() { @Override public void warningOccurred(ImageReader imageReader, String s) { LOGGER.warn("Image read warning: " + s); } }); Iterator<ImageTypeSpecifier> imageTypes = reader.getImageTypes(0); while (imageTypes.hasNext()) { ImageTypeSpecifier imageTypeSpecifier = imageTypes.next(); int bufferedImageType = imageTypeSpecifier.getBufferedImageType(); if (bufferedImageType == BufferedImage.TYPE_BYTE_GRAY || bufferedImageType == BufferedImage.TYPE_BYTE_INDEXED) { param.setDestinationType(imageTypeSpecifier); break; } } img = reader.read(0, param); if (mimeContentType.matches("^image/gif") && scalerParam.getAnimatedGifMode() == AnimatedGIFMode.PASSTHROUGH) { // Attempt to read GIF frame #2, in which case an exception should be thrown. try { reader.read(1, param); throw new ImageConverterException("Scaling animated GIF images is not supported."); } catch (IndexOutOfBoundsException e) { LOGGER.debug("No problem for images with a single frame, keep going.", e); } } if (null != img) { imgis.close(); break; } } catch (Exception e) { lastException = e; } finally { if (null != reader) { reader.dispose(); } } } // If you don't have an image at the end of all readers if (img == null && lastException != null) { LOGGER.error("Exception: ", lastException); throw new RuntimeException(lastException); } if (lastException instanceof ImageConverterException && lastException.getMessage().contains("GIF") && scalerParam.getAnimatedGifMode() == AnimatedGIFMode.PASSTHROUGH) { throw (ImageConverterException) lastException; } return img; } public static BufferedImage scaleImage(InputStream inputImage, String mimeContentType, ScalerParam scalerParam) throws IOException, ImageConverterException { BufferedImage img = readImage(inputImage, mimeContentType, scalerParam); if (!scalerParam.isUpScale()) { int w = img.getWidth(), h = img.getHeight(); double scale = Math.max((double) h / (double) scalerParam.getTargetHeight(), (double) w / (double) scalerParam.getTargetWidth()); int targetHeight = new Double(Math.floor(h / scale)).intValue(); int targetWidth = new Double(Math.floor(w / scale)).intValue(); if (targetWidth >= img.getWidth() || targetHeight >= img.getHeight()) { LOGGER.debug("Not up-scaling image."); return img; } } return scaleImage(img, scalerParam); } private static BufferedImage scaleImage(BufferedImage img, ScalerParam scalerParam) { BufferedImage imgScaled; if (scalerParam.getTargetWidth() == scalerParam.getTargetHeight() && scalerParam.getTargetWidth() == ScalerParam.UNDEFINED_SIZE) { /* Optimize-only */ imgScaled = img; } else if (scalerParam.getTargetWidth() == scalerParam.getTargetHeight() || scalerParam.isHasPadding()) { /* Scale and fit to a boundary box, paint background with specified color or white */ int w = img.getWidth(), h = img.getHeight(); double scale = Math.max((double) h / (double) scalerParam.getTargetHeight(), (double) w / (double) scalerParam.getTargetWidth()); int targetHeight = new Double(Math.floor(h / scale)).intValue(); int targetWidth = new Double(Math.floor(w / scale)).intValue(); /* Ignore rounding errors up to 1px for all edges, 2px max. */ int roundErrorDiff = Math.abs(targetWidth - scalerParam.getTargetWidth()); if (roundErrorDiff > 0 && roundErrorDiff <= 2) { targetWidth = scalerParam.getTargetWidth(); } roundErrorDiff = Math.abs(targetHeight - scalerParam.getTargetHeight()); if (roundErrorDiff > 0 && roundErrorDiff <= 2) { targetHeight = scalerParam.getTargetHeight(); } imgScaled = Scalr.resize(img, scalerParam.getScalingMethod(), Scalr.Mode.FIT_TO_HEIGHT, targetWidth, targetHeight); if (imgScaled.getWidth() != scalerParam.getTargetWidth() || imgScaled.getHeight() != scalerParam.getTargetHeight()) { BufferedImage paddedImage = new BufferedImage(scalerParam.getTargetWidth(), scalerParam.getTargetHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D gi = paddedImage.createGraphics(); gi.setComposite(AlphaComposite.SrcOver); Color fillRectColor = scalerParam.getPaddingColor() != null && scalerParam.getPaddingColor().length() == 6 ? Color.decode("#" + scalerParam.getPaddingColor()) : Color.WHITE; if (scalerParam.getPaddingColor() != null && "t".equals(scalerParam.getPaddingColor())) { fillRectColor = Color.WHITE; } gi.setColor(fillRectColor); gi.fillRect(0, 0, scalerParam.getTargetWidth(), scalerParam.getTargetHeight()); gi.drawImage(imgScaled, (int) Math.floor((scalerParam.getTargetWidth() - imgScaled.getWidth()) / 2.0), (int) Math.floor((scalerParam.getTargetHeight() - imgScaled.getHeight()) / 2.0), null); gi.dispose(); imgScaled = paddedImage; } } else if (scalerParam.getTargetWidth() <= 0 && scalerParam.getTargetHeight() > 0) { /* Scale to specified height, keep aspect ratio */ imgScaled = Scalr.resize(img, scalerParam.getScalingMethod(), Scalr.Mode.FIT_TO_HEIGHT, scalerParam.getTargetHeight()); } else { /* Scale to specified width, keep aspect ratio */ imgScaled = Scalr.resize(img, scalerParam.getScalingMethod(), Scalr.Mode.FIT_TO_WIDTH, scalerParam.getTargetWidth()); } if (scalerParam.getPadding() > 0) { imgScaled = scalerParam.getPaddingColor() != null && scalerParam.getPaddingColor().length() == 6 ? Scalr.pad(imgScaled, scalerParam.getPadding(), Color.decode("#" + scalerParam.getPaddingColor())) : Scalr.pad(imgScaled, scalerParam.getPadding()); } return imgScaled; } public static void writeImageToStream(BufferedImage img, OutputStream stream, String mimeContentType, ScalerParam scalerParam) throws IOException { ImageWriter imgWriter = ImageIO.getImageWritersByMIMEType(mimeContentType.matches("^image/gif") ? "image/png" : mimeContentType).next(); ImageWriteParam imgWriterParams = imgWriter.getDefaultWriteParam(); if (mimeContentType.matches("^image/(jpeg|jpg)")) { imgWriterParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); imgWriterParams.setCompressionQuality(scalerParam.getQuality()); imgWriterParams.setProgressiveMode(scalerParam.getProgressiveMode() ? ImageWriteParam.MODE_DEFAULT : ImageWriteParam.MODE_DISABLED); } ImageOutputStream ios = ImageIO.createImageOutputStream(stream); imgWriter.setOutput(ios); try { imgWriter.write(null, new IIOImage(img, null, null), imgWriterParams); } finally { imgWriter.dispose(); ios.close(); } } }