【问题标题】:How to tell when Graphics.drawImage() has actually completed如何判断 Graphics.drawImage() 何时实际完成
【发布时间】:2013-01-10 01:31:09
【问题描述】:

这是我第一次在这里提出问题,希望能得到对我有帮助的答案或想法。

我正在绘制一个大图像并使用 drawImage() 将其缩小。然后,我立即使用 drawImage() 绘制另一个图像,我希望将其绘制在前一个图像之上(第二个)。问题是 drawImage 会立即返回,即使缩放和渲染第一张图像需要大约 50 毫秒。大多数情况下,第二张图片最终位于第一张图片下方,因为它是在处理第一张大图片时首先绘制的。基本上无论如何都要强制 drawImage() 阻塞直到它完成,或者以某种方式检查它何时完成?

我知道 ImageObserver 参数,它在从 Internet 或其他地方下载图像时可以正常工作,但是当使用已经加载的 BufferedImage 时,它​​不会在缩放和绘图之后触发 ImageUpdate()。基本上,由于第一个图像已经“加载”,它永远不会联系 ImageObserver,它只需要大约 50 毫秒的时间在它自己的线程中处理,从不通知它何时完成。

有谁知道如何强制它阻止或等到它完全完成缩放和 blitting 图像?显然,使用 Thread.sleep(xx) 是一种完全的黑客行为,由于计算机速度之间的差异,它是不可行的。所有这些渲染都发生在 paint(Graphics g) 方法内的 Event 线程上。

谢谢!

编辑:以下是我目前必须让您了解该问题的代码:

public void paint(Graphics window)
{
    window.setColor(Color.WHITE);
    window.fillRect(0, 0, Settings.width * Settings.aaFactor, Settings.height * Settings.aaFactor);

    Graphics2D window2D = (Graphics2D) window;
    window2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
    window2D.drawImage(this.image, 0, 0, Settings.width, Settings.height, null);

    try
    {
        Thread.sleep(50);
    }
    catch (InterruptedException e)
    {
        e.printStackTrace();
    }

    window2D.drawImage(this.image2, 0, 0, null);

    repaint();
}

编辑 2:为了更好地解释我正在谈论的问题,我制作了一些示例代码来更好地解释我想要解释的内容。运行它,你会看到它在有时第一个图像在底部的地方闪烁(就像它应该是的那样),但大多数时候它会是一个顶部(第二个),这是错误的。只需将文件路径更改为小图像和大图像即可。

public class Main extends Applet implements ImageObserver
{
    private BufferedImage imageA;
    private BufferedImage imageB;

    @Override
    public void init()
    {
        try
        {
            this.imageA = ImageIO.read(new File("C:\\LargeImage.jpg"));
            this.imageB = ImageIO.read(new File("C:\\SmallImage.jpg"));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void update(Graphics g)
    {
        paint(g);
    }

    @Override
    public void paint(Graphics g)
    {
        Graphics2D w = (Graphics2D) g;
        w.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
        w.drawImage(this.imageA, 0, 0, 50, 50, this);// This takes a while to do (scaling down and drawing)...
        w.drawImage(this.imageB, 10, 10, null);// While this is drawn quickly, before A is done.

        repaint();
    }

    @Override
    public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
    {
        System.out.println("ImageObserver fired, done drawing image.  NEVER CALLED!");
        return false;
    }
}

【问题讨论】:

  • 我无法确定 Java 何时会真正呈现 Graphics 对象的输出。您可以确定的一件事是它不会在paint方法返回之后发生,因此 Thread.sleep 除了减慢您的程序速度之外什么也不做。如果您正在通过某种组件进行绘画,请将其作为 ImageObserver 传递给 drawInage 方法,并在准备好时让它自行更新。另一种选择是在后台线程中缩放图像
  • public class Main extends Applet.. 这在这里不起作用,因为这个小程序使用了不在本地磁盘上的图像。 Applet 也比应用程序更难运行。我建议你发布一个基于 SSCCE 的框架,它可以热链接到图像或在代码中生成它们。

标签: java image awt java-2d


【解决方案1】:

drawImage(传递 null)的最后一个参数是 ImageObserver。如果您提供该接口的自己的实现 (see JavaDoc),您将在实际绘制图像时收到通知。

【讨论】:

  • 我已经这样做了,但这不起作用。正如我在第二段中尝试解释的那样,我已经实现了 ImageObserver 和方法 imageUpdate() 并且我拥有它 System.out.println("Fired");如果它被调用,但它从未被调用。我将最终的 drawImage() 参数从 null 更改为 this。
  • 但是如果图像已经预计算并准备好绘制,drawImage返回true(并且不会通知ImageObserver)。你检查了吗?并且 drawImage 真的在没有实际绘制图像的情况下立即返回 true 吗?
  • 正是问题所在。它确实返回 true(因为它在读入和转换时已“加载”),但它仍然会立即返回,即使它需要大约 100 毫秒来缩小和 blit 它。到那时,第二个小图像已经被不整齐(不正确地)blitted。 imageUpdate() 永远不会被调用。
  • 在那里,我添加了第二个示例,您可以复制/粘贴,它将演示问题。而 drawImage() 确实 always 立即返回。
  • 在你的第二个例子中,你在你的paint方法结束时调用repaint(),触发一个立即的repaint,因此一个无限的repaint循环。闪烁使我无法看到首先绘制的图像。如果我删除重绘调用,则首先缩放和绘制第一个图像,然后在其顶部绘制小图像(如预期的那样)。第一次调用 drawImage 大约需要 130 毫秒,第二次 0.06 毫秒。在 Windows 上使用 Oracle JDK 1.7.0_07 测试。
【解决方案2】:

不可能知道 Swing 何时将 Graphics 对象的内容真正呈现到屏幕上。所知道的是,直到 paint 方法返回之后才会发生这种情况(因为 Graphics 对象在它完成之前还没有完成渲染)。

你应该做的是让你正在绘画的Component决定什么时候需要更新,这就是它的设计方式......(Component实现ImageObserver

以下示例在调整框架大小时不断重新缩放主背景图像

public class TestPaint03 {

    public static void main(String[] args) {
        new TestPaint03();
    }

    public TestPaint03() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (Exception ex) {
                }

                JFrame frame = new JFrame("Test");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(new PaintPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class PaintPane extends JPanel {

        private BufferedImage background;
        private BufferedImage foreground;
        private Image scaled;

        public PaintPane() {

            try {
                background = ImageIO.read(new File("/path/to/background/image));
                foreground = ImageIO.read(new File("path/to/foreground/image"));
            } catch (Exception e) {
            }

        }

        @Override
        public void invalidate() {
            scaled = null;
            super.invalidate();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            if (background != null) {

                if (scaled == null) {
                    int size = Math.min(getWidth(), getHeight());
                    scaled = background.getScaledInstance(-1, size, Image.SCALE_SMOOTH);
                }

                int x = (getWidth() - scaled.getWidth(this)) / 2;
                int y = (getHeight() - scaled.getHeight(this)) / 2;
                g.drawImage(scaled, x, y, this);

                x = (getWidth() - foreground.getWidth()) / 2;
                y = (getHeight() - foreground.getHeight()) / 2;
                g.drawImage(foreground, x, y, this);

            }
        }
    }
}

虽然我确定你有你的理由,但我个人会避免使用 Applet 而支持JApplet,事实上,我个人会避免使用小程序或一起使用。我开始了我的职业生涯,编写小程序,它们简直就是一种痛苦,尤其是当你试图做的只是测试一个想法时。

【讨论】:

    猜你喜欢
    • 2021-12-04
    • 1970-01-01
    • 1970-01-01
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    • 2018-03-01
    • 2012-12-04
    • 1970-01-01
    相关资源
    最近更新 更多