Reading all frames and information of GIF [solution]

V

Vincent Vollers

Well, I've been bussy with a gif loader, because I needed all the frames.
It's just some code around the ImageIO library. But since I was unable to
find much about it, I decided to experiment myself a bit.

Please tell me if you find areas where to following code can be improved or
cleaned up.

This is just a snapshot from a larger class, but you'll find it easy to
copy/paste it in your own code. The function "initialize(InputStream
stream)" takes an inputstream (such as a FileInputStream) and tries to
construct all frames. For each frame, the function addFrame(Image image,
long delay) is called. (image is an ARGB BufferedImage and delay is in
millis).

The first couple of functions are xml node processing functions because the
metadata is stored in w3c xml nodes... which are a *** in the *** to
process. And I -refuse- to add the apache XPATH libs just to read a GIF
file.

Also, can anyone explain to me why there are 2 functions in the IIOImage
class to return either a RenderedImage OR a Raster. What is the design logic
of that?

One last note, one of the things which isn't truly clean is the fact that I
feed the result of a function which could -possibly- return a null value
directly to Integer.ParseInt. The reason is that all GIF files have that
data. I know.. I know.. presumptions arent solid reasoning. but
still -grin-...

Anyway, I hope you find this usefull, let me know.

(This is a copy from the same thread on sun's java forums:
http://forum.java.sun.com/thread.jspa?threadID=629277&tstart=0 )

=============
Start Code
=============

private static final String NODEPATH_WIDTH =
"LogicalScreenDescriptor.logicalScreenWidth";
private static final String NODEPATH_HEIGHT =
"LogicalScreenDescriptor.logicalScreenHeight";
private static final String NODEPATH_BACKGROUNDCOLORINDEX =
"GlobalColorTable.backgroundColorIndex";
private static final String NODEPATH_COLORENTRY =
"GlobalColorTable.ColorTableEntry";
private static final String NODEPATH_DELAYTIME =
"GraphicControlExtension.delayTime";
private static final String NODEPATH_IMAGELEFTPOS =
"ImageDescriptor.imageLeftPosition";
private static final String NODEPATH_IMAGETOPPOS =
"ImageDescriptor.imageTopPosition";
private static final String NODEPATH_DISPOSALMETHOD =
"GraphicControlExtension.disposalMethod";
private static final String NODEPATH_ISTRANSPARENT =
"GraphicControlExtension.transparentColorFlag";
private static final String NODEPATH_TRANSPARENTCOLORINDEX =
"GraphicControlExtension.transparentColorIndex";

private static final String DISPOSALMETHOD_RESTOREPREVIOUS =
"restoreToPrevious";
private static final String DISPOSALMETHOD_CLEARTOBACKGROUNDCOLOR =
"restoreToBackgroundColor";

private static final String NODENAME_INDEX = "index";
private static final String NODENAME_RED = "red";
private static final String NODENAME_GREEN = "green";
private static final String NODENAME_BLUE = "blue";

private static final String EXTENSION_GIF = "gif";

private static final String ERROR_INPUTSTREAMISNULL = "InputStream must not
be null";
private static final String ERROR_NOCOMPATIBLEREADERFOUND = "No GIF
compatible imagereader found";
private static final String ERROR_IMAGEINPUTSTREAMNOTACCEPTED = "Provided
stream is not a GIF image";

private ArrayList<Node> findNodesWithName(Node n, String s) {
NodeList l = n.getChildNodes();

ArrayList<Node> nodes = new ArrayList<Node>();

for(int i=0; i<l.getLength(); i++) {
if(l.item(i).getNodeName().equals(s)) {
nodes.add(l.item(i));
}
}

NamedNodeMap attributes = n.getAttributes();
if(attributes != null) {
Node nn = attributes.getNamedItem(s);
if(nn != null) {
nodes.add(nn);
}
}
return nodes;
}

private Node findNodeWithName(Node n, String s) {
NodeList l = n.getChildNodes();

ArrayList<Node> nodes = new ArrayList<Node>();

for(int i=0; i<l.getLength(); i++) {
if(l.item(i).getNodeName().equals(s)) {
return l.item(i);
}
}
return null;
}

private ArrayList<Node> findNodesWithPath(Node n, String path) {
if(n == null || path == null || path.length() == 0)
return null;

String[] nodeNames = path.split("\\.");

if(nodeNames.length == 0)
return null;

Node currentNode = n;

for(int i=0;i<nodeNames.length-1;i++) {
currentNode = findNodeWithName(currentNode, nodeNames);
if(currentNode == null) {
return null;
}
}

return findNodesWithName(currentNode, nodeNames[nodeNames.length-1]);
}

private String findValueWithPath(Node n, String path) {
ArrayList<Node> nodes = findNodesWithPath(n, path);

if(nodes.size() > 0)
return nodes.get(0).getNodeValue();

return null;
}

private ImageReader getImageReader(String format) {
Iterator<ImageReader> readerIterator =
ImageIO.getImageReadersBySuffix(format);
if(!readerIterator.hasNext())
return null;

return readerIterator.next();
}

private void initialize(InputStream stream) throws IOException {
// ensure we have a stream
if(stream == null)
throw new IOException(ERROR_INPUTSTREAMISNULL);

// ensure we have a reader which can read gifs
ImageReader reader = getImageReader(EXTENSION_GIF);
if(reader == null)
throw new IOException(ERROR_NOCOMPATIBLEREADERFOUND);

// ensure we can create an ImageInputStream from the given Inputstream
ImageInputStream imageInputStream = ImageIO.createImageInputStream(stream);
// (IOException will be thrown if there was an error)

try {
reader.setInput(imageInputStream);
} catch(IllegalArgumentException e) {
throw new IOException(ERROR_IMAGEINPUTSTREAMNOTACCEPTED + e.toString());
}

// read all images in the gif stream,
// giving a null argument because we dont want give
// any special parameters to the gifreader
Iterator<IIOImage> images = reader.readAll(null);

// get the metadata for the entire stream
IIOMetadata streamMetaData = reader.getStreamMetadata();
Node streamRoot =
streamMetaData.getAsTree(streamMetaData.getNativeMetadataFormatName());

// process the metadata, find the dimensions for this animation
// and the backgroundcolor
int imageWidth = Integer.parseInt(findValueWithPath(streamRoot,
NODEPATH_WIDTH));
int imageHeight = Integer.parseInt(findValueWithPath(streamRoot,
NODEPATH_HEIGHT));

int backgroundColorIndex = Integer.parseInt(findValueWithPath(streamRoot,
NODEPATH_BACKGROUNDCOLORINDEX));

// set the default backgroundcolor to black
Color backgroundColor = Color.black;

// Get the color palette as given by the gif file
// and search for the background color
// when found, set the backgroundColor to that value
ArrayList<Node> colors = findNodesWithPath(streamRoot,
NODEPATH_COLORENTRY);
for(Node n : colors) {
int index =
Integer.parseInt(n.getAttributes().getNamedItem(NODENAME_INDEX).getNodeValue());
if(index == backgroundColorIndex) {
int r =
Integer.parseInt(n.getAttributes().getNamedItem(NODENAME_RED).getNodeValue());
int g =
Integer.parseInt(n.getAttributes().getNamedItem(NODENAME_GREEN).getNodeValue());
int b =
Integer.parseInt(n.getAttributes().getNamedItem(NODENAME_BLUE).getNodeValue());

backgroundColor = new Color(r,g,b);
break;
}
}

// Initialize the default image
BufferedImage resultImage = new BufferedImage(imageWidth, imageHeight,
BufferedImage.TYPE_INT_ARGB);
BufferedImage previousImage;

// for each frame in the gif animation
while(images.hasNext()) {
// create a copy for the disposal method "restoreToPrevious"
previousImage = new BufferedImage(imageWidth, imageHeight,
BufferedImage.TYPE_INT_ARGB);
previousImage.setData(resultImage.copyData(null));

// fetch the frame
IIOImage im = images.next();

// get the metadata for this frame
IIOMetadata metaData = im.getMetadata();
Node root = metaData.getAsTree(metaData.getNativeMetadataFormatName());

// we need to determine the "delay" factor,
// the amount of time between this frame and the next
int delay = 0;
String delayString = findValueWithPath(root, NODEPATH_DELAYTIME);

if(delayString != null) {
// values returned are in (1/100), multiply by 10 to convert to millis
(1/1000)
// for ease of use with Thread.sleep() and System.currentTimeMillis()

delay = Integer.parseInt(delayString) * 10;
}

// Gif animations return a renderedImage
RenderedImage i = im.getRenderedImage();

// determine where to draw on current frame,
// in the case of a progressive gif animation
int imageLeft= Integer.parseInt(findValueWithPath(root,
NODEPATH_IMAGELEFTPOS));
int imageTop = Integer.parseInt(findValueWithPath(root,
NODEPATH_IMAGETOPPOS));

// paint the result on the renderedImage
resultImage.createGraphics().drawRenderedImage(i,
AffineTransform.getTranslateInstance(imageLeft, imageTop));

// Now we have a new frame, create a bufferedImage to hold this frame
BufferedImage newIm = new BufferedImage(imageWidth, imageHeight,
BufferedImage.TYPE_INT_ARGB);

// and copy the framedata to it
newIm.setData(resultImage.copyData(null));

// and store it somewhere
this.addFrame(newIm, delay);

// now we have to take a look at how to dispose of the current frame
String disposalMethod = findValueWithPath(root, NODEPATH_DISPOSALMETHOD);

// we either restore it to the previous image
if(disposalMethod.equals(DISPOSALMETHOD_RESTOREPREVIOUS)) {
resultImage.setData(previousImage.copyData(null));

// or to the background color
} else if(disposalMethod.equals(DISPOSALMETHOD_CLEARTOBACKGROUNDCOLOR)) {
Graphics2D g = resultImage.createGraphics();

// Set the graphics2D to overwrite mode,
// otherwise painting with transparent colors
// wont be very effective
g.setPaintMode();

// determine if we have a transparent gif
// and wheter the backgroundcolor is the
// transparent color
boolean hasTransparancy = Boolean.parseBoolean(findValueWithPath(root,
NODEPATH_ISTRANSPARENT));
int transParantColorIndex = Integer.parseInt(findValueWithPath(root,
NODEPATH_TRANSPARENTCOLORINDEX));

if(hasTransparancy && backgroundColorIndex == transParantColorIndex) {
// since the original color isn't transparent
// we must create a new color which is
g.setColor(
new Color(
backgroundColor.getRed(),
backgroundColor.getGreen(),
backgroundColor.getBlue(),
0.0f
)
);
} else {
// use the opaque backgroundColor
g.setColor(backgroundColor);
}

// clear the image
g.fillRect(0,0, imageWidth, imageHeight);
}

// ok.. next frame
}
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,776
Messages
2,569,603
Members
45,191
Latest member
BuyKetoBeez

Latest Threads

Top