重新绘制脏区域会干扰 graphics2D clipbounds

Repainting a dirty region interfere with graphics2D clipbounds

在下面的复制器中,我正在复制我在图形界面中看到的内容。 我想重绘 canvas(JPanel)的脏区域,因为完全重绘会引入更多延迟。

但是,当调用重绘(脏矩形)函数时,它会导致嵌套的 Graphics2D 对象出现一些问题。特别是

此代码可能会误用剪辑边界,但我没想到会在创建子(嵌套)图形上下文时出现此“工件”。

在这种情况下,处理剪辑、嵌套图形上下文和脏区域(或矩形)的正确方法是什么repaint

我的直觉告诉我代码应该在不使用裁剪区域的情况下计算它的形状。


import javax.swing.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;

public class Reproducer {
    public static void main(String[] args) {

        var comp = new MyJPanel();
        var timer = new Timer(2_000, e -> {
            SwingUtilities.invokeLater(comp::bug);
        });
        timer.start();
        timer.setRepeats(true);

        SwingUtilities.invokeLater(() -> {
            var frame = new JFrame("Reproducer");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(new Dimension(300, 300));
            frame.setVisible(true);
            frame.getContentPane().add(comp);
        });
    }

    private static class MyJPanel extends JPanel {
        public MyJPanel() {
            setSize(600, 600);
            setPreferredSize(getSize());
        }

        @Override
        protected void paintComponent(Graphics g) {
            var g2 = (Graphics2D) g;
            super.paintComponent(g2);
            g2.setColor(Color.cyan);
            g2.fill(g2.getClip());

            var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
            var clipBounds = graphicsBox.getClipBounds();
            graphicsBox.setColor(Color.LIGHT_GRAY);
            graphicsBox.setStroke(new BasicStroke(2));
            graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
            graphicsBox.setColor(Color.red);
            graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
                                                    clipBounds.y + 2,
                                                    clipBounds.width - 2,
                                                    clipBounds.height - 2));
            graphicsBox.dispose();


            var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
            graphicsOverlay.setColor(Color.YELLOW);
            graphicsOverlay.fillOval(10, 10, 60, 60);
            graphicsOverlay.dispose();
        }

        public void bug() {
            getVisibleRect();
            repaint(100, 100, 100, 100);
        }
    }
}

通过将 fill 移动到 drawRect

上方进行编辑

所以,有几个“错误”

我发现了这个...

graphicsBox.drawRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x + 2,
        clipBounds.y + 2,
        clipBounds.width - 2,
        clipBounds.height - 2));

有点错误,这意味着当应用剪辑时,背景区域没有正确填充。

调用 super.paintComponent 用组件的背景色填充剪辑区域,但 graphicsBox.fill 没有填充整个区域,允许看到部分背景色。

我也发现Graphics2D#create(int, int, int, int)的结果有点意外

The new Graphics object has its origin translated to the specified point (x, y). Its clip area is determined by the intersection of the original clip area with the specified rectangle.

(重点是我加的)

老实说,我不应该感到惊讶,但我以前从未使用过这些功能,所以我有点吃惊。

因此,我更正了 graphicsBox 渲染代码,以便 fill 将首先填充整个裁剪区域,然后绘制矩形(在边界矩形内)

var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
var clipBounds = graphicsBox.getClipBounds();

graphicsBox.setColor(Color.red);
graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
        clipBounds.y,
        clipBounds.width,
        clipBounds.height));

Collections.shuffle(colors);
graphicsBox.setColor(colors.get(0));
graphicsBox.setStroke(new BasicStroke(2));
graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);

graphicsBox.dispose();

nb:对 colors 的引用只是 Color 的列表,我在绘制过程中将其随机化,因此我可以“看到”发生了什么

另一个技巧可能是更改组件的背景颜色,以便更容易“看到”错误更新的地方。

当你遇到这样的问题时,减少混乱的数量非常重要,看看你是否可以缩小问题的范围,我拿出大部分绘画代码来找出所有这些“小”东西结合给你问题。

可运行示例...

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class Main {

    public static void main(String[] args) {

        var comp = new MyJPanel();
        var timer = new Timer(2_000, e -> {
            SwingUtilities.invokeLater(comp::bug);
        });
        timer.start();
        timer.setRepeats(true);

        SwingUtilities.invokeLater(() -> {
            var frame = new JFrame("Reproducer");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setSize(new Dimension(300, 300));
            frame.setVisible(true);
            frame.getContentPane().add(comp);
        });
    }

    private static class MyJPanel extends JPanel {
        
        private List<Color> colors;
        
        public MyJPanel() {
            setSize(600, 600);
            setPreferredSize(getSize());
            colors = Arrays.asList(new Color[] {
               Color.BLACK,
                Color.BLUE,
                Color.CYAN,
                Color.DARK_GRAY,
                Color.GRAY,
                Color.GREEN,
                Color.LIGHT_GRAY,
                Color.MAGENTA,
                Color.ORANGE,
                Color.PINK,
                Color.RED,
                Color.WHITE,
                Color.YELLOW
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            var g2 = (Graphics2D) g;
            super.paintComponent(g2);
            g2.setColor(Color.cyan);
            g2.fill(g2.getClip());

            var graphicsBox = (Graphics2D) g2.create(10, 10, 290, 290);
            var clipBounds = graphicsBox.getClipBounds();

            graphicsBox.setColor(Color.red);
            graphicsBox.fill(new Rectangle2D.Double(clipBounds.x,
                    clipBounds.y,
                    clipBounds.width,
                    clipBounds.height));
            
            Collections.shuffle(colors);
            graphicsBox.setColor(colors.get(0));
            graphicsBox.setStroke(new BasicStroke(2));
            graphicsBox.drawRect(clipBounds.x + 1, clipBounds.y + 1, clipBounds.width - 2, clipBounds.height - 2);

            graphicsBox.dispose();

            var graphicsOverlay = (Graphics2D) g2.create(100, 100, 100, 100);
            graphicsOverlay.setColor(Color.YELLOW);
            graphicsOverlay.fillOval(10, 10, 60, 60);
            graphicsOverlay.dispose();
        }

        public void bug() {
            getVisibleRect();
            repaint(100, 100, 100, 100);
        }
    }
}

如果您遇到“性能”问题,您可以考虑使用 BufferedImage 作为主要 canvas 并更新它,然后简单地绘制图像。

或者,可能是时候转向使用 BufferStrategy,这将使您尽可能接近“金属”,因为 Java/Swing 允许