<html><head>

<style id="css_styles"> 
blockquote.cite { margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:0px; border-left: 1px solid #cccccc }
blockquote.cite2 {margin-left: 5px; margin-right: 0px; padding-left: 10px; padding-right:0px; border-left: 1px solid #cccccc; margin-top: 3px; padding-top: 0px; }
a img { border: 0px; }
table { border-collapse: collapse; }
li[style='text-align: center;'], li[style='text-align: center; '], li[style='text-align: right;'], li[style='text-align: right; '] {  list-style-position: inside;}
body { font-family: Helvetica; font-size: 9pt; }
.quote { margin-left: 1em; margin-right: 1em; border-left: 5px #ebebeb solid; padding-left: 0.3em; }
a.em-mention[href] { text-decoration: none; color: inherit; border-radius: 3px; padding-left: 2px; padding-right: 2px; background-color: #e2e2e2; }

 </style>
</head>
<body style="overflow-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;">

<div></div><div>I have a generic question for the group. I’m trying to implement a method resembling:</div><div><br /></div><div><div id="x3a7cf97c41bc4858b167a4bb585d98cf"><span style="color: rgb(8, 8, 8); font-family: monospace; font-size: 13.066667px; margin: 13.066667px 0px;">public </span><span style="color: rgb(8, 8, 8); font-family: monospace; font-size: 13.066667px; margin: 13.066667px 0px;">BufferedImage seek(File gifFile, </span><span style="color: rgb(8, 8, 8); font-family: monospace; font-size: 13.066667px; margin: 13.066667px 0px;">int </span><span style="color: rgb(8, 8, 8); font-family: monospace; font-size: 13.066667px; margin: 13.066667px 0px;">millis) </span></div></div><div><br /></div><div>Ideally I’d like to:</div><div>1. Not add a 3rd party jar to our class path</div><div>2. Not write a new file</div><div>3. Not load the entire gif file into memory as a byte array</div><div>4. Use well-tested/stable code to handle gif frame disposal, custom color palettes, and any other obscure gif parsing challenges.</div><div><br /></div><div>ImageIO doesn’t really work without a lot of intervention. It can return the individual frames of a GIF, but it becomes the caller’s responsibility to handle frame disposal/placement.</div><div><br /></div><div>So I tried working with ToolkitImages. What I wrote (see below) functionally works, but it’s not an acceptable solution because it’s too slow. The problem now is sun.awt.image.GifImageDecoder calls Thread.sleep(delay). So it acts more like a player than a parser. If my gif file contains 10 one-second frames, then this code takes at least 9 seconds.</div><div><br /></div><div>This feels way too hard for such a simple ask. Is there a solution / toolset I’m missing? All the code I need already exists in the desktop module, but I seem unable to leverage it.</div><div><br /></div><div>Regards,</div><div> - Jeremy</div><div><br /></div><div>———</div><div><div style="color: rgb(8, 8, 8); font-family: "JetBrains Mono", monospace; font-size: 9.8pt;"><pre><span style="color:#0033B3;">import </span>javax.imageio.ImageIO;<br /><span style="color:#0033B3;">import </span>javax.imageio.ImageReader;<br /><span style="color:#0033B3;">import </span>javax.imageio.metadata.IIOMetadataNode;<br /><span style="color:#0033B3;">import </span>java.awt.*;<br /><span style="color:#0033B3;">import </span>java.awt.image.*;<br /><span style="color:#0033B3;">import </span>java.io.*;<br /><span style="color:#0033B3;">import </span>java.util.ArrayList;<br /><span style="color:#0033B3;">import </span>java.util.Hashtable;<br /><span style="color:#0033B3;">import </span>java.util.Objects;<br /><span style="color:#0033B3;">import </span>java.util.List;<br /><span style="color:#0033B3;">import </span>java.util.concurrent.Semaphore;<br /><span style="color:#0033B3;">import </span>java.util.concurrent.atomic.AtomicInteger;<br /><span style="color:#0033B3;">import </span>java.util.concurrent.atomic.AtomicReference;<br /><br /><span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;"> * This uses a combination of java.awt.Image classes and ImageIO classes<br /></span><span style="color:#8C8C8C;font-style:italic;"> * to convert a GIF image to a series of frames.<br /></span><span style="color:#8C8C8C;font-style:italic;"> */<br /></span><span style="color:#0033B3;">public class </span>GifReader {<br /><br />    <span style="color:#0033B3;">public enum </span>FrameConsumerResult {<br />        CONTINUE, STOP, SKIP<br />    }<br /><br />    <span style="color:#0033B3;">public final static class </span>Info {<br />        <span style="color:#0033B3;">public final int </span>width, height, numberOfFrames, duration;<br />        <span style="color:#0033B3;">public final boolean </span>isLooping;<br /><br />        <span style="color:#0033B3;">public </span>Info(<span style="color:#0033B3;">int </span>width, <span style="color:#0033B3;">int </span>height, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">int </span>duration, <span style="color:#0033B3;">boolean </span>isLooping) {<br />            <span style="color:#0033B3;">this</span>.width = width;<br />            <span style="color:#0033B3;">this</span>.height = height;<br />            <span style="color:#0033B3;">this</span>.numberOfFrames = numberOfFrames;<br />            <span style="color:#0033B3;">this</span>.duration = duration;<br />            <span style="color:#0033B3;">this</span>.isLooping = isLooping;<br />        }<br />    }<br /><br />    <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">     * Consumes information about the frames of a GIF animation.<br /></span><span style="color:#8C8C8C;font-style:italic;">     */<br /></span><span style="color:#8C8C8C;font-style:italic;">    </span><span style="color:#0033B3;">public interface </span>GifFrameConsumer {<br />        <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">         * This provides meta information about a GIF image.<br /></span><span style="color:#8C8C8C;font-style:italic;">         *<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @return true if the reader should start supplying frame data, or false if this meta information<br /></span><span style="color:#8C8C8C;font-style:italic;">         * is all the consumer wanted.<br /></span><span style="color:#8C8C8C;font-style:italic;">         */<br /></span><span style="color:#8C8C8C;font-style:italic;">        </span><span style="color:#0033B3;">boolean </span>startImage(<span style="color:#0033B3;">int </span>imageWidth, <span style="color:#0033B3;">int </span>imageHeight, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">int </span>durationMillis, <span style="color:#0033B3;">boolean </span>isLooping);<br /><br />        <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param frameIndex the frame index (starting at 0)<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param numberOfFrames the total number of frames in the GIF image.<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param startTimeMillis the start time of this frame (relative to the start time of the animation)<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param durationMillis the duration of this frame<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @return if this returns CONTINUE then this consumer expects {@link #consumeFrame(BufferedImage, int, int, int, int, boolean)}<br /></span><span style="color:#8C8C8C;font-style:italic;">         * to be called next. If this returns STOP then this consumer expects to stop all reading. If this returns SKIP<br /></span><span style="color:#8C8C8C;font-style:italic;">         * then this consumer is not interested in this frame, but it expects to be asked about `frameIndex + 1`.<br /></span><span style="color:#8C8C8C;font-style:italic;">         */<br /></span><span style="color:#8C8C8C;font-style:italic;">        </span><span style="color:#0033B3;">default </span>FrameConsumerResult startFrame(<span style="color:#0033B3;">int </span>frameIndex, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">int </span>startTimeMillis, <span style="color:#0033B3;">int </span>durationMillis) {<br />            <span style="color:#0033B3;">return </span>FrameConsumerResult.CONTINUE;<br />        }<br /><br />        <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">         * Consume a new frame from a GIF image.<br /></span><span style="color:#8C8C8C;font-style:italic;">         *<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param frame an INT_ARGB image. This BufferedImage reference will be reused with each<br /></span><span style="color:#8C8C8C;font-style:italic;">         *              call to this method, so if you want to keep these images in memory you<br /></span><span style="color:#8C8C8C;font-style:italic;">         *              need to clone this image.<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param startTimeMillis the start time of this frame (relative to the start time of the animation)<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param frameIndex the current frame index (starting at 0).<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param numberOfFrames the total number of frames in the GIF image.<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param frameDurationMillis the duration of this frame in milliseconds<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @param isDone if true then this method will not be called again.<br /></span><span style="color:#8C8C8C;font-style:italic;">         * @return true if the reader should continue reading additional frames, or false if the reader should<br /></span><span style="color:#8C8C8C;font-style:italic;">         * immediately stop. This return value is ignored if `isDone` is true.<br /></span><span style="color:#8C8C8C;font-style:italic;">         */<br /></span><span style="color:#8C8C8C;font-style:italic;">        </span><span style="color:#0033B3;">boolean </span>consumeFrame(BufferedImage frame, <span style="color:#0033B3;">int </span>startTimeMillis, <span style="color:#0033B3;">int </span>frameDurationMillis, <span style="color:#0033B3;">int </span>frameIndex, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">boolean </span>isDone);<br />    }<br /><br />    <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">     * Read a GIF image.<br /></span><span style="color:#8C8C8C;font-style:italic;">     *<br /></span><span style="color:#8C8C8C;font-style:italic;">     * @param gifFile the GIF image file to read.<br /></span><span style="color:#8C8C8C;font-style:italic;">     * @param waitUntilFinished if true then this method will not return until the GifFrameConsumer<br /></span><span style="color:#8C8C8C;font-style:italic;">     *                          has received every frame.<br /></span><span style="color:#8C8C8C;font-style:italic;">     * @param frameConsumer the consumer that will consume the image data.<br /></span><span style="color:#8C8C8C;font-style:italic;">     */<br /></span><span style="color:#8C8C8C;font-style:italic;">    </span><span style="color:#0033B3;">public void </span>read(<span style="color:#0033B3;">final </span>File gifFile, <span style="color:#0033B3;">final boolean </span>waitUntilFinished, <span style="color:#0033B3;">final </span>GifFrameConsumer frameConsumer) <span style="color:#0033B3;">throws </span>IOException {<br />        Objects.requireNonNull(frameConsumer);<br />        <span style="color:#0033B3;">final </span>Semaphore semaphore = <span style="color:#0033B3;">new </span>Semaphore(<span style="color:#1750EB;">1</span>);<br />        semaphore.acquireUninterruptibly();<br /><br />        <span style="color:#0033B3;">try </span>(FileInputStream gifFileIn = <span style="color:#0033B3;">new </span>FileInputStream(gifFile)) {<br />            List<Integer> frameDurationsMillis = readFrameDurationMillis(gifFileIn);<br />            Image image = Toolkit.getDefaultToolkit().createImage(gifFile.getPath());<br />            ImageConsumer consumer = <span style="color:#0033B3;">new </span>ImageConsumer() {<br />                <span style="color:#0033B3;">private </span>BufferedImage bi;<br />                <span style="color:#0033B3;">private boolean </span>isActive = <span style="color:#0033B3;">true</span>;<br />                <span style="color:#0033B3;">private int </span>frameCtr, imageWidth, imageHeight, currentFrameStartTime;<br />                <span style="color:#0033B3;">private boolean </span>ignoreCurrentFrame;<br /><br />                @Override<br />                <span style="color:#0033B3;">public void </span>setDimensions(<span style="color:#0033B3;">int </span>width, <span style="color:#0033B3;">int </span>height) {<br />                    imageWidth = width;<br />                    imageHeight = height;<br /><br />                    <span style="color:#8C8C8C;font-style:italic;">// if this gif loops:<br /></span><span style="color:#8C8C8C;font-style:italic;">                    // the sun.awt.image.GifImageDecoder calls ImageFetcher.startingAnimation, which<br /></span><span style="color:#8C8C8C;font-style:italic;">                    // changes the name of this thread. We don't know how many times it's supposed<br /></span><span style="color:#8C8C8C;font-style:italic;">                    // to loop, but in my experience gifs either don't loop at all or they loop forever;<br /></span><span style="color:#8C8C8C;font-style:italic;">                    // they aren't asked to loop N-many times anymore<br /></span><span style="color:#8C8C8C;font-style:italic;">                    </span><span style="color:#0033B3;">boolean </span>isLooping = Thread.currentThread().getName().contains(<span style="color:#067D17;">"Image Animator"</span>);<br /><br />                    <span style="color:#0033B3;">try </span>{<br />                        <span style="color:#0033B3;">int </span>totalDuration = <span style="color:#1750EB;">0</span>;<br />                        <span style="color:#0033B3;">for </span>(<span style="color:#0033B3;">int </span>frameDuration : frameDurationsMillis)<br />                            totalDuration += frameDuration;<br /><br />                        <span style="color:#0033B3;">if </span>(!frameConsumer.startImage(width, height, frameDurationsMillis.size(), totalDuration, isLooping))<br />                            stop(<span style="color:#0033B3;">null</span>);<br />                    } <span style="color:#0033B3;">catch</span>(Exception e) {<br />                        stop(e);<br />                    }<br />                }<br /><br />                @Override<br />                <span style="color:#0033B3;">public void </span>setProperties(Hashtable<?, ?> props) {}<br /><br />                @Override<br />                <span style="color:#0033B3;">public void </span>setColorModel(ColorModel model) {}<br /><br />                @Override<br />                <span style="color:#0033B3;">public void </span>setHints(<span style="color:#0033B3;">int </span>hintflags) {<br />                    <span style="color:#8C8C8C;font-style:italic;">// this is called before every frame starts:<br /></span><span style="color:#8C8C8C;font-style:italic;">                    </span><span style="color:#0033B3;">int </span>frameDuration = frameDurationsMillis.get(frameCtr);<br />                    FrameConsumerResult r = frameConsumer.startFrame(frameCtr, frameDurationsMillis.size(), currentFrameStartTime, frameDuration);<br />                    <span style="color:#0033B3;">if </span>(r == FrameConsumerResult.STOP) {<br />                        stop(<span style="color:#0033B3;">null</span>);<br />                    } <span style="color:#0033B3;">else </span>{<br />                        ignoreCurrentFrame = r == FrameConsumerResult.SKIP;<br />                    }<br />                }<br /><br />                <span style="color:#0033B3;">private int</span>[] argbRow;<br />                <span style="color:#0033B3;">private int</span>[] colorModelRGBs;<br />                <span style="color:#0033B3;">private </span>IndexColorModel lastModel;<br /><br />                @Override<br />                <span style="color:#0033B3;">public void </span>setPixels(<span style="color:#0033B3;">final int </span>x, <span style="color:#0033B3;">final int </span>y, <span style="color:#0033B3;">final int </span>w, <span style="color:#0033B3;">final int </span>h,<br />                                      <span style="color:#0033B3;">final </span>ColorModel model, <span style="color:#0033B3;">final byte</span>[] pixels, <span style="color:#0033B3;">final int </span>off, <span style="color:#0033B3;">final int </span>scansize) {<br />                    <span style="color:#8C8C8C;font-style:italic;">// Even if ignoreCurrentFrame is true we still need to update the image every iteration.<br /></span><span style="color:#8C8C8C;font-style:italic;">                    // (This is because each frame in a GIF has a "disposal method", and some of them rely<br /></span><span style="color:#8C8C8C;font-style:italic;">                    // on the previous frame.) In theory we *may* be able to skip this method *sometimes*<br /></span><span style="color:#8C8C8C;font-style:italic;">                    // depending on the disposal methods in use, but that would take some more research.<br /></span><span style="color:#8C8C8C;font-style:italic;"><br /></span><span style="color:#8C8C8C;font-style:italic;">                    </span><span style="color:#0033B3;">try </span>{<br />                        <span style="color:#8C8C8C;font-style:italic;">// ImageConsumer javadoc says:<br /></span><span style="color:#8C8C8C;font-style:italic;">                        // Pixel (m,n) is stored in the pixels array at index (n * scansize + m + off)<br /></span><span style="color:#8C8C8C;font-style:italic;"><br /></span><span style="color:#8C8C8C;font-style:italic;">                        </span><span style="color:#0033B3;">final int </span>yMax = y + h;<br />                        <span style="color:#0033B3;">final int </span>xMax = x + w;<br /><br />                        <span style="color:#0033B3;">if </span>(model <span style="color:#0033B3;">instanceof </span>IndexColorModel icm) {<br />                            <span style="color:#0033B3;">if </span>(icm != lastModel) {<br />                                colorModelRGBs = <span style="color:#0033B3;">new int</span>[icm.getMapSize()];<br />                                icm.getRGBs(colorModelRGBs);<br />                            }<br />                            lastModel = icm;<br />                        } <span style="color:#0033B3;">else </span>{<br />                            colorModelRGBs = <span style="color:#0033B3;">null</span>;<br />                        }<br /><br />                        <span style="color:#0033B3;">if </span>(bi == <span style="color:#0033B3;">null</span>) {<br />                            bi = <span style="color:#0033B3;">new </span>BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);<br />                            argbRow = <span style="color:#0033B3;">new int</span>[imageWidth];<br />                        }<br /><br />                        <span style="color:#0033B3;">for </span>(<span style="color:#0033B3;">int </span>y_ = y; y_ < yMax; y_++) {<br />                            <span style="color:#8C8C8C;font-style:italic;">// we're not told to use (off-x), but empirically this is what we get/need:<br /></span><span style="color:#8C8C8C;font-style:italic;">                            </span><span style="color:#0033B3;">int </span>i = y_ * scansize + x + (off - x);<br />                            <span style="color:#0033B3;">for </span>(<span style="color:#0033B3;">int </span>x_ = x; x_ < xMax; x_++, i++) {<br />                                <span style="color:#0033B3;">int </span>pixel = pixels[i] & <span style="color:#1750EB;">0xff</span>;<br />                                <span style="color:#0033B3;">if </span>(colorModelRGBs != <span style="color:#0033B3;">null</span>) {<br />                                    argbRow[x_ - x] = colorModelRGBs[pixel];<br />                                } <span style="color:#0033B3;">else </span>{<br />                                    <span style="color:#8C8C8C;font-style:italic;">// I don't think we ever resort to this:<br /></span><span style="color:#8C8C8C;font-style:italic;">                                    </span>argbRow[x_ - x] = <span style="color:#1750EB;">0xff000000 </span>+ (model.getRed(pixel) << <span style="color:#1750EB;">16</span>) + (model.getGreen(pixel) << <span style="color:#1750EB;">8</span>) + (model.getBlue(pixel));<br />                                }<br />                            }<br />                            bi.getRaster().setDataElements(x, y_, w, <span style="color:#1750EB;">1</span>, argbRow);<br />                        }<br />                    } <span style="color:#0033B3;">catch</span>(RuntimeException e) {<br />                        <span style="color:#8C8C8C;font-style:italic;">// we don't expect this to happen, but if something goes wrong nobody else<br /></span><span style="color:#8C8C8C;font-style:italic;">                        // will print our stacktrace for us:<br /></span><span style="color:#8C8C8C;font-style:italic;">                        </span>stop(e);<br />                        <span style="color:#0033B3;">throw </span>e;<br />                    }<br />                }<br /><br />                @Override<br />                <span style="color:#0033B3;">public void </span>setPixels(<span style="color:#0033B3;">int </span>x, <span style="color:#0033B3;">int </span>y, <span style="color:#0033B3;">int </span>w, <span style="color:#0033B3;">int </span>h, ColorModel model, <span style="color:#0033B3;">int</span>[] pixels, <span style="color:#0033B3;">int </span>off, <span style="color:#0033B3;">int </span>scansize) {<br />                    <span style="color:#8C8C8C;font-style:italic;">// we never expect this for a GIF image<br /></span><span style="color:#8C8C8C;font-style:italic;">                    </span><span style="color:#0033B3;">throw new </span>UnsupportedOperationException();<br />                }<br /><br />                @Override<br />                <span style="color:#0033B3;">public void </span>imageComplete(<span style="color:#0033B3;">int </span>status) {<br />                    <span style="color:#0033B3;">try </span>{<br />                        <span style="color:#0033B3;">int </span>numberOfFrames = frameDurationsMillis.size();<br />                        <span style="color:#0033B3;">int </span>frameDuration = frameDurationsMillis.get(frameCtr);<br />                        <span style="color:#0033B3;">boolean </span>consumeResult;<br />                        <span style="color:#0033B3;">if </span>(ignoreCurrentFrame) {<br />                            consumeResult = <span style="color:#0033B3;">true</span>;<br />                        } <span style="color:#0033B3;">else </span>{<br />                            consumeResult = frameConsumer.consumeFrame(bi, currentFrameStartTime, frameDuration,<br />                                    frameCtr, numberOfFrames, frameCtr + <span style="color:#1750EB;">1 </span>>= numberOfFrames);<br />                        }<br />                        frameCtr++;<br />                        currentFrameStartTime += frameDuration;<br /><br />                        <span style="color:#8C8C8C;font-style:italic;">// if we don't remove this ImageConsumer the animating thread will loop forever<br /></span><span style="color:#8C8C8C;font-style:italic;">                        </span><span style="color:#0033B3;">if </span>(frameCtr == numberOfFrames || !consumeResult)<br />                            stop(<span style="color:#0033B3;">null</span>);<br />                    } <span style="color:#0033B3;">catch</span>(Exception e) {<br />                        stop(e);<br />                    }<br />                }<br /><br />                <span style="color:#0033B3;">private void </span>stop(Exception e) {<br />                    <span style="color:#0033B3;">synchronized </span>(<span style="color:#0033B3;">this</span>) {<br />                        <span style="color:#0033B3;">if </span>(!isActive)<br />                            <span style="color:#0033B3;">return</span>;<br />                        isActive = <span style="color:#0033B3;">false</span>;<br />                    }<br />                    <span style="color:#0033B3;">if </span>(e != <span style="color:#0033B3;">null</span>)<br />                        e.printStackTrace();<br />                    image.getSource().removeConsumer(<span style="color:#0033B3;">this</span>);<br />                    image.flush();<br />                    <span style="color:#0033B3;">if </span>(bi != <span style="color:#0033B3;">null</span>)<br />                        bi.flush();<br />                    semaphore.release();<br /><br />                }<br />            };<br />            image.getSource().startProduction(consumer);<br />        }<br />        <span style="color:#0033B3;">if </span>(waitUntilFinished)<br />            semaphore.acquireUninterruptibly();<br />    }<br /><br />    <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">     * Return the frame at a given time in an animation.<br /></span><span style="color:#8C8C8C;font-style:italic;">     */<br /></span><span style="color:#8C8C8C;font-style:italic;">    </span><span style="color:#0033B3;">public </span>BufferedImage seek(File gifFile, <span style="color:#0033B3;">int </span>millis) <span style="color:#0033B3;">throws </span>IOException {<br />        AtomicInteger seekTime = <span style="color:#0033B3;">new </span>AtomicInteger();<br />        AtomicReference<BufferedImage> returnValue = <span style="color:#0033B3;">new </span>AtomicReference<>();<br />        read(gifFile, <span style="color:#0033B3;">true</span>, <span style="color:#0033B3;">new </span>GifFrameConsumer() {<br />            @Override<br />            <span style="color:#0033B3;">public boolean </span>startImage(<span style="color:#0033B3;">int </span>imageWidth, <span style="color:#0033B3;">int </span>imageHeight, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">int </span>durationMillis, <span style="color:#0033B3;">boolean </span>isLooping) {<br />                seekTime.set(millis%durationMillis);<br />                <span style="color:#0033B3;">return true</span>;<br />            }<br /><br />            @Override<br />            <span style="color:#0033B3;">public </span>FrameConsumerResult startFrame(<span style="color:#0033B3;">int </span>frameIndex, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">int </span>startTimeMillis, <span style="color:#0033B3;">int </span>durationMillis) {<br />                <span style="color:#0033B3;">if </span>(numberOfFrames == <span style="color:#1750EB;">1 </span>||<br />                        (startTimeMillis <= seekTime.get() && seekTime.get() < startTimeMillis + durationMillis))<br />                    <span style="color:#0033B3;">return </span>FrameConsumerResult.CONTINUE;<br /><br />                <span style="color:#0033B3;">if </span>(startTimeMillis + durationMillis <= seekTime.get())<br />                    <span style="color:#0033B3;">return </span>FrameConsumerResult.SKIP;<br />                <span style="color:#0033B3;">return </span>FrameConsumerResult.STOP;<br />            }<br /><br />            @Override<br />            <span style="color:#0033B3;">public boolean </span>consumeFrame(BufferedImage frame, <span style="color:#0033B3;">int </span>startTimeMillis, <span style="color:#0033B3;">int </span>frameDurationMillis, <span style="color:#0033B3;">int </span>frameIndex, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">boolean </span>isDone) {<br />                returnValue.set(frame);<br />                <span style="color:#0033B3;">return false</span>;<br />            }<br />        });<br />        <span style="color:#0033B3;">return </span>returnValue.get();<br />    }<br /><br />    <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">     * Return basic information about a GIF image/animation.<br /></span><span style="color:#8C8C8C;font-style:italic;">     */<br /></span><span style="color:#8C8C8C;font-style:italic;">    </span><span style="color:#0033B3;">public </span>Info getInfo(File gifFile) <span style="color:#0033B3;">throws </span>IOException {<br />        AtomicReference<Info> returnValue = <span style="color:#0033B3;">new </span>AtomicReference<>();<br />        read(gifFile, <span style="color:#0033B3;">true</span>, <span style="color:#0033B3;">new </span>GifFrameConsumer() {<br />            @Override<br />            <span style="color:#0033B3;">public boolean </span>startImage(<span style="color:#0033B3;">int </span>imageWidth, <span style="color:#0033B3;">int </span>imageHeight, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">int </span>durationMillis, <span style="color:#0033B3;">boolean </span>isLooping) {<br />                returnValue.set(<span style="color:#0033B3;">new </span>Info(imageWidth, imageHeight, numberOfFrames, durationMillis, isLooping));<br />                <span style="color:#0033B3;">return false</span>;<br />            }<br /><br />            @Override<br />            <span style="color:#0033B3;">public boolean </span>consumeFrame(BufferedImage frame, <span style="color:#0033B3;">int </span>startTimeMillis, <span style="color:#0033B3;">int </span>frameDurationMillis, <span style="color:#0033B3;">int </span>frameIndex, <span style="color:#0033B3;">int </span>numberOfFrames, <span style="color:#0033B3;">boolean </span>isDone) {<br />                <span style="color:#0033B3;">return false</span>;<br />            }<br />        });<br />        <span style="color:#0033B3;">return </span>returnValue.get();<br />    }<br /><br />    <span style="color:#8C8C8C;font-style:italic;">/**<br /></span><span style="color:#8C8C8C;font-style:italic;">     * Read the frame durations of a gif. This relies on ImageIO classes.<br /></span><span style="color:#8C8C8C;font-style:italic;">     */<br /></span><span style="color:#8C8C8C;font-style:italic;">    </span><span style="color:#0033B3;">private </span>List<Integer> readFrameDurationMillis(InputStream stream) <span style="color:#0033B3;">throws </span>IOException {<br />        ImageReader reader = ImageIO.getImageReadersByFormatName(<span style="color:#067D17;">"gif"</span>).next();<br />        <span style="color:#0033B3;">try </span>{<br />            reader.setInput(ImageIO.createImageInputStream(stream));<br />            <span style="color:#0033B3;">int </span>frameCount = reader.getNumImages(<span style="color:#0033B3;">true</span>);<br />            List<Integer> frameDurations = <span style="color:#0033B3;">new </span>ArrayList<>(frameCount);<br />            <span style="color:#0033B3;">for </span>(<span style="color:#0033B3;">int </span>frameIndex = <span style="color:#1750EB;">0</span>; frameIndex < frameCount; frameIndex++) {<br />                IIOMetadataNode root = (IIOMetadataNode) reader.getImageMetadata(frameIndex).getAsTree(<span style="color:#067D17;">"javax_imageio_gif_image_1.0"</span>);<br />                IIOMetadataNode gce = (IIOMetadataNode) root.getElementsByTagName(<span style="color:#067D17;">"GraphicControlExtension"</span>).item(<span style="color:#1750EB;">0</span>);<br />                <span style="color:#0033B3;">int </span>delay = Integer.parseInt(gce.getAttribute(<span style="color:#067D17;">"delayTime"</span>));<br />                frameDurations.add( delay * <span style="color:#1750EB;">10 </span>);<br />            }<br />            <span style="color:#0033B3;">return </span>frameDurations;<br />        } <span style="color:#0033B3;">finally </span>{<br />            reader.dispose();<br />        }<br />    }<br />}<br /></pre></div></div></body></html>