синхронизировать таймер Swing с изображением в формате gif

У меня есть изображение в формате GIF, отображаемое на панели JPanel в бесконечном цикле. Теперь мне нужно остановить анимацию после случайного количества кадров. Фактически, я генерирую случайное число, которое может быть 0 или 1. Скажем, гифка состоит из 6 кадров. Если число равно 0, я хочу остановиться на 3-м кадре, если оно равно 1, анимация должна остановиться на 6-м кадре.

Чтобы понять это, я попытался использовать таймер Swing Timer, который запускает события именно тогда, когда приходит следующий кадр. Поэтому, если кадры имеют задержку 50 мс, я создаю таймер как

new Timer(50, this);

К сожалению, похоже, что это не работает, на самом деле анимация кажется медленнее, чем таймер. (Я предполагаю, что это как-то связано с временем загрузки.) В любом случае, я добавил код, иллюстрирующий подход к проблеме и (еже) решению.

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);
}
}

Для giftest.gif это простые 6 слоев с номерами от 1 до 6, сохраненные с задержкой 50 мс.

Буду благодарен за любую помощь.

Ps: Если окажется, что нет элегантного способа сделать это, также будет достаточно получить отображаемый в данный момент фрейм. Таким образом, я мог бы попросить об этом и остановиться, когда наступит 3-й (соответственно 6-й) фрейм. Однако из-за контекста задачи я бы предпочел модифицированную версию своего решения.


person Kenji    schedule 07.12.2015    source источник
comment
В худшем случае распакуйте изображения из GIF и отобразите каждое изображение с помощью таймера Swing.   -  person Gilbert Le Blanc    schedule 07.12.2015
comment
Почему у вас Thread.sleep (1000)?   -  person Leet-Falcon    schedule 07.12.2015
comment
Просто чтобы не допустить продолжения анимации в ближайшее время, чтобы я мог видеть 13-й кадр.   -  person Kenji    schedule 07.12.2015
comment
Чтобы понять это, я попытался использовать таймер Swing, который запускает события именно тогда, когда наступает следующий кадр - это неправда, Swing Timer только гарантирует, что он будет вызван по крайней мере по истечении указанного времени   -  person MadProgrammer    schedule 07.12.2015
comment
Спасибо, MadProgrammer. Теперь я вызываю System.currentTimeMillis () каждый раз, когда выполняется метод actionPerformed, и действительно, с задержкой таймера 50 мс фактический вызов метода может варьироваться от 20 до 130 мс после последнего вызова (100 наблюдений). Поэтому я предполагаю, что это невозможно сделать таким образом. Есть ли тогда способ получить текущий отображаемый кадр изображения в формате gif?   -  person Kenji    schedule 07.12.2015
comment
Не обошлось и без затрат на написание собственного декодера и аниматора - например. Я обычно думаю, что лучшим решением будет извлекать каждый кадр для разделения изображения и вручную отображать их с помощью Timer, таким образом у вас есть абсолютный контроль   -  person MadProgrammer    schedule 08.12.2015


Ответы (1)


Вы можете распаковать, как указано выше, и сохранить в виде массива изображений (простых и понятных).
Вы также можете использовать более продвинутый вариант, используя ImageObserver Interface:
ImageObserver обеспечивает мониторинг процесса загрузки через специальный API, который называется:
imageUpdate (Image img, int infoflags, int x, int y, int width, int height)
Вы можете отслеживать прогресс с помощью этого API следующее:

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"));

Вы можете остановить процесс загрузки с помощью return false;

ОБНОВЛЕНИЕ: (с использованием ImageReader)
ImageObserver не настолько интуитивно понятен для работы.
Он будет обновляться каждый раз, когда потребуется перерисовка, и будет запускаться полная последовательность анимации.
Хотя вы можете остановить его в какой-то момент, он будет выполняться каждый раз с первого изображения.

Другое решение - использовать ImageReader:
ImageReader можно распаковать GIF в последовательность из BufferedImages.
Затем вы можете управлять всей последовательностью с помощью таймера по своему желанию.

    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));
    }
person Leet-Falcon    schedule 07.12.2015
comment
Спасибо @ Leet-Falcon. Насколько я понял, метод imageUpdate должен вызываться всякий раз, когда отображается следующий кадр ?! На самом деле это не работает для меня. Печать счетчика, увеличенного в методе, и сравнение с фактическим gif, отображаемым на моем JPanel, показывает, что эти два все еще асинхронны. - person Kenji; 07.12.2015