/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.map;
import java.awt.RenderingHints;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import javax.media.jai.RenderedOp;
import javax.media.jai.operator.ExtremaDescriptor;
import org.geoserver.wms.WMSMapContent;
import org.geotools.resources.image.ImageUtilities;
/**
* A support object attaching itself to the WebMapContent and deciding which format should be used
* between jpeg and png when using the image/vnd.jpeg-png image format. This is not done in the renderer
* because when meta-tiling we want to defer the decision about the format to the point in which the single
* tiles are encoded
*
* @author Andrea Aime - GeoSolutions
*/
public class JpegOrPngChooser {
/**
* The key used to store the chooser in the map content metadata map
*/
static final String JPEG_PNG_CHOOSER = "jpegOrPngChooser";
public static JpegOrPngChooser getFromMap(RenderedImageMap map) {
WMSMapContent ctx = map.getMapContext();
return getFromMapContent(map.getImage(), ctx);
}
/**
* Returns the chooser from the map content, eventually creating it if missing
* @param image
* @param ctx
* @return
*/
public static JpegOrPngChooser getFromMapContent(RenderedImage image, WMSMapContent ctx) {
JpegOrPngChooser chooser = (JpegOrPngChooser) ctx.getMetadata().get(JPEG_PNG_CHOOSER);
if(chooser == null) {
chooser = new JpegOrPngChooser(image);
ctx.getMetadata().put(JPEG_PNG_CHOOSER, chooser);
}
return chooser;
}
boolean jpegPreferred;
public JpegOrPngChooser(RenderedImage image) {
this.jpegPreferred = isBestFormatJpeg(image);
}
/**
* Returns the full mime type of the chosen format (<code>image/jpeg</code> or <code>image/png</code>)
* @return
*/
public String getMime() {
final String mime = jpegPreferred ? "image/jpeg" : "image/png";
return mime;
}
/**
* Returns the extension of the chosen format (<code>jpeg</code> or <code>png</code>)
* @return
*/
public String getExtension() {
String extension = jpegPreferred ? "jpeg" : "png";
return extension;
}
/**
* Returns true if the best format to encode the image is jpeg (the image is rgb, or rgba without any actual
* transparency use)
*
* @param renderedImage
* @param renderingHints
* @return
*/
private boolean isBestFormatJpeg(RenderedImage renderedImage)
{
int numBands = renderedImage.getSampleModel().getNumBands();
if (numBands == 4 || numBands == 2)
{
RenderingHints renderingHints = ImageUtilities.getRenderingHints(renderedImage);
RenderedOp extremaOp = ExtremaDescriptor.create(renderedImage, null, 1, 1, false, 1, renderingHints);
double[][] extrema = (double[][]) extremaOp.getProperty("Extrema");
double[] mins = extrema[0];
return mins[mins.length - 1] == 255; // fully opaque
} else if(renderedImage.getColorModel() instanceof IndexColorModel) {
// JPEG would still compress a bit better, but in order to figure out
// if the image has transparency we'd have to expand to RGB or roll
// a new JAI image op that looks for the transparent pixels. Out of scope for the moment
return false;
} else {
// otherwise support RGB or gray
return (numBands == 3) || (numBands == 1);
}
}
/**
* Returns true if the JPEG format was the preferred one
*/
public boolean isJpegPreferred() {
return jpegPreferred;
}
}