/* (c) 2014 Open Source Geospatial Foundation - all rights reserved
* (c) 2001 - 2013 OpenPlans
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.map.turbojpeg;
import it.geosolutions.imageio.plugins.turbojpeg.TurboJpegImageWriteParam;
import it.geosolutions.imageio.plugins.turbojpeg.TurboJpegImageWriter;
import it.geosolutions.imageio.plugins.turbojpeg.TurboJpegImageWriterSpi;
import it.geosolutions.imageio.plugins.turbojpeg.TurboJpegUtilities;
import it.geosolutions.imageio.utilities.ImageOutputStreamAdapter2;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.IIOImage;
import javax.imageio.ImageWriteParam;
import javax.imageio.spi.ImageOutputStreamSpi;
import javax.imageio.stream.ImageOutputStream;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.operator.FormatDescriptor;
import org.geotools.image.ImageWorker;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.util.logging.Logging;
/**
* Specific subclass of {@link ImageWorker} for writing JPEG images using libjpeg-turbo.
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
final class TurboJpegImageWorker extends ImageWorker {
static final String ERROR_LIB_MESSAGE = "The TurboJpeg native library hasn't been loaded: Skipping";
static final String ERROR_FILE_MESSAGE = "The specified input file can't be read: Skipping";
/** Is the libjpeg-turbo available? **/
private static final TurboJpegImageWriterSpi TURBO_JPEG_SPI = new TurboJpegImageWriterSpi();
/**
* The logger to use for this class.
*/
private final static Logger LOGGER = Logging.getLogger(TurboJpegImageWorker.class);
public TurboJpegImageWorker() {
super();
}
public TurboJpegImageWorker(File input) throws IOException {
super(input);
}
public TurboJpegImageWorker(RenderedImage image) {
super(image);
}
/**
* Writes outs the image contained into this {@link ImageWorker} as a JPEG using the provided destination , compression and compression rate.
* <p>
* The destination object can be anything providing that we have an {@link ImageOutputStreamSpi} that recognizes it.
*
* @param destination where to write the internal {@link #image} as a JPEG.
* @param compressionRate percentage of compression.
* @return this {@link ImageWorker}.
* @throws IOException In case an error occurs during the search for an {@link ImageOutputStream} or during the eoncding process.
*/
public final void writeTurboJPEG(final OutputStream destination, final float compressionRate)
throws IOException {
if (!TurboJpegUtilities.isTurboJpegAvailable()) {
throw new IllegalStateException(ERROR_LIB_MESSAGE);
}
// Reformatting this image for jpeg.
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Encoding input image to write out as JPEG using .");
// go to component color model if needed
ColorModel cm = image.getColorModel();
final boolean hasAlpha = cm.hasAlpha();
forceComponentColorModel();
cm = image.getColorModel();
// rescale to 8 bit
rescaleToBytes();
cm = image.getColorModel();
// remove transparent band
final int numBands = image.getSampleModel().getNumBands();
if (hasAlpha) {
final int requestedBands = numBands - 1;
if (ImageUtilities.isMediaLibAvailable()) {
retainBands(requestedBands);
} else if (getNumBands() > requestedBands) {
removeAlpha(requestedBands);
}
}
// Getting a writer.
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Creating a TURBO JPEG writer and configuring it.");
final TurboJpegImageWriter writer = (TurboJpegImageWriter) TURBO_JPEG_SPI.createWriterInstance();
// Compression is available on both lib
TurboJpegImageWriteParam iwp = (TurboJpegImageWriteParam) writer.getDefaultWriteParam();
final ImageOutputStreamAdapter2 outStream = new ImageOutputStreamAdapter2(destination);
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionType("JPEG");
iwp.setCompressionQuality(compressionRate); // We can control quality here.
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Writing image out...");
try {
writer.setOutput(outStream);
writer.write(null, new IIOImage(image, null, null), iwp);
} finally {
try {
writer.dispose();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
try {
outStream.close();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
}
}
/**
* Remove the alpha band
*
* @param requestedBands
*/
private void removeAlpha(int requestedBands) {
// Retrieving/Setting the ImageLayout
final RenderingHints hints = getRenderingHints();
ImageLayout layout = null;
if (hints.containsKey(JAI.KEY_IMAGE_LAYOUT)) {
layout = (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT);
} else {
layout = new ImageLayout();
hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
}
// Forcing the colormodel with noAlpha
final ColorModel colorModel = new ComponentColorModel(
ColorSpace.getInstance(requestedBands == 3 ? ColorSpace.CS_sRGB : ColorSpace.CS_GRAY),
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
SampleModel sm = colorModel.createCompatibleSampleModel(image.getWidth(), image.getHeight());
layout.setSampleModel(sm);
// Forcing the output format to remove the alpha Band
image = FormatDescriptor.create(image, DataBuffer.TYPE_BYTE, hints);
}
}