如何从静止帧创建动画图像?

时间:2015-09-04 20:18:24

标签: java image animated

鉴于BufferedImage中的真彩色全帧列表和帧持续时间列表,我如何无损地创建图像,当放在JLabel上时,会动画?

find开始,我可以创建ImageWriter包裹ByteArrayOutputStream,向其中写IIOImage帧,然后Toolkit.getDefaultToolkit().createImage将流转换为ToolkitImage

此尝试存在两个问题。

  • ImageWriter只能使用其中一个已知的图像编码器进行实例化,而无损无误的真彩色动画图像格式(例如MNG),
  • 它对图像进行编码(压缩),然后再次对其进行解压缩,从而成为不必要的性能危险。

[编辑]
一些更简洁的约束和要求。请不要提出任何使这些规则发生变化的事情。

想要的东西:

  • 制作动画线程并自己绘制/更新动画的每一帧,
  • 使用任何类型的第三方库,
  • 借用任何外部流程,例如网络浏览器
  • 在某种视频播放器对象或3D加速场景(OpenGL /等)中显示,
  • 直接使用sun.*
  • 中的类

想要的事情:

  • 帧大小可以与监视器大小一样大。请不要担心性能。我会担心的。你只会担心正确性。
  • 框架都具有相同的尺寸,
  • 一个Image子类。我应该能够像g.drawImage(ani, 0, 0, this)那样绘制图像,它会动画,或者将它包装在ImageIcon中并将其显示在JLabel / JButton / etc上,它会动画,
  • 每个帧都可以有不同的延迟,从10毫秒到秒,
  • 动画可循环或可以结束,每个动画定义一次(就像GIF一样),
  • 我可以使用与Oracle Java 8打包的任何东西(例如JavaFX),
  • 无论发生什么,它都应该与SWING集成

可选:

  • 框架可以具有透明度。如果需要,我可以事先验证我的图像,因为动画将以已知的背景(单色)显示。
  • 我不在乎我是否必须自己继承Image并在那里添加一个与ImageObserver合作的动画线程,或者写我自己的InputStreamImageSource,但我不知道不知道怎么做。
  • 如果我能以某种方式显示一个带有动画我的图像的HTML和CSS代码的JavaFX场景,那也没关系。但只要它全部封装在一个可以传递的SWING兼容对象中。

2 个答案:

答案 0 :(得分:1)

你是对的,ImageIO不是一个选项,因为保证支持的唯一动画格式是GIF。

你说你不想制作动画线程,但是像Timeline那样的JavaFX动画对象呢?

public JComponent createAnimationComponent(List<BufferedImage> images,
                                           List<Long> durations) {

    Objects.requireNonNull(images, "Image list cannot be null");
    Objects.requireNonNull(durations, "Duration list cannot be null");

    if (new ArrayList<Object>(images).contains(null)) {
        throw new IllegalArgumentException("Null image not permitted");
    }
    if (new ArrayList<Object>(durations).contains(null)) {
        throw new IllegalArgumentException("Null duration not permitted");
    }

    int count = images.size();
    if (count != durations.size()) {
        throw new IllegalArgumentException(
            "Lists must have the same number of elements");
    }

    ImageView view = new ImageView();
    ObjectProperty<Image> imageProperty = view.imageProperty();

    Rectangle imageSize = new Rectangle();
    KeyFrame[] frames = new KeyFrame[count];
    long time = 0;
    for (int i = 0; i < count; i++) {
        Duration duration = Duration.millis(time);
        time += durations.get(i);

        BufferedImage bufImg = images.get(i);
        imageSize.add(bufImg.getWidth(), bufImg.getHeight());

        Image image = SwingFXUtils.toFXImage(bufImg, null);
        KeyValue imageValue = new KeyValue(imageProperty, image,
            Interpolator.DISCRETE);
        frames[i] = new KeyFrame(duration, imageValue);
    }

    Timeline timeline = new Timeline(frames);
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();

    JFXPanel panel = new JFXPanel();
    panel.setScene(new Scene(new Group(view)));
    panel.setPreferredSize(imageSize.getSize());

    return panel;
}

(我不知道为什么必须明确设置JFXPanel的首选大小,但确实如此。可能是一个错误。)

请注意,与所有JavaFX代码一样,它必须在JavaFX Application Thread中运行。如果您从Swing应用程序中使用它,您可以执行以下操作:

public JComponent createAnimationComponentFromAWTThread(
                                final List<BufferedImage> images,
                                final List<Long> durations)
throws InterruptedException {

    final JComponent[] componentHolder = { null };

    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            synchronized (componentHolder) {
                componentHolder[0] =
                    createAnimationComponent(images, durations);
                componentHolder.notifyAll();
            }
        }
    });

    synchronized (componentHolder) {
        while (componentHolder[0] == null) {
            componentHolder.wait();
        }
        return componentHolder[0];
    }
}

但这还不够。首先必须通过使用显式方法调用调用Application.launch来初始化JavaFX,或者通过将Application子类指定为主类来隐式地初始化JavaFX。

答案 1 :(得分:0)

类似

public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.drawImage(getImageForCurrentTime(), 3, 4, this);
}