【问题标题】:How to create an animated Image from still frames?如何从静止帧创建动画图像?
【发布时间】:2015-09-04 20:18:23
【问题描述】:

给定BufferedImage 中的真彩色全帧列表和帧持续时间列表,我怎样才能无损地创建一个图像,当它放在 JLabel 上时会产生动画?

根据find 的能力,我可以创建一个ImageWriter 包装ByteArrayOutputStream,将IIOImage 帧写入其中,然后将Toolkit.getDefaultToolkit().createImage 流写入ToolkitImage

这个尝试有两个问题。

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

[编辑]
一些更简洁的约束和要求。请不要想出任何违反这些规则的东西。

想要什么:

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

想要什么:

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

可选:

  • 框架可以具有透明度。如果需要,我可以预先对图像进行不透明处理,因为无论如何动画都会在已知背景(单色)上显示。
  • 我不在乎是否必须自己继承Image 并在其中添加一个与ImageObserver 合作的动画线程,或者编写我自己的InputStreamImageSource,但我不知道如何。
  • 如果我能以某种方式显示一个 JavaFX 场景,其中包含一些 HTML 和 CSS 代码来为我的图像设置动画,那也很好。但只要它全部封装在一个我可以传递的与 SWING 兼容的对象中。

【问题讨论】:

  • 我不会使用 JLabel。我会使用paintComponent 方法直接在JPanel 上绘制图像。你的图片有多大?
  • 图像大小可以是从图标到显示器分辨率的任何东西(不能更大)。我实际上并不需要 JLabel(我将不得不重写我当前使用 JLabel 的一个应用程序)但主要是,我要求 JLabel 兼容性是一种简单的表示方式:一个包含所有帧的 (Toolkit)Image。跨度>
  • 不同尺寸的图片,动画更加跳跃。您希望每张图片显示多长时间?

标签: java image animated


【解决方案1】:

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

你说你不想创建动画线程,但是 JavaFX Animation 对象呢,比如Timeline

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 应用程序线程中运行。如果您在 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 子类指定为主类来隐式调用。

【讨论】:

  • 我必须解决两个小的主要问题,然后它就起作用了!首先用new JFXPanel();初始化JavaFX线程,并在时间线末尾添加第一张图像和最终时间戳
  • 好的,目前不与 SWING 集成。当我执行frame.addMouseListener(x)frame.setContentPane(thatJFXPanel) 时,我的鼠标事件没有通过。知道是否有可靠的修复(不仅仅是对鼠标事件的破解,而是让它完全和完全像 JComponent 一样工作?(我似乎也不能让它透明)
  • 我对此表示怀疑。我怀疑你可以用 JLayer 解决鼠标事件问题,但由于负责 JFXPanel 的内容的是 JavaFX,而不是 Swing,我怀疑它是否可以支持所有 AWT/Swing 功能。
【解决方案2】:

类似

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

【讨论】:

  • 如果我只想在自己的动画循环中画帧,我就不必在这里问了。
  • 然后我错过了一些我不明白为什么这个解决方案不可接受的东西你只需要检查 System.currentTimeInMilis() 并有一些逻辑来决定你想要加载哪个图像
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-10-20
  • 2012-12-27
  • 1970-01-01
  • 2011-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多