<div dir="auto"><div>Hey Jeremy,</div><div dir="auto"><br></div><div dir="auto">Yeah, that's not even close to the performance that Java is capable of.</div><div dir="auto"><br></div><div dir="auto">If you want to see something that is used in Production using only Java code, look at iCafe. The code is all open source, and I know it can do what you are trying to do because I did it too. It'll give you an idea of what is going wrong here.</div><div dir="auto"><br><div class="gmail_quote gmail_quote_container" dir="auto"><div dir="ltr" class="gmail_attr">On Fri, Feb 14, 2025, 5:04 PM Jeremy Wood <<a href="mailto:mickleness@gmail.com">mickleness@gmail.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">



<div style="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="m_5975792960531701509x3a7cf97c41bc4858b167a4bb585d98cf"><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></div></blockquote></div></div></div>