<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>