这是使用Java 2D图形API的正确方法是什么?这是、图形、正确、方法

2023-09-07 12:21:31 作者:泡芙

我要创建一个图形前端的JBox2D模拟。模拟运行递增,而在更新之间,模拟的内容都应该被绘制。类似游戏除了没有输入。

I'm creating a graphical front-end for a JBox2D simulation. The simulation runs incrementally, and in between the updates, the contents of the simulation are supposed to be drawn. Similar to a game except without input.

我只需要几何图元绘制JBox2D模拟。这个API似乎是最简单的选择,但它的设计是一个有点混乱。

I only need geometric primitives to draw a JBox2D simulation. This API seemed like the simplest choice, but its design is a bit confusing.

目前我还叫了一个类窗口延长的JFrame ,包含作为成员另一个类名为渲染。该窗口类只初始化本身提供了一个 updateDisplay()方法(也就是所谓的主循环),即电话 updateDisplay(对象)方法上的渲染。我做了这两种方法,我和他们唯一的目的就是叫重绘()渲染

Currently I have one class called Window extending JFrame, that contains as a member another class called Renderer. The Window class only initializes itself and provides an updateDisplay() method (that is called by the main loop), that calls updateDisplay(objects) method on the Renderer. I made these two methods myself and their only purpose is to call repaint() on the Renderer.

的JP​​anel 应该被使用的方式?或者我应该使用动画一些更复杂的方法(例如涉及一些后台线程事件和/或时间间隔)?

Is the JPanel supposed to be used that way? Or am I supposed to use some more sophisticated method for animation (such that involves events and/or time intervals in some back-end thread)?

推荐答案

如果你想要安排更新在设定的时间间隔的 javax.swing.Timer中提供了一个Swing的综合服务吧。定时器运行其上的EDT任务。最终做任何画种在Swing你还是会做两件事情,但:

If you are wanting to schedule the updates at a set interval, javax.swing.Timer provides a Swing-integrated service for it. Timer runs its task on the EDT. Ultimately doing any kind of painting in Swing you'll still be doing two things though:

重写的paintComponent 做你的绘图。 电话重绘要求在需要时您的绘图完成。 Overriding paintComponent to do your drawing. Calling repaint to request that your drawing be done as needed.

如果你正在做这两件事情你可能做的是正确的。摇摆并没有真正有动画较高层次的API。它在考虑GUI组件绘制主要目的。它当然可以做一些好的东西,但你将有大部分是从头喜欢你正在做的(可能延长的JPanel喜欢你正在做的)写一个组成部分。

If you're doing those two things you're probably doing it right. Swing doesn't really have a high level API for animation. It's designed primarily with GUI component drawing in mind. It can certainly do some good stuff but you will have to write a component mostly from scratch like you're doing (probably extending JPanel like you're doing).

画在AWT和Swing 涵盖了一些'幕后场景'的东西,如果你不把它添加书签。

Painting in AWT and Swing covers some of the 'behind the scenes' stuff if you do not have it bookmarked.

您可能在期待的JavaFX。我不知道这种事,个人,但它应该是对动画更为适合。

You might look in to JavaFX. I don't know that much about it personally but it's supposed to be more geared towards animation.

由于有些优化,有一件事是可以做的是要画上一个单独的图像,然后绘制图像到面板的paintComponent。这是特别有用的,如果这幅画长:重绘可调度系统,使这样下去,当它发生在控制之下了。我写了这方面的一个简短演示了另一个答案:

As somewhat of an optimization, one thing that can be done is to paint on a separate image and then paint the image on to the panel in paintComponent. This is especially useful if the painting is long: repaints can be scheduled by the system so this keeps when it happens more under control. I wrote a short demonstration of this for another answer:

import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;

class PaintAnyTime {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new PaintAnyTime();
            }
        });
    }

    final BufferedImage image = (
        new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB)
    );

    final JFrame frame = new JFrame();

    final JPanel panel = new JPanel() {
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);

            g.drawImage(
                image, 0, 0, image.getWidth(), image.getHeight(), null
            );
        }
    };

    final MouseAdapter drawer = new MouseAdapter() {
        Graphics2D g2D;

        @Override
        public void mousePressed(MouseEvent me) {
            g2D = image.createGraphics();
            g2D.setColor(Color.BLACK);
        }

        @Override
        public void mouseDragged(MouseEvent me) {
            g2D.fillRect(me.getX(), me.getY(), 3, 3);
            panel.repaint();
        }

        @Override
        public void mouseReleased(MouseEvent me) {
            g2D.dispose();
            g2D = null;
        }
    };

    PaintAnyTime() {
        panel.setPreferredSize(
            new Dimension(image.getWidth(), image.getHeight())
        );

        panel.addMouseListener(drawer);
        panel.addMouseMotionListener(drawer);

        frame.setContentPane(panel);

        frame.pack();
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}

如果该例程是长期运行并重新绘制可能同时发生,双缓冲也可使用。绘制完成对后台缓冲区,然后将缓冲区交换这样的更新是无缝的。这里有一个稍微长一些的例子,显示了长时间运行的任务和缓冲交换:

If the routine is long-running and repaints could happen concurrently, double buffering can also be used. Drawing is done on a background buffer and then the buffers are swapped so the update is seamless. Here is a somewhat longer example that shows a "long-running" task and a buffer swap:

import java.awt.*;
import javax.swing.*;
import java.awt.image.*;
import java.awt.event.*;
import java.util.concurrent.atomic.*;

class DoubleBuffer {
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new DoubleBuffer();
            }
        });
    }

    static final int WIDTH = 640, HEIGHT = 480;
    static final double COS_PI_D_4 = Math.cos(Math.PI / 4.0);

    static BufferedImage newCompatibleImage() {
        return GraphicsEnvironment
        .getLocalGraphicsEnvironment()
        .getDefaultScreenDevice()
        .getDefaultConfiguration()
        .createCompatibleImage(
            WIDTH, HEIGHT, Transparency.TRANSLUCENT
        );
    }

    BufferedImage front = newCompatibleImage();
    BufferedImage back = newCompatibleImage();

    final JFrame frame = new JFrame();

    final JPanel panel = new JPanel() {
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(front, 0, 0, null);
        }
    };

    final MouseAdapter onClick = new MouseAdapter() {
        @Override
        public void mousePressed(MouseEvent me) {
            beginPaint(me.getPoint());
        }
    };

    final Object monitor = new Object();
    final AtomicBoolean painting = new AtomicBoolean();

    DoubleBuffer() {
        panel.setPreferredSize(new Dimension(WIDTH, HEIGHT));
        panel.setBackground(Color.WHITE);
        panel.addMouseListener(onClick);

        frame.setContentPane(panel);

        frame.pack();
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    void beginPaint(Point pt) {
        if(painting.compareAndSet(false, true)) {
            new PaintTask(pt).execute();
        }
    }

    void swapBuffers() {
        BufferedImage swap = front;
        front = back;
        back = swap;
    }

    class PaintTask
    extends SwingWorker<Void, Void> {
        final Point pt;

        PaintTask(Point pt) {
            this.pt = pt;
        }

        @Override
        public Void doInBackground() {
            synchronized(monitor) {
                Graphics2D g2D = back.createGraphics();
                g2D.setRenderingHint(
                    RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON
                );
                g2D.setRenderingHint(
                    RenderingHints.KEY_STROKE_CONTROL,
                    RenderingHints.VALUE_STROKE_PURE
                );

                g2D.setBackground(new Color(0, true));
                g2D.clearRect(0, 0, WIDTH, HEIGHT);

                float depth = (float)(
                    Math.pow(2.0, 7.0 + (int)(Math.random() * 3.0))
                );

                int h = (int)(Math.random() * depth);

                int r = 1;
                int c;
                do {
                    g2D.setColor(new Color(
                        Color.HSBtoRGB(h / depth, 1f, 1f)
                    ));

                    int x = pt.x - r;
                    int y = pt.y - r;
                    int d = r * 2;

                    g2D.drawOval(x, y, d, d);

                    r++;
                    h++;

                    c = (int)(r * COS_PI_D_4);
                } while(
                       0 <= pt.x - c || pt.x + c < WIDTH
                    || 0 <= pt.y - c || pt.y + c < HEIGHT
                );

                g2D.dispose();
                back.flush();

                return (Void)null;
            }
        }

        @Override
        public void done() {
            synchronized(monitor) {
                swapBuffers();
            }

            painting.set(false);
            panel.repaint();
        }
    }
}

这幅画基本上是垃圾,但不要点击过快:

The painting is basically garbage but don't click too fast:

 
精彩推荐