package net.coobird.thumbnailator.tasks.io; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.List; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; import net.coobird.thumbnailator.ThumbnailParameter; import net.coobird.thumbnailator.tasks.UnsupportedFormatException; import net.coobird.thumbnailator.util.BufferedImages; import net.coobird.thumbnailator.util.ThumbnailatorUtils; /** * An {@link ImageSink} which specifies an {@link OutputStream} to which the * thumbnail image should be written to. * * @author coobird * */ public class OutputStreamImageSink extends AbstractImageSink { /** * The {@link OutputStream} to which the thumbnail image is to be * written to. */ private final OutputStream os; /** * Instantiates an {@link OutputStreamImageSink} with the * {@link OutputStream} to which the thumbnail should be written to. * * @param os The {@link OutputStream} to write the thumbnail to. * @throws NullPointerException If the {@link OutputStream} is * {@code null}. */ public OutputStreamImageSink(OutputStream os) { super(); if (os == null) { throw new NullPointerException("OutputStream cannot be null."); } this.os = os; } /** * Writes the resulting image to the {@link OutputStream}. * * @param img The image to write. * @throws UnsupportedFormatException When an unsupported format has been * specified by the * {@link #setOutputFormatName(String)} * method. * @throws IOException When a problem occurs while writing * the image. * @throws NullPointerException If the image is {@code null}. * @throws IllegalStateException If the output format has not been set * by calling the * {@link #setOutputFormatName(String)} * method. */ public void write(BufferedImage img) throws IOException { super.write(img); if (outputFormat == null) { throw new IllegalStateException("Output format has not been set."); } String formatName = outputFormat; Iterator writers = ImageIO.getImageWritersByFormatName(formatName); if (!writers.hasNext()) { throw new UnsupportedFormatException( formatName, "No suitable ImageWriter found for " + formatName + "." ); } ImageWriter writer = writers.next(); ImageWriteParam writeParam = writer.getDefaultWriteParam(); if (writeParam.canWriteCompressed() && param != null) { writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); /* * Sets the compression format type, if specified. * * Note: * The value to denote that the codec's default compression type * should be used is null. */ if (param.getOutputFormatType() != ThumbnailParameter.DEFAULT_FORMAT_TYPE) { writeParam.setCompressionType(param.getOutputFormatType()); } else { List supportedFormats = ThumbnailatorUtils.getSupportedOutputFormatTypes(formatName); if (!supportedFormats.isEmpty()) { writeParam.setCompressionType(supportedFormats.get(0)); } } /* * Sets the compression quality, if specified. * * Note: * The value to denote that the codec's default compression quality * should be used is Float.NaN. */ if (!Float.isNaN(param.getOutputQuality())) { writeParam.setCompressionQuality(param.getOutputQuality()); } } ImageOutputStream ios = ImageIO.createImageOutputStream(os); if (ios == null) { throw new IOException("Could not open OutputStream."); } /* * Note: * The following code is a workaround for the JPEG writer which ships * with the JDK. * * At issue is, that the JPEG writer appears to write the alpha * channel when it should not. To circumvent this, images which are * to be saved as a JPEG will be copied to another BufferedImage without * an alpha channel before it is saved. * * Also, the BMP writer appears not to support ARGB, so an RGB image * will be produced before saving. */ if ( formatName.equalsIgnoreCase("jpg") || formatName.equalsIgnoreCase("jpeg") || formatName.equalsIgnoreCase("bmp") ) { img = BufferedImages.copy(img, BufferedImage.TYPE_INT_RGB); } writer.setOutput(ios); writer.write(null, new IIOImage(img, null, null), writeParam); /* * Dispose the writer to free resources. * * This seems to be the main culprit of `OutOfMemoryError`s which * started to frequently appear with Java 7 Update 21. * * Issue: * http://code.google.com/p/thumbnailator/issues/detail?id=42 */ writer.dispose(); ios.close(); } public OutputStream getSink() { return os; } }