com.day.image.Animation 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.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageOutputStream;
import com.day.imageio.plugins.GIFImageMetadata;
import com.day.imageio.plugins.GIFStreamMetadata;
/**
* The Animation
class takes a series of Layer
s and
* puts them in a single output graphic stream adding animation code for the
* number of loops and the delay time and disposal method of the single patches.
*
*
* What we do in this class is accept a series of layers, which will make up
* the single images. Currently layers are inserted into the animation in the
* succession of their addition. For each layer added, you may define the
* display time in 1/100th seconds and the disposal method used at the end of
* the display time.
*
*
* In addition you may set global looping instruction, on how many times the
* sequence of images should be displayed. To disable setting the loop control
* value in the output file, set the global loop value to a negative value.
*
*
* Properties
*
* The Animation
class supports the following properties, which
* are all backed by setter and getter methods :
*
*
* loops
*
* The number of loops to go through the animation. The default is not set
* byte the constructor or the setter is 0, which boils down to an endless
* loop.
*
* defaultDisposal
*
* The action the image decoder should take after the display time of an
* image has elapsed. This must be one of the defined DISPOSAL_* constants.
* The default if not set by a constructor or the setter is
* DISPOSAL_BACKGROUND.
*
* backgroundColor
*
* The background color of the animation. The default if not set by a
* constructor or the setter is to take the background color of the first
* image in the animation as the background color.
*
*
*
*
* Supported Image Format
*
* Currently the Animation
class is solely implemented based on the
* GIF image file format, whose general structure is described here. You will
* also note, that the API of the Animation
class is based on the
* functionality the GIF format offers for animated image files.
*
*
* CompuServe Graphics Interchange Format (GIF)
*
* The CompuServe Graphics Interchange Format (GIF) is a multi-image graphics
* format, which provides the capability for simple animations provided the
* image viewer is capable of interpreting the multi-image nature of the image
* file and the instructions for display.
*
*
* Generally a GIF file is made up of the following structural elements :
*
* - GIF89a Header
*
- Logical Screen Descriptor Block, with optional global color table
*
- optional Netscape Application Extension Block
*
- stream of graphics
*
* - optional Graphic Control Block
*
- Image Descriptor or Plain Text Block with optional local color table
*
* - GIF Trailer
*
*
*
* For more details, look at the
* GIF89 Specification.
* Generally this page
* contains many interesting information on animated GIFs.
*
* @version $Revision$, $Date$
* @author fmeschbe
* @since coati
* @audience wad
*/
public class Animation {
/**
* No disposal specifed. The decoder is not required to take any action.
*/
public static int DISPOSAL_NONE = 0;
/**
* Do not dispose. The graphic is to be left in place.
*/
public static int DISPOSAL_NO = 1;
/**
* Restore to background color. The area used by the graphic must be
* restored to the background color.
*/
public static int DISPOSAL_BACKGROUND = 2;
/**
* Restore to previous. The decoder is required to restore the area
* overwritten by the graphic with what was there prior to rendering
* the graphic.
*/
public static int DISPOSAL_PREVIOUS = 4;
//---------- fields --------------------------------------------------------
/**
* The list of {@link Patch}es in the animation
*/
private List layers;
/**
* Number of loops for the animation.
* Special values :
*
* - negative number
* - Do not add a loop information block
*
* - 0
* - infinite loop
*
* - positive number
* - Iterate this number of times through the images
*
*/
private int loops;
/**
* Default disposal method used, if none is specifed
* to addLayer
or addLayers
*/
private int defaultDisposal;
/**
* The desired background color for the animated GIF. If this is undefined,
* that is -1, the background color of the first layer in the animation is
* taken.
*/
private int bgColor;
/**
* Initializes the {@link ImageSupport} class to (1) register the Gif
* image writer with the ImageIO but also to load the correct JRE release
* dependent classes.
*/
static {
ImageSupport.initialize();
}
/**
* Creates the Animation
object using the given properties.
*
* @param loops Number of times the application should loop through the
* images. Use a negative value to prevent looping at all.
* @param defaultDisposal default disposal method for images after the
* delay time has elapsed
* @param bgColor The background color to set on the animated GIF. Only the
* red, green and blue values are used, the alpha value is ignored
* as GIF does not support alpha values.
*/
public Animation(int loops, int defaultDisposal, int bgColor) {
this.layers = new ArrayList();
this.loops = loops;
this.defaultDisposal = defaultDisposal;
this.bgColor = bgColor & 0xffffff;
}
/**
* Creates the Animation
object using the given properties. The
* background color is taken from the first image in the animation while the
* default disposal method is set to DISPOSAL_BACKGROUND
.
*
* @param loops Number of times the application should loop through the
* images. Use a negative value to prevent looping at all.
*/
public Animation(int loops) {
this(loops, DISPOSAL_BACKGROUND, -1);
}
/**
* Creates the Animation
object using the default values. The
* background color is taken from the first image in the animation while the
* default disposal method is set to DISPOSAL_BACKGROUND
. The
* loop counter is initialized to 0, which means endless loop.
*/
public Animation() {
this(0, DISPOSAL_BACKGROUND, -1);
}
/**
* Change the number of iterations through the images to the new value.
*
* @param loops Number of times the application should loop through the
* images. Use a negative value to prevent looping at all or set
* to 0 for endless looping.
*/
public void setLoops(int loops) {
this.loops = loops;
}
/**
* Returns the number of iterations through the images currently set.
*
* @return Number of iterations through the images.
*/
public int getLoops() {
return loops;
}
/**
* Sets the default disposal method for newly added Layer
s.
* This setting does not affect Layer
s already added to
* the animation object !
*
* @param defaultDisposal The disposal method to use. This must be one of
* DISPOSAL_* constants defined above.
*
* @throws IllegalArgumentException if the disposal value is illegal.
*
* @see #DISPOSAL_NONE
* @see #DISPOSAL_NO
* @see #DISPOSAL_BACKGROUND
* @see #DISPOSAL_PREVIOUS
*/
public void setDefaultDisposal(int defaultDisposal) {
this.defaultDisposal = defaultDisposal;
}
/**
* Return the current default disposal method used for newly added
* Layer
s.
*
* @return current default disposal method used
*/
public int getDefaultDisposal() {
return defaultDisposal;
}
/**
* Sets the desired background color. If the desired color is -1, the
* background color of the first layer in the animation is taken as the
* animations background color.
*
* @param bgColor The new background color to set. Only the
* red, green and blue values are used, the alpha value is ignored
* as GIF does not support alpha values.
*/
public void setBackgroundColor(int bgColor) {
this.bgColor = bgColor & 0xffffff;
}
/**
* Returns the current desired background color, -1 if undefined.
* @return the current desired background color, -1 if undefined.
*/
public int getBackgroundColor() {
return bgColor;
}
/**
* Add a layer for display during the indicated delay time after which the
* image is disposed of using the default disposal method.
*
* @param layer the Layer
object to add
* @param delay the display time of the image in 1/100 sec.
*
* @throws NullPointerException if the layer is null
.
*/
public void addLayer(Layer layer, int delay) {
addLayer(layer, delay, defaultDisposal);
}
/**
* Add a layer for display during the indicated delay time after which the
* image is disposed of using the disposal method.
*
* @param layer the Layer
object to add
* @param delay the display time of the image in 1/100 sec.
* @param disposal the disposal method for the object. This must be one
* of the predefined DISPOSAL_* constants.
*
* @throws NullPointerException if the layer is null
.
* @throws IllegalArgumentException if the disposal value is not one
* of the predefined DISPOSAL_* constants.
*
* @see #DISPOSAL_NONE
* @see #DISPOSAL_NO
* @see #DISPOSAL_BACKGROUND
* @see #DISPOSAL_PREVIOUS
*/
public void addLayer(Layer layer, int delay, int disposal) {
layers.add(new Patch(layer, delay, disposal));
}
/**
* Remove the layer with the given index from the Animation
.
*
* @param idx index of the Layer
in the list
*
* @return the layer removed from the Animation
*
* @throws IndexOutOfBoundsException if the index is less than zero or
* bigger than the number of layers in the animation.
* @throws NullPointerException if no layer is present at the given
* position.
*/
public Layer removeLayer(int idx) {
Patch p = (Patch)layers.remove(idx);
return (p != null) ? p.getLayer() : null;
}
/**
* Removes the first layer (in chronological order) matching the given
* layer from the animation. If a layer is contained more than once
* within the animation, you must use multiple calls to
* removeLayer
.
*
* @param layer Layer
object to remove from the animation
*
* @return the layer removed from the animation or null
if
* the layer was not part of the animation.
*/
public Layer removeLayer(Layer layer) {
Iterator iter = layers.iterator();
while (iter.hasNext()) {
Patch patch = (Patch)iter.next();
if (patch.getLayer() == layer) {
layers.remove(patch);
return layer;
}
}
// if we come here, no match was found
return null;
}
/**
* Write the animation to the output indicated.
*
* @param mimeType MIME type indicating the graphics output file format
* to use. Currently only image/gif
is
* supported.
* @param numColors The number of colors to generate in the animated GIF, if
* not in the range [2 .. 256], 256 is taken as the
* default.
* @param outs the OutputStream
used for output
*
* @throws IOException got from called write
methods
* @throws IllegalArgumentException or if the mimeType
is
* not image/gif
or if the
* OutputStream
is null
.
*/
public void write(String mimeType, int numColors, OutputStream outs)
throws IOException {
// Check the mimeType
if ((mimeType == null) || (mimeType.toLowerCase().indexOf("gif") < 0)) {
throw new IllegalArgumentException("image/gif support only");
}
// Check the output stream
if (outs == null) {
throw new NullPointerException("outs");
}
// Check the numColors
if (numColors < 2 || numColors > 256) {
numColors = 256;
}
// try to get an image writer from the ImageIO API for the MIME type
Iterator writers = ImageIO.getImageWritersByMIMEType(mimeType);
ImageWriter writer = (ImageWriter)writers.next();
// Attach the outStream to the ImageIO
ImageOutputStream ios = ImageIO.createImageOutputStream(outs);
writer.setOutput(ios);
// The stream meta data - max size and background color
IIOMetadata streamMetadata = getStreamMetaData(writer);
IIOMetadata imageMetadata = null;
Iterator iter = layers.iterator();
while (iter.hasNext()) {
Patch patch = (Patch)iter.next();
Layer layer = patch.getLayer();
IIOMetadata gifMeta[]
= ImageSupport.createGIFMetadata(layer, writer, numColors);
if (gifMeta[1] != null) {
imageMetadata = gifMeta[1];
} else {
imageMetadata = writer.getDefaultImageMetadata(null, null);
}
// define the animation stuff..
GIFImageMetadata gifIM = (GIFImageMetadata)imageMetadata;
gifIM.delayTime = patch.getDelay();
gifIM.disposalMethod = patch.getDisposal();
// write the image
IIOImage image = new IIOImage(layer.getImage(),null,imageMetadata);
writer.write(streamMetadata, image, null);
}
// Release resources
writer.dispose();
ios.close();
// ensure stream is written completely
outs.flush();
}
//---------- Object overwrites ---------------------------------------------
/**
* Convert the Animation to some string representation for intelligent
* display.
* @return the string representation of the Animation
object.
*/
public String toString() {
return "Animation : loops=" + loops + ", defaultDisposal=" +
defaultDisposal + ", patches:" + layers.size();
}
//---------- internal ------------------------------------------------------
/**
* Calculate the bound of all the layers in the animation to get
* the screen size of the logical screen.
*
* @return a Rect
object indicating the rectangle within
* which all layers can be placed.
*/
private IIOMetadata getStreamMetaData (ImageWriter writer) {
int height = 0;
int width = 0;
long bgcolor = bgColor;
Iterator iter = layers.iterator();
while (iter.hasNext()) {
Patch patch = (Patch)iter.next();
Layer l = patch.getLayer();
if (width < l.getWidth()) width = l.getWidth();
if (height < l.getHeight()) height = l.getHeight();
if (bgcolor == -1) bgcolor = l.getBackgroundColor().getRGB();
}
GIFStreamMetadata streamMetadata = (GIFStreamMetadata) writer.getDefaultStreamMetadata(null);
streamMetadata.logicalScreenHeight = height;
streamMetadata.logicalScreenWidth = width;
streamMetadata.backgroundColorIndex = (int)bgcolor;
if (loops >= 0) {
streamMetadata.setLoops(loops);
}
return streamMetadata;
}
//----------- internal class -----------------------------------------------
/**
* The Patch
class encapsulates a patch representing one
* single image added to the animation. The class is simply a container
* for the data. There is no application logic contained in the class.
*
* @version $Revision$, $Date$
* @author fmeschbe
* @since coati
*/
private static class Patch {
/**
* The delay time before disposing of this patch's image. The time
* is measured in 1/100th of seconds, i.e. 1 second is 100
*/
private final int delay;
/**
* Disposal method for the patch's image. The method is one of the
* constants defined in the {@link Animation} class.
*/
private final int disposal;
/**
* Image layer for this patch.
*/
private final Layer layer;
/**
* Create a patch with the layer given, to be displayed for the
* delay time given and disposed using the indicated method.
*
* @param layer the layer for this patch
* @param delay the deley in 1/100th of seconds
* @param disposal the disposal method used.
*
* @throws NullPointerException if the layer is null
.
* @throws IllegalArgumentException if the disposal value is not one
* of the predefined DISPOSAL_* constants.
*/
public Patch(Layer layer, int delay, int disposal) {
this.layer = layer;
this.delay = delay;
this.disposal = disposal;
}
/**
* Returns the delay time of the image in 1/100th seconds.
*
* @return the delay time
*/
public int getDelay() {
return delay;
}
/**
* Returns the disposal method value, which presumably is one of the
* predefined constants.
*
* @return the disposal method of the image
*/
public int getDisposal() {
return disposal;
}
/**
* Returns the layer associated with the patch. The layer constitutes
* the image to be shown for the patch.
*
* @return the Layer
of the patch
*/
public Layer getLayer() {
return layer;
}
}
}