这是使用 Java 2D Graphics API 的正确方法吗? [英] Is this the correct way of using Java 2D Graphics API?

查看:30
本文介绍了这是使用 Java 2D Graphics API 的正确方法吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为 JBox2D 模拟创建图形前端.模拟以增量方式运行,并且在更新之间,模拟的内容应该被绘制.类似于游戏,只是没有输入.

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

目前我有一个名为 Window 的类扩展了 JFrame,它包含另一个名为 Renderer 的类作为成员.Window 类只初始化自身并提供一个 updateDisplay() 方法(由主循环调用),该方法调用 updateDisplay(objects)Renderer 上的方法.我自己制作了这两个方法,它们的唯一目的是在 Renderer 上调用 repaint().

JPanel 应该这样使用吗?还是我应该使用一些更复杂的动画方法(例如在某些后端线程中涉及事件和/或时间间隔)?

解决方案

如果您想按设定的时间间隔安排更新,

<小时>

如果例程长时间运行并且重绘可能同时发生,也可以使用双缓冲.绘图是对与显示的图像分开的图像进行的.然后,当绘制例程完成时,图像参考被交换,因此更新是无缝的.

例如,您通常应该为游戏使用双缓冲.双缓冲可防止图像以部分状态显示.例如,如果您在游戏循环中使用后台线程(而不是 Timer)并且游戏正在绘制时发生了重绘,则可能会发生这种情况.如果没有双缓冲,这种情况会导致闪烁或撕裂.

Swing 组件默认是双缓冲的,因此如果您的所有绘图都发生在 EDT 上,则您无需自己编写双缓冲逻辑.Swing 已经做到了.

这是一个稍微复杂一些的例子,它显示了一个长时间运行的任务和一个缓冲区交换:

import java.awt.*;导入 javax.swing.*;导入 java.awt.image.*;导入 java.awt.event.*;导入 java.util.*;/*** 左键单击以生成新背景* 绘画任务.*/类双缓冲{公共静态无效主(字符串 [] args){SwingUtilities.invokeLater(new Runnable() {@覆盖公共无效运行(){新的双缓冲();}});}最终 int 宽度 = 640;最终 int 高度 = 480;BufferedImage createCompatibleImage() {图形配置 gc =图形环境.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();//createCompatibleImage 创建一个图像//针对显示设备进行了优化.//参见 http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-返回 gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);}//前面的图像是那个//显示在面板中.BufferedImage front = createCompatibleImage();//后面的图片是得到的那个//画到.BufferedImage back = createCompatibleImage();boolean isPainting = false;最终 JFrame 框架 = new JFrame("双缓冲");最终的 JPanel 面板 = 新的 JPanel() {@覆盖受保护的无效paintComponent(图形g){super.paintComponent(g);//缩放图像以适合面板.尺寸实际尺寸 = getSize();int w = actualSize.width;int h = actualSize.height;g.drawImage(front, 0, 0, w, h, null);}};final MouseAdapter onClick = new MouseAdapter() {@覆盖public void mousePressed(MouseEvent e) {如果 (!isPainting) {isPainting = true;new PaintTask(e.getPoint()).execute();}}};双缓冲(){panel.setPreferredSize(new Dimension(width, height));panel.setBackground(Color.WHITE);panel.addMouseListener(onClick);frame.setContentPane(面板);框架.pack();frame.setLocationRelativeTo(null);frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);frame.setVisible(true);}无效交换(){BufferedImage temp = 前面;前 = 后;返回 = 温度;}类 PaintTask 扩展了 SwingWorker{最终点 pt;PaintTask(Point pt) {this.pt = pt;}@覆盖公共无效 doInBackground() {随机 rand = new Random();同步(DoubleBuffer.this){Graphics2D g2 = back.createGraphics();g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,RenderingHints.VALUE_STROKE_PURE);g2.setBackground(new Color(0, true));g2.clearRect(0, 0, width, height);//(这计算 pow(2, rand.nextInt(3) + 7).)整数深度 = 1 <<( rand.nextInt(3) + 7 );浮动色调 = rand.nextInt(depth);整数半径 = 1;国际 c;//这个循环只是绘制同心圆,//从内部开始并延伸//向外直到它碰到外面//图片.做 {int rgb = Color.HSBtoRGB(色调/深度, 1, 1);g2.setColor(新颜色(rgb));int x = pt.x - 半径;int y = pt.y - 半径;int d = 半径 * 2;g2.drawOval(x, y, d, d);++半径;++色调;c = (int) (radius * Math.cos(Math.PI/4));} 尽管 ((0 <= pt.x - c) ||(pt.x + c <宽度)||(0 <= pt.y - c) ||(pt.y + c <高度));g2.dispose();back.flush();返回(无效)空;}}@覆盖公共无效完成(){//done() 在 EDT 上完成,//所以对于这个小程序,这个//是唯一同步的地方//是必要的.//paintComponent 将看到交换//下次调用时发生.同步(DoubleBuffer.this){交换();}isPainting = false;panel.repaint();}}}

绘画程序只是为了绘制垃圾,需要很长时间:

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.

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

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.

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)?

解决方案

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 periodically, without having an explicit loop. (An explicit loop would block the EDT from processing events, which would freeze the UI. I explained this more in-depth here.)

Ultimately doing any kind of painting in Swing you'll still be doing two things:

  1. Overriding paintComponent to do your drawing.
  2. Calling repaint as-needed to request that your drawing be made visible. (Swing normally only repaints when it's needed, for example when some other program's window passes over top of a Swing component.)

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 drawing GUI components in mind. It can certainly do some good stuff, but you will have to write a component mostly from scratch, like you're doing.

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

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.

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.

If you aren't drawing to an image, then you'd need to build a model with objects, and paint all of them every time inside paintComponent.


Here's an example of drawing to an image:

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

/**
 * Holding left-click draws, and
 * right-clicking cycles the color.
 */
class PaintAnyTime {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new PaintAnyTime();
            }
        });
    }

    Color[]    colors = {Color.red, Color.blue, Color.black};
    int  currentColor = 0;
    BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
    Graphics2D  imgG2 = img.createGraphics();

    JFrame frame = new JFrame("Paint Any Time");
    JPanel panel = new JPanel() {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            // Creating a copy of the Graphics
            // so any reconfiguration we do on
            // it doesn't interfere with what
            // Swing is doing.
            Graphics2D g2 = (Graphics2D) g.create();
            // Drawing the image.
            int w = img.getWidth();
            int h = img.getHeight();
            g2.drawImage(img, 0, 0, w, h, null);
            // Drawing a swatch.
            Color color = colors[currentColor];
            g2.setColor(color);
            g2.fillRect(0, 0, 16, 16);
            g2.setColor(Color.black);
            g2.drawRect(-1, -1, 17, 17);
            // At the end, we dispose the
            // Graphics copy we've created
            g2.dispose();
        }
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(img.getWidth(), img.getHeight());
        }
    };

    MouseAdapter drawer = new MouseAdapter() {
        boolean rButtonDown;
        Point prev;

        @Override
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                prev = e.getPoint();
            }
            if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
                // (This just behaves a little better
                // than using the mouseClicked event.)
                rButtonDown  = true;
                currentColor = (currentColor + 1) % colors.length;
                panel.repaint();
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (prev != null) {
                Point  next = e.getPoint();
                Color color = colors[currentColor];
                // We can safely paint to the
                // image any time we want to.
                imgG2.setColor(color);
                imgG2.drawLine(prev.x, prev.y, next.x, next.y);
                // We just need to repaint the
                // panel to make sure the
                // changes are visible
                // immediately.
                panel.repaint();
                prev = next;
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                prev = null;
            }
            if (SwingUtilities.isRightMouseButton(e)) {
                rButtonDown = false;
            }
        }
    };

    PaintAnyTime() {
        // RenderingHints let you specify
        // options such as antialiasing.
        imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        imgG2.setStroke(new BasicStroke(3));
        //
        panel.setBackground(Color.white);
        panel.addMouseListener(drawer);
        panel.addMouseMotionListener(drawer);
        Cursor cursor =
            Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
        panel.setCursor(cursor);
        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 to an image which is separate from the one being shown. Then, when the drawing routine is done, the image references are swapped so the update is seamless.

You should typically use double buffering for a game, for example. Double buffering prevents the image from being shown in a partial state. This could happen if, for example, you were using a background thread for the game loop (instead of a Timer) and a repaint happened the game was doing the painting. Without double buffering, this kind of situation would result in flickering or tearing.

Swing components are double buffered by default, so if all of your drawing is happening on the EDT you don't need to write double buffering logic yourself. Swing already does it.

Here is a somewhat more complicated example which 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.*;

/**
 * Left-click to spawn a new background
 * painting task.
 */
class DoubleBuffer {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new DoubleBuffer();
            }
        });
    }

    final int  width = 640;
    final int height = 480;

    BufferedImage createCompatibleImage() {
        GraphicsConfiguration gc =
            GraphicsEnvironment
                .getLocalGraphicsEnvironment()
                .getDefaultScreenDevice()
                .getDefaultConfiguration();
        // createCompatibleImage creates an image that is
        // optimized for the display device.
        // See http://docs.oracle.com/javase/8/docs/api/java/awt/GraphicsConfiguration.html#createCompatibleImage-int-int-int-
        return gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
    }

    // The front image is the one which is
    // displayed in the panel.
    BufferedImage front = createCompatibleImage();
    // The back image is the one that gets
    // painted to.
    BufferedImage  back = createCompatibleImage();
    boolean  isPainting = false;

    final JFrame frame = new JFrame("Double Buffer");
    final JPanel panel = new JPanel() {
        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            // Scaling the image to fit the panel.
            Dimension actualSize = getSize();
            int w = actualSize.width;
            int h = actualSize.height;
            g.drawImage(front, 0, 0, w, h, null);
        }
    };

    final MouseAdapter onClick = new MouseAdapter() {
        @Override
        public void mousePressed(MouseEvent e) {
            if (!isPainting) {
                isPainting = true;
                new PaintTask(e.getPoint()).execute();
            }
        }
    };

    DoubleBuffer() {
        panel.setPreferredSize(new Dimension(width, height));
        panel.setBackground(Color.WHITE);
        panel.addMouseListener(onClick);
        frame.setContentPane(panel);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

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

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

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

        @Override
        public Void doInBackground() {
            Random rand = new Random();

            synchronized(DoubleBuffer.this) {
                Graphics2D g2 = back.createGraphics();
                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                    RenderingHints.VALUE_ANTIALIAS_ON);
                g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
                                    RenderingHints.VALUE_STROKE_PURE);
                g2.setBackground(new Color(0, true));
                g2.clearRect(0, 0, width, height);
                // (This computes pow(2, rand.nextInt(3) + 7).)
                int  depth = 1 << ( rand.nextInt(3) + 7 );
                float  hue = rand.nextInt(depth);
                int radius = 1;
                int c;
                // This loop just draws concentric circles,
                // starting from the inside and extending
                // outwards until it hits the outside of
                // the image.
                do {
                    int rgb = Color.HSBtoRGB(hue / depth, 1, 1);
                    g2.setColor(new Color(rgb));

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

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

                    ++radius;
                    ++hue;
                    c = (int) (radius * Math.cos(Math.PI / 4));
                } while (
                       (0 <= pt.x - c) || (pt.x + c < width)
                    || (0 <= pt.y - c) || (pt.y + c < height)
                );

                g2.dispose();
                back.flush();

                return (Void) null;
            }
        }

        @Override
        public void done() {
            // done() is completed on the EDT,
            // so for this small program, this
            // is the only place where synchronization
            // is necessary.
            // paintComponent will see the swap
            // happen the next time it is called.
            synchronized(DoubleBuffer.this) {
                swap();
            }

            isPainting = false;
            panel.repaint();
        }
    }
}

The painting routine is just intended draw garbage which takes a long time:

这篇关于这是使用 Java 2D Graphics API 的正确方法吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆