【问题标题】:Java, how to draw constantly changing graphicsJava,如何绘制不断变化的图形
【发布时间】:2011-04-14 03:38:56
【问题描述】:

以前没有这样做过,所以显然我很烂。在这里,当前鼠标位置周围的 64 像素在窗体上被绘制得更大一些。问题是,它有点“慢”,我不知道从哪里开始修复。

除此之外,我还创建了一个线程,它会在完成后不断调用更新图形和类似文本的一点 fps,以显示绘制的速度有多快。

图片示例:(图片来自 Eclipse 中的字母“a”)

代码示例:

@SuppressWarnings("serial")
public static class AwtZoom extends Frame {
    private BufferedImage image;
    private long timeRef = new Date().getTime();
    Robot robot = null;

    public AwtZoom() {
        super("Image zoom");
        setLocation(new Point(640, 0));
        setSize(400, 400);
        setVisible(true);
        final Ticker t = new Ticker();

        this.image = (BufferedImage) (this.createImage(320, 330));
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent we) {
                t.done();
                dispose();
            }
        });
        try {
            robot = new Robot();
        } catch (AWTException e) {
            e.printStackTrace();
        }
        t.start();
    }

    private class Ticker extends Thread {
        public boolean update = true;
        public void done() {
            update = false;
        }
        public void run() {
            try {

                while (update == true) {
                    update(getGraphics());
                    // try {
                    // Thread.sleep(200);
                    // } catch (InterruptedException e) {
                    // e.printStackTrace();
                    // return;
                    // }
                }
            } catch (Exception e) {

                update=false;
            }
        }
    }

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

    boolean isdone = true;
    public void paint(Graphics g) {
        if (isdone) {
            isdone=false;
            int step = 40;
            Point p = MouseInfo.getPointerInfo().getLocation();

            Graphics2D gc = this.image.createGraphics();

            try {

                for (int x = 0; x < 8; x++) {
                    for (int y = 0; y < 8; y++) {
                        gc.setColor(robot.getPixelColor(p.x - 4 + x, p.y
                                - 4 + y));
                        gc.fillOval(x * step, y * step, step - 3, step - 3);
                        gc.setColor(Color.GRAY);
                        gc.drawOval(x * step, y * step, step - 3, step - 3);
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
            gc.dispose();
            isdone = true;
            iter++;
        }
        g.drawImage(image, 40, 45, this);
        g.setColor(Color.black);
        StringBuilder sb = new StringBuilder();
        sb.append(iter)
                .append(" frames in ")
                .append((double) (new Date().getTime() - this.timeRef) / 1000)
                .append("s.");
        g.drawString(sb.toString(), 50, 375);
    }

    int iter = 0;
}

所做的更改:
* 添加“gc.dispose();”
* 添加了“isdone”,因此不能更快地调用重绘,那么它应该。
* 添加 this link 到 thrashgod 源代码重写
* 添加 this link 到 thrashgod 源代码重写 2

【问题讨论】:

  • 为什么不只在鼠标移动事件发生时重绘?
  • 你为什么在paint期间调用垃圾收集器?
  • 真的很想绘制鼠标附近的东西,并希望它可以在鼠标所在的任何地方工作(尽可能快,即使在视频上)。我假设你发现的问题是,paint 事件也可以不被线程 t 调用。我认为,这不是问题,但无论如何添加“isdone”来解决它。
  • 一些通用指针 (1) 你应该只在 Event Dispatch Thread 上绘画,但你似乎是在 Ticker Thread 上绘画。 (2) 您不应该需要“done”变量,因为单个线程不能同时运行两位代码。 (3) 尝试将其分成一个模型(一个 8*8 的颜色数组)和视图(您要求在模型更新时重新绘制)。 (4) 考虑使用 repaint(x, y, w, h),它指定了一个剪切区域,因此重绘速度更快。
  • 我做了一些分析——robot.getPixelColor(..) 在我的 Mac 上很慢。这是开始寻找改进的好地方

标签: java graphics


【解决方案1】:

这是我的主要重写,有以下值得注意的变化:

  • 我已将检测像素颜色的任务与绘图任务分开
  • 我已将 robot.getPixelColor(...) 替换为 robot.createScreenCapture(...) 以一次获取所有 64 个像素,而不是一次获取一个
  • 我引入了智能剪辑 - 只有需要重绘的才会重绘。
  • 我已经修复了线程,因此模型和视图的所有更新都发生在事件调度线程上

自动收报机不断运行。当它检测到像素颜色的变化(由于鼠标移动到不同的区域或鼠标下方的像素发生变化)时,它会准确检测到发生了什么变化,更新模型,然后请求重新绘制视图。这种方法会立即更新到人眼。 289 次屏幕更新累计耗时 1 秒。

对于一个安静的周六晚上来说,这是一个令人愉快的挑战。

import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;

public class ZoomPanel extends JPanel {

    private static final int STEP = 40;
    private int iter = 0;
    private long cumulativeTimeTaken = 0;


    public static void main(String[] args) {
        final JFrame frame = new JFrame("Image zoom");

        final ZoomPanel zoomPanel = new ZoomPanel();
        frame.getContentPane().add(zoomPanel);
        final Ticker t = new Ticker(zoomPanel);

        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent we) {
                t.done();
                frame.dispose();
            }
        });
        t.start();

        frame.setLocation(new Point(640, 0));
        frame.pack();
        frame.setVisible(true);
    }

    private final Color[][] model = new Color[8][8];

    public ZoomPanel() {
        setSize(new Dimension(400, 400));
        setMinimumSize(new Dimension(400, 400));
        setPreferredSize(new Dimension(400, 400));
        setOpaque(true);
    }

    private void setColorAt(int x, int y, Color pixelColor) {
        model[x][y] = pixelColor;
        repaint(40 + x * STEP, 45 + y * STEP, 40 + (x * STEP) - 3, 45 + (y * STEP) - 3);
    }

    private Color getColorAt(int x, int y) {
        return model[x][y];
    }

    public void paintComponent(Graphics g) {
        long start = System.currentTimeMillis();
        if (!SwingUtilities.isEventDispatchThread()) {
            throw new RuntimeException("Repaint attempt is not on event dispatch thread");
        }
        final Graphics2D g2 = (Graphics2D) g;
        g2.setColor(getBackground());
        try {

            for (int x = 0; x < 8; x++) {
                for (int y = 0; y < 8; y++) {
                    g2.setColor(model[x][y]);
                    Ellipse2D e = new Ellipse2D.Double(40 + x * STEP, 45 + y * STEP, STEP - 3, STEP - 3);
                    g2.fill(e);
                    g2.setColor(Color.GRAY);
                    g2.draw(e);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        iter++;
        g2.setColor(Color.black);
        long stop = System.currentTimeMillis();
        cumulativeTimeTaken += stop - start;
        StringBuilder sb = new StringBuilder();
        sb.append(iter)
                .append(" frames in ")
                .append((double) (cumulativeTimeTaken) / 1000)
                .append("s.");

        System.out.println(sb);
    }

    private static class Ticker extends Thread {

        private final Robot robot;

        public boolean update = true;
        private final ZoomPanel view;

        public Ticker(ZoomPanel zoomPanel) {
            view = zoomPanel;
            try {
                robot = new Robot();
            } catch (AWTException e) {
                throw new RuntimeException(e);
            }
        }

        public void done() {
            update = false;
        }

        public void run() {
            int runCount = 0;
            while (update) {
                runCount++;
                if (runCount % 100 == 0) {
                    System.out.println("Ran ticker " + runCount + " times");
                }
                final Point p = MouseInfo.getPointerInfo().getLocation();

                Rectangle rect = new Rectangle(p.x - 4, p.y - 4, 8, 8);
                final BufferedImage capture = robot.createScreenCapture(rect);

                for (int x = 0; x < 8; x++) {
                    for (int y = 0; y < 8; y++) {
                        final Color pixelColor = new Color(capture.getRGB(x, y));

                        if (!pixelColor.equals(view.getColorAt(x, y))) {
                            final int finalX = x;
                            final int finalY = y;
                            SwingUtilities.invokeLater(new Runnable() {
                                public void run() {
                                    view.setColorAt(finalX, finalY, pixelColor);
                                }
                            });
                        }
                    }
                }

            }
        }

    }

}

【讨论】:

  • +1 我冒昧地使用createScreenCapture() 来加速我的替代代码。
【解决方案2】:

如果你不介意使用 Swing,这个example 展示了如何快速放大从Icon 获得的BufferedImage。在您的情况下,您需要一个 8x8 BufferedImage,用机器人看到的像素填充 mouseMoved()

附录:这是您的示例左上角的快照。

附录:

缩放本身并不重要...

缓慢的部分是从桌面获取像素;缩放是次要的。如果你只是想看看各种动画技术,看看这个example

附录:由于获取单个像素很慢,而@Steve McLeod 建议的createScreenCapture() 方法很快,这就是我的想法。您可以看到它的更新也更加顺畅。请注意,释放鼠标按钮可以看到捕获的颜色。

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;

/** @see https://stackoverflow.com/questions/3742731 */
public class Zoom extends JPanel implements MouseMotionListener {

    private static final int SIZE = 16;
    private static final int S2 = SIZE / 2;
    private static final int SCALE = 48;
    private BufferedImage img;
    private Robot robot;

    public Zoom() {
        super(true);
        this.setPreferredSize(new Dimension(SIZE * SCALE, SIZE * SCALE));
        img = new BufferedImage(SIZE, SIZE, BufferedImage.TYPE_INT_RGB);
        try {
            robot = new Robot();
        } catch (AWTException e) {
            e.printStackTrace(System.err);
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        Point p = e.getPoint();
        int x = p.x * SIZE / getWidth();
        int y = p.y * SIZE / getHeight();
        int c = img.getRGB(x, y);
        this.setToolTipText(x + "," + y + ": "
            + String.format("%08X", c));
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        int x = e.getXOnScreen();
        int y = e.getYOnScreen();
        Rectangle rect = new Rectangle(x - S2, y - S2, SIZE, SIZE);
        img = robot.createScreenCapture(rect);
        repaint();
    }

    private static void create() {
        JFrame f = new JFrame("Click & drag to zoom.");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Zoom zoom = new Zoom();
        f.add(zoom);
        f.pack();
        f.setVisible(true);
        zoom.addMouseMotionListener(zoom);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                create();
            }
        });
    }
}

【讨论】:

  • 我当然不介意。这似乎是一个使用指针信息绘制静止图像的示例,当在表单内绘制时,鼠标在图像中的位置。尝试使用缓冲图像,就像在那个例子中一样,没有明显的区别。后来发现我使用的是缓冲图像,可以跳过这个重写。
  • 您是要放大组件还是桌面?
  • 缩放本身并不重要,只需要快速输入以更改图形。
  • 添加了指向您的代码重写的链接,这样您就可以看到我做了什么,以及我所说的“没有明显差异”是什么意思。
  • @Margus:我没有按照你的想法去做。我添加了一个动画示例的链接。
【解决方案3】:

将此方法添加到 Paint 方法中:

public void clear(Graphics g, Color currentColor) {
    g.setColor(backgroundColor);
    g.fillRect(0, 0, width, height);
    g.setColor(currentColor);
    int delay = 5; //milliseconds

    ActionListener taskPerformer = new ActionListener() {
    public void actionPerformed(ActionEvent evt) { } };

    new Timer(delay, taskPerformer).start();
} //run this right before you draw something

好的,所以使用计时器来减慢延迟,而不是线程,这很糟糕。

【讨论】:

  • paint() 在 Event Dispatching 线程上被调用 .... 添加 Thread.sleep() 不好。
  • 只是为了减慢绘图速度,所以没有闪烁。
  • 然后考虑一个 Swing 计时器:docs.oracle.com/javase/7/docs/api/javax/swing/Timer.html ... 从不 在事件调度线程上休眠 ;-)(对于“从不”的几乎所有含义)
  • 在那里我添加了一个计时器而不是一个新线程。现在,为什么那么糟糕?当我使用它时它会起作用。
  • 参见docs.oracle.com/javase/tutorial/uiswing/concurrency/… - 事件调度线程上的任何延迟都会影响应用程序的响应能力,可能会导致事件积压。
【解决方案4】:

只需使用时间延迟循环。然后您可以通过调整 i 的限制来微调延迟。它还可以让您控制通过一些命中和试验来调整过渡速度。

for(long i=0;i

canvas.repaint();

它对我来说很好用,也不需要使用缓冲图像。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-19
    • 2021-07-28
    • 1970-01-01
    相关资源
    最近更新 更多