com.day.image.ImageSupport Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2012 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package com.day.image;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.io.IOException;
import java.util.Iterator;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import ch.randelshofer.media.jpeg.CMYKJPEGImageReaderSpi;
import com.day.imageio.plugins.GIFImageMetadata;
import com.day.imageio.plugins.GIFStreamMetadata;
import com.day.imageio.plugins.GifImageWriterSpi;
/**
* The ImageSupport
class provides methods, which are implemented
* differently for Java 1.3 and for Java 1.4. These methods are implemented as
* public static methods, which delegate to protected instance methods of
* implementations of this abstract class.
*
* @version $Revision$
* @author fmeschbe
* @since coati
* @audience core, renamed from GifImageSupport to ImageSupport in gumbaer
*/
public class ImageSupport {
/** The JRE dependent ImageSupport instance */
private static final ImageSupport instance;
/** Flag indicating whether GIF Image Writer has been registered
* with the ImageIO registry through this class.
* @see ImageSupport#registerImageIOSpi()
* @see ImageSupport#deregisterImageIOSpi()
*/
private static boolean registered = false;
/** The GifImageWriterSpi instance used for (de-)registration */
private ImageWriterSpi gifImageWriterSpi;
/** The CMYKJPEGImageReaderSpi instance used for (de-)registration */
private ImageReaderSpi cmykJpegImageReaderSpi;
/** Private constructor prevents instantiation */
protected ImageSupport() {}
static {
// get the instance of the ImageSupport implementation
instance = new ImageSupport();
// prevent ImageIO from using files for caching
ImageIO.setUseCache(false);
}
/**
* Initializes the GIF image writing support. The first task is to define
* the classes to use for the GIF image writing and the to register the
* image writer SPI and to get the instance of the support class.
*
* This does nothing if the writing support has already been initialized.
*/
static void initialize() {
registerImageIOSpi();
}
/**
* Interpret the GIF metadata of a GIF image stream. The access to this
* metadata is not very portable, as we directly access Sun's GIF Metadata
* information.
*
* @param reader The reader, which read the image data
*
* @throws IOException if reading the meta data from the image fails.
* @throws IllegalStateException if the input source of the reader has
* not been set.
*/
static void getGIFMetaData(Layer layer, ImageReader reader)
throws IOException {
instance.doGetGIFMetaData(layer, reader);
}
/**
* Returns an image writer suitable to write the given format
.
* This special implementation makes sure, that we use our own GIF writer
* implementation even if the platform provides another GIF writer. The
* reason for this is, that we depend on the image and stream metadata
* format.
*
* @param format The format to write, which must be something link "gif",
* "jpeg", or the likes.
*
* @return An ImageWriter
suitable to write the
* layer
or null
if no such writer is
* available.
*/
static ImageWriter getImageWriter(String format) throws IOException {
if ("gif".equalsIgnoreCase(format)) {
// should create a writer from the SPI
return instance.getGIFImageWriterSpi().createWriterInstance();
}
// check both the mimetype and the format
Iterator writers = ImageIO.getImageWritersByFormatName(format);
if (!writers.hasNext()) {
writers = ImageIO.getImageWritersByMIMEType(format);
}
if (writers.hasNext()) {
return (ImageWriter) writers.next();
}
// nothing found, return nothing
return null;
}
/**
* Do GIF preprocessing : Reduce colors according to the quality desired
* and define metadata structures.
*
* As a side effect the base image will be replaced by a reduced color
* one !
*
* @param numCol The number of colors for the GIF Image
*
* @return The stream and image metadata as the first and second element
* in the returned array of IIOMetadata.
*/
static IIOMetadata[] createGIFMetadata(Layer layer, ImageWriter writer,
int numCol) {
return instance.doCreateGIFMetadata(layer, writer, numCol);
}
/**
* Forces the data to match the state specified in the
* isAlphaPremultiplied
variable. It may multiply or
* divide the color raster data by alpha, or do nothing if the data is
* in the correct state.
*
* NOTE: Due to a bug in the DirectColorModel.coerceData
* implementation of Java 1.3 we have to hack this in using the
* implementation of Java 1.4 for the 1.3 version and delegating to the
* library implementation for the 1.4 version.
*
* @param image The image to apply the alpha channel for.
* @param isAlphaPremultiplied true
if the alpha has been
* premultiplied; false
otherwise.
*
* @return The image with correct state. Depending on the implementation and
* the real need for coercion, the image may or may not be the same as
* the input image
. Callers should not assume to get a
* different image or even different raster data object.
*/
public static BufferedImage coerceData(BufferedImage image,
boolean isAlphaPremultiplied) {
return instance.doCoerceData(image, isAlphaPremultiplied);
}
/**
* Registers the GIF Image writer to be used depending on the Java
* runtime to the ImageIO registry. If the image writer has already been
* registered, this method has no effect.
*/
public static void registerImageIOSpi() {
if (!registered) {
IIORegistry reg = IIORegistry.getDefaultInstance();
reg.registerServiceProvider(instance.getGIFImageWriterSpi());
reg.registerServiceProvider(instance.getCmykJpegImageReaderSpi());
registered = true;
}
}
/**
* Deregisters the GIF Image writer to be used depending on the Java
* runtime from the ImageIO registry. If the image writer is not registered,
* this method has no effect.
*/
public static void deregisterImageIOSpi() {
if (registered) {
IIORegistry reg = IIORegistry.getDefaultInstance();
reg.deregisterServiceProvider(instance.getGIFImageWriterSpi());
reg.deregisterServiceProvider(instance.getCmykJpegImageReaderSpi());
registered = false;
}
}
//---------- to be implemented ---------------------------------------------
/**
* Interpret the GIF metadata of a GIF image stream. The access to this
* metadata is not very portable, as we directly access Sun's GIF Metadata
* information.
*
* @param reader The reader, which read the image data
* @throws IOException if reading the meta data from the image fails.
* @throws IllegalStateException if the input source of the reader has not
* been set.
*/
protected void doGetGIFMetaData(Layer layer, ImageReader reader)
throws IOException {
// Get background and transparency color, if available....
IIOMetadata smd;
IIOMetadata imd;
try {
smd = reader.getStreamMetadata();
imd = reader.getImageMetadata(layer.getImageIndex());
} catch (IIOException iioe) {
// log or throw
return;
}
byte[] globalColorTable = (byte[]) get(smd, "globalColorTable");
if (globalColorTable != null) {
int bgidx = 3 * getInt(smd, "backgroundColorIndex", Integer.MAX_VALUE);
if (bgidx < globalColorTable.length) {
int r = (globalColorTable[bgidx++] + 0x100) & 0xff;
int g = (globalColorTable[bgidx++] + 0x100) & 0xff;
int b = (globalColorTable[bgidx] + 0x100) & 0xff;
// Set the background color
layer.setBackground(new Color(r, g, b));
}
}
// Set the transparency color
if (getBoolean(imd, "transparentColorFlag", false)) {
byte[] colTab = (byte[]) get(imd, "localColorTable");
if (colTab == null && globalColorTable != null) {
colTab = globalColorTable;
}
if (colTab != null) {
int transidx = 3 * getInt(imd, "transparentColorIndex", 0);
int r = (colTab[transidx++] + 0x100) & 0xff;
int g = (colTab[transidx++] + 0x100) & 0xff;
int b = (colTab[transidx++] + 0x100) & 0xff;
// Set the transparency color
layer.setTransparency(new Color(r, g, b, 0));
}
}
}
/**
* Do GIF preprocessing : Reduce colors according to the quality desired and
* define metadata structures.
*
* As a side effect the base image will be replaced by a reduced color one !
*
* @param numCol The number of colors for the GIF Image
* @return The stream and image metadata as the first and second element in
* the returned array of IIOMetadata.
*/
protected IIOMetadata[] doCreateGIFMetadata(Layer layer,
ImageWriter writer, int numCol) {
// Assert correctness of the numCol value
if (numCol <= 0) {
numCol = 2;
} else if (numCol > 256) {
numCol = 256;
}
// Reduce colors and convert to IndexColorModel if needed
BufferedImage reduced = DitherOp.convertToIndexed(layer.getImage(),
numCol, layer.getTransparency(), layer.getBackgroundColor(), null);
if (reduced != layer.getImage()) {
/**
* This is not optimal, the reduced image should only be used for
* writing but not to replace the layer's image ...
*/
layer.setImage(reduced);
}
// we use the IndexColorModel later
IndexColorModel icm = (IndexColorModel) reduced.getColorModel();
// Set some properties like background color and transparency color
GIFImageMetadata imd = (GIFImageMetadata) writer.getDefaultImageMetadata(
null, null);
imd.imageLeftPosition = layer.getX() > 0 ? layer.getX() : 0; // only
// if
// positive
imd.imageTopPosition = layer.getY() > 0 ? layer.getY() : 0; // only if
// positive
imd.imageWidth = layer.getWidth();
imd.imageHeight = layer.getHeight();
GIFStreamMetadata smd = (GIFStreamMetadata) writer.getDefaultStreamMetadata(null);
smd.logicalScreenHeight = layer.getHeight();
smd.logicalScreenWidth = layer.getWidth();
if (layer.getTransparency() != null) {
imd.transparentColorFlag = true;
imd.transparentColorIndex = icm.getTransparentPixel();
}
if (layer.getBackgroundColor().equals(layer.getTransparency())) {
smd.backgroundColorIndex = icm.getTransparentPixel();
} else {
smd.backgroundColorIndex = toIndex(icm,
layer.getBackgroundColor().getRGB());
}
return new IIOMetadata[] { smd, imd };
}
/**
* Forces the data to match the state specified in the
* isAlphaPremultiplied
variable. It may multiply or
* divide the color raster data by alpha, or do nothing if the data is
* in the correct state.
*
* @param image The image to apply the alpha channel for.
* @param isAlphaPremultiplied true
if the alpha has been
* premultiplied; false
otherwise.
*
* @return The image with correct state. For this implementation this is
* always the same as the input image
.
*/
protected BufferedImage doCoerceData(BufferedImage image,
boolean isAlphaPremultiplied) {
image.coerceData(isAlphaPremultiplied);
return image;
}
/**
* Returns the {@link com.day.imageio.plugins.GifImageWriterSpi} instance
* associated with this support implementation. The object returned may
* be used for registration to the ImageIO registry of Writer SPIs.
*
* @return The GIF image writer SPI instance.
*/
protected ImageWriterSpi getGIFImageWriterSpi() {
if (gifImageWriterSpi == null) {
gifImageWriterSpi = new GifImageWriterSpi();
}
return gifImageWriterSpi;
}
/**
* Returns the {@link CMYKJPEGImageReaderSpi} instance
* associated with this support implementation. The object returned may
* be used for registration to the ImageIO registry of Reader SPIs.
*
* @return The GIF image writer SPI instance.
*/
protected ImageReaderSpi getCmykJpegImageReaderSpi() {
if (cmykJpegImageReaderSpi == null) {
cmykJpegImageReaderSpi = new CMYKJPEGImageReaderSpi();
}
return cmykJpegImageReaderSpi;
}
//----------- helpers ------------------------------------------------------
/**
* Gets the nearest matching color index for the given rgba color value. If
* the alpha channel value of rgb
is zero, the index for the
* transparent color is returned.
*
* This method's implementation can only handle color maps in the int, byte,
* or short transfer type format. For any other format, 0 is returned.
*
* @param icm The IndexColorModel
containing the color map
* @param rgb The ARGB color value to find in the color map
*
* @return The index of the color entry most closely matching the ARGB color
* value or zero if the color model is not based on one of the
* int, short, or byte transfer types.
*/
protected static int toIndex(IndexColorModel icm, int rgb) {
Object pixel = icm.getDataElements(rgb, null);
switch (icm.getTransferType()) {
case DataBuffer.TYPE_INT:
return ((int[]) pixel)[0];
case DataBuffer.TYPE_BYTE:
return ((byte[]) pixel)[0];
case DataBuffer.TYPE_USHORT:
return ((short[]) pixel)[0];
default:
return 0;
}
}
private static int getInt(Object object, String name, int defValue) {
try {
return object.getClass().getField(name).getInt(object);
} catch (Throwable t) {
// don't care
}
return defValue;
}
private static boolean getBoolean(Object object, String name, boolean defValue) {
try {
return object.getClass().getField(name).getBoolean(object);
} catch (Throwable t) {
// don't care
}
return defValue;
}
private static Object get(Object object, String name) {
try {
return object.getClass().getField(name).get(object);
} catch (Throwable t) {
// don't care
}
return null;
}
}