【问题标题】:Repainting a dirty region interfere with graphics2D clipbounds重新绘制脏区域会干扰 graphics2D 剪辑边界
【发布时间】:2022-02-04 22:41:41
【问题描述】:

在下面的复制器中,我正在复制我在图形界面中看到的内容。 我想重绘画布的脏区域(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 上方进行编辑

【问题讨论】:

  • 所以,“问题”似乎与g2.create(10, 10, 290, 290)graphicsBox.getClipBounds() 有关。第一个是创建和“翻译和剪辑”上下文,但是,它考虑到了父上下文的原始clip,或者正如文档所说,“它的剪辑区域由原始剪辑的交集决定具有指定矩形的区域” ????.然后,您在这些边界内绘制一个新矩形,但是,由于剪辑,它不会清除旧矩形
  • 现在,如果您将drawRectfill 命令交换为graphicsBox(并调整drawRect 以考虑线宽),您可以在它周围得到一个漂亮的正方形.现在,说了这么多,就我个人而言,我不会以这种方式使用getClipBounds,相反,它“应该”用于确定您是否真的需要采取行动(不要在clipBounds 如果你不需要)
  • 我只是不以这种方式使用 getClipBounds,相反,它“应该”用于确定您是否真的需要采取行动(不要在clipBounds 如果你不需要) 好的,这是一个好建议!

标签: java swing awt graphics2d


【解决方案1】:

所以,有几个“错误”

我找到了这个……

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) 的结果有点令人惊讶

新的 Graphics 对象将其原点转换为指定的点 (x, y)。 其剪辑区域由原始剪辑区域与指定矩形的交点确定

(重点由我添加)

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

因此,我更正了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 的引用只是我在绘制过程中随机排列的Colors 列表,因此我可以“看到”发生了什么

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

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

可运行示例...

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 作为主要画布并更新它,然后简单地绘制图像。

或者,是时候改用BufferStrategy了,这会让你尽可能接近Java/Swing允许的“金属”

【讨论】:

  • 有趣。但是交换drawRect / fill 顺序,仍然会创建一个不需要的边框,如screenshot 所示,因为不应在红色矩形内绘制边框(尤其是在重绘区域内)。现在正如您所提到的,我已经了解到clipBounds 不适合获取图形上下文可以绘制到的封闭区域的困难方式,事实上我解决了这个问题方式,依靠我自己的几何而不是clipbounds。
  • 我还不知道BufferStrategy,谢谢提醒。
  • 交换订单本身并不能解决问题。我所做的是确保fill 填充了整个clipBounds,然后修改了draw 确保框架在clipBounds 的范围内绘制,这就是我“解决”提出的整体问题的地方
  • 对不起,如果我的问题措辞不足。所以这个问题产生了更广泛的影响,直到我取消了对 clipbounds 的使用。如果一个区域有多次重绘,JComponent 正在使用 [RepaintManager.addDirtyRegion](docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/…,因为组件的脏区域是 unioned 正如 javadoc 所说的那样。通常我看到这些边界时鼠标悬停在滚动条上。
  • @Brice 您还需要小心使用“脏区”,您需要确保“以前”占用的区域也已更新 - 经常看到这种混乱
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-10-08
  • 2019-04-22
  • 1970-01-01
  • 2019-01-31
  • 2015-11-22
  • 1970-01-01
  • 2014-06-17
相关资源
最近更新 更多