Using GIF animations

Hi.

I’m considering using a gif animation in my game but I don’t know even if it is possible. I’m sure that other people have made themself the same question before.

Can I somehow load a GIF image and then access its frames one per one? Is it any “automatic” way of doing so such as function “drawGIFAnimation(image, frame)”?

Last, to have a all-in-one post, I would be please if someone knows an open source animation format (I’m afraid that gif is copyrighted) that could acomplish my previous needs.

Thanks a lot.

PS: Great forum. Should have discovered long time ago!

Most people use sprite sheets (try the search function). Like this one:

http://kaioa.com/k/newexplosion7_sheet.png

For displaying individual frames you simply draw a specific region of the image.

I know. I just thought that GIF files maybe internally have the same structure and there was some way of calling its frames directly.

Thanks.

Yes, it’s possible… I dont have time to explain much, but i’ll do that later…


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[i]);
		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 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 static final int IMAGETYPE = BufferedImage.TYPE_4BYTE_ABGR;

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

private void initialize(InputStream stream, boolean calcTexture) 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, IMAGETYPE);
	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, IMAGETYPE);
		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, IMAGETYPE);
		
		// and copy the framedata to it
		newIm.setData(resultImage.copyData(null));
		
		// and store it somewhere
		this.addFrame(newIm, delay, calcTexture);
		
		// 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
	}

I’ve had .gif’s animate by loading them via ImageIcon.

I also had the component that drew the image (a JPanel) implement ImageObserver, and passed it to Graphics.drawImage() when drawing the ImageIcon.