0

I have a gif Image being displayed on a JPanel in an endless Loop. Now I need to stop the animation after a random amount of Frames. In fact, I generate a random number that can be 0 or 1. Say the gif consists of 6 Frames. If the number is 0 I want to stop at the 3rd Frame, if it is 1 the animation should freeze at the 6th Frame.

To realize this I tried to use a Swing Timer which fires Events exactly when the next Frame comes. So if the Frames have a delay of 50 ms, I construct the Timer like

new Timer(50, this);

Sadly, this doesn't seem to work, in fact the Animation seems to be slower than the Timer. (I assume this has something to do with loading Times.) Anyway, i added some Code illustrating the Problem and (faily) Solution approach.

import java.awt.event.*;
import javax.swing.*;

public class GifTest extends JPanel implements ActionListener{

ImageIcon gif = new ImageIcon(GifTest.class.getResource("testgif.gif"));
JLabel label = new JLabel(gif);
Timer timer = new Timer(50, this);
int ctr;

public GifTest() {
    add(label);
    timer.setInitialDelay(0);
    timer.start();
}

@Override
public void actionPerformed(ActionEvent e) {
    ctr++;
    if (ctr == 13){
        timer.stop();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException i) {
        }
    }
}

public static void main(String[] args) {
    JFrame frame = new JFrame("Gif Test");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.add(new GifTest());
    frame.setSize(150,150);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
}
}

For the giftest.gif, it is a simple 6 Layers with the Numbers 1 to 6 on them, saved with a delay of 50ms.

I would be grateful for any help.

Ps: If it turns out that there is no elegant way to do this, it would also suffice to retrieve the Frame currently displayed. That way I could ask for it and stop when it's the 3rd (resp. 6th) Frame. Due to the task's context i would prefer a modified version of my solution though.

Kenji
  • 61
  • 6
  • 2
    Worst case, unpack the images from the GIF and display each image with the Swing Timer. – Gilbert Le Blanc Dec 07 '15 at 17:30
  • Why do you Thread.sleep(1000) ? – Leet-Falcon Dec 07 '15 at 18:47
  • Just to prevent the Animation from continuing shortly, so i can see which is the 13th Frame. – Kenji Dec 07 '15 at 19:04
  • *"To realize this I tried to use a Swing Timer which fires Events exactly when the next Frame comes"* - That's not true, Swing `Timer` only guarantees that it will be called after at least the specified time – MadProgrammer Dec 07 '15 at 19:48
  • Thanks MadProgrammer. I now called System.currentTimeMillis() every time the actionPerformed method is executed and indeed with a Timer delay of 50 ms the actual method call can vary from 20 up to 130 ms after the last call (100 observations). I therefore assume that it cant be done this way. Is there a way to retrieve the currently displayed Frame of the gif image then? – Kenji Dec 07 '15 at 20:15
  • Not without going to the expense of writing your own decoder and animator - [for example](http://stackoverflow.com/questions/22188940/gif-image-doesnt-moves-on-adding-it-to-the-jtabbed-pane/22190844#22190844). I generally think a better solution is to extract each frame to separate image and manually display them using a `Timer`, this way you have absolute control – MadProgrammer Dec 07 '15 at 21:53

1 Answers1

0

You can unpack as mentioned above and store in an array of images (simple and straightforward).
You could also use a more advanced option using ImageObserver Interface:
ImageObserver provides monitoring for the loading process via a special API called:
imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
You can track progress with this API as follows:

ImageIcon gif = new ImageIcon();
JLabel label = new JLabel(gif);
ImageObserver myObserver = new ImageObserver() {
      public boolean imageUpdate(Image image, int flags, int x, int y, int width, int height) {
        if ((flags & HEIGHT) != 0)
          System.out.println("Image height = " + height);
        if ((flags & WIDTH) != 0)
          System.out.println("Image width = " + width);
        if ((flags & FRAMEBITS) != 0)
          System.out.println("Another frame finished.");
        if ((flags & SOMEBITS) != 0)
          System.out.println("Image section :" + new Rectangle(x, y, width, height));
        if ((flags & ALLBITS) != 0)
          System.out.println("Image finished!");
        if ((flags & ABORT) != 0)
          System.out.println("Image load aborted...");
        label.repaint();
        return true;
      }
    };
gif.setImageObserver( myObserver );
gif.setImage(GifTest.class.getResource("testgif.gif"));

You can stop the loading process using return false;

UPDATE: (using ImageReader)
ImageObserver is not so intuitive to work with.
It will update each time a repaint is required and a full animation sequence will be triggered.
Although you can stop it as some point, it will execute from the first image each time.

Another solution is to use ImageReader:
ImageReader can unpack a GIF to a sequence of BufferedImages.
You can then control the entire sequence with a Timer as you wish.

    String gifFilename = "testgif.gif";
    URL url = getClass().getResource(gifFilename);
    ImageInputStream iis = new FileImageInputStream(new File(url.toURI()));
    ImageReader reader = ImageIO.getImageReadersByFormatName("GIF").next();
    // (reader is actually a GIFImageReader plugin)
    reader.setInput(iis);
    int total = reader.getNumImages(true);
    System.out.println("Total images: "+total);
    BufferedImage[] imgs = new BufferedImage[total];

    for (int i = 0; i < total; i++) {
        imgs[i] = reader.read(i);
        Icon icon = new ImageIcon(imgs[i]);
        // JLabel l = new JLabel(icon));
    }
Leet-Falcon
  • 2,107
  • 2
  • 15
  • 23
  • Thank you @Leet-Falcon. As far as i understood the imageUpdate method should be called whenever the next Frame is being displayed?! That doesnt really work for me. Printing a counter incremented within the method and compare with the actual gif displayed on my JPanel shows that those two are still asynchronous. – Kenji Dec 07 '15 at 20:48