Loading an animated image to a BufferedImage array

2.2k views Asked by At

I'm trying to implement animated textures into an OpenGL game seamlessly. I made a generic ImageDecoder class to translate any BufferedImage into a ByteBuffer. It works perfectly for now, though it doesn't load animated images.

I'm not trying to load an animated image as an ImageIcon. I need the BufferedImage to get an OpenGL-compliant ByteBuffer.

How can I load every frames as a BufferedImage array in an animated image ? On a similar note, how can I get the animation rate / period ?

Does Java handle APNG ?

2

There are 2 answers

1
chubbsondubs On BEST ANSWER

I don't think Java supports APNG by default, but you can use an 3rd party library to parse it:

http://code.google.com/p/javapng/source/browse/trunk/javapng2/src/apng/com/sixlegs/png/AnimatedPngImage.java?r=300

That might be your easiest method. As for getting the frames from an animated gif you have to register an ImageObserver:

new ImageIcon( url ).setImageObserver( new ImageObserver() {
    public void imageUpdate( Image img, int infoFlags, int x, int y, int width, int height ) {
        if( infoFlags & ImageObserver.FRAMEBITS == ImageObserver.FRAMEBITS ) {
            // another frame was loaded do something with it.
        }
    }
});

This loads asynchronously on another thread so imageUpdate() won't be called immediately. But it will be called for each frame as it parses it.

http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/image/ImageObserver.html

0
Frederic Leitenberger On

The following code is an adaption from my own implementation to accommodate the "into array" part.

The problem with gifs is: There are different disposal methods which have to be considered, if you want this to work with all of them. The code below tries to compensate for that. For example there is a special implementation for "doNotDispose" mode, which takes all frames from start to N and paints them on top of each other into a BufferedImage.

The advantage of this method over the one posted by chubbsondubs is that it does not have to wait for the gif animation delays, but can be done basically instantly.

BufferedImage[] array = null;
ImageInputStream imageInputStream = ImageIO.createImageInputStream(new ByteArrayInputStream(data)); // or any other source stream
Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(imageInputStream);
while (imageReaders.hasNext())
{
    ImageReader reader = (ImageReader) imageReaders.next();
    try
    {
        reader.setInput(imageInputStream);
        frames = reader.getNumImages(true);
        array = new BufferedImage[frames];
        for (int frameId : frames)
        {
            int w = reader.getWidth(0);
            int h = reader.getHeight(0);
            int fw = reader.getWidth(frameId);
            int fh = reader.getHeight(frameId);
            if (h != fh || w != fw)
            {
                GifMeta gm = getGifMeta(reader.getImageMetadata(frameId));
                // disposalMethodNames: "none", "doNotDispose","restoreToBackgroundColor","restoreToPrevious",
                if ("doNotDispose".equals(gm.disposalMethod))
                {
                    image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
                    Graphics2D g = (Graphics2D) image.getGraphics();
                    for (int f = 0; f <= frameId; f++)
                    {
                        gm = getGifMeta(reader.getImageMetadata(f));

                        if ("doNotDispose".equals(gm.disposalMethod))
                        {
                            g.drawImage(reader.read(f), null, gm.imageLeftPosition, gm.imageTopPosition);
                        }
                        else
                        {
                            // XXX "Unimplemented disposalMethod (" + getName() + "): " + gm.disposalMethod);
                        }
                    }
                    g.dispose();
                }
                else
                {
                    image = reader.read(frameId);
                    // XXX "Unimplemented disposalMethod (" + getName() + "): " + gm.disposalMethod;
                }
            }
            else
            {
                image = reader.read(frameId);
            }
            if (image == null)
            {
                throw new NullPointerException();
            }
            array[frame] = image;
        }
    }
    finally
    {
        reader.dispose();
    }
}
return array;

private final static class GifMeta
{

    String disposalMethod = "none";
    int imageLeftPosition = 0;
    int imageTopPosition = 0;
    int delayTime = 0;
}

private GifMeta getGifMeta(IIOMetadata meta)
{
    GifMeta gm = new GifMeta();
    final IIOMetadataNode gifMeta = (IIOMetadataNode) meta.getAsTree("javax_imageio_gif_image_1.0");
    NodeList childNodes = gifMeta.getChildNodes();
    for (int i = 0; i < childNodes.getLength(); ++i)
    {
        IIOMetadataNode subnode = (IIOMetadataNode) childNodes.item(i);
        if (subnode.getNodeName().equals("GraphicControlExtension"))
        {
            gm.disposalMethod = subnode.getAttribute("disposalMethod");
            gm.delayTime = Integer.parseInt(subnode.getAttribute("delayTime"));
        }
        else if (subnode.getNodeName().equals("ImageDescriptor"))
        {
            gm.imageLeftPosition = Integer.parseInt(subnode.getAttribute("imageLeftPosition"));
            gm.imageTopPosition = Integer.parseInt(subnode.getAttribute("imageTopPosition"));
        }
    }
    return gm;
}