在 BufferedImage 上绘制一个不透明的圆角矩形

Drawing a rounded rectangle with opacity on a BufferedImage

我一直在尝试为我正在开发的小游戏实现基本的文本气泡。不想太花哨,我从一个基本的圆角矩形开始,它的边框包含一些文本:

然后,我决定文本气泡应在预设时间后淡出。这就是我偶然发现问题的地方:当我尝试在测试 window 中显示气泡时,一切正常,但是当我在游戏中显示它们时,气泡消失时出现失真。我又测试了一些,调试了一下,发现这两种情况的唯一区别是,在测试 window 中,我使用 paintComponent 方法的 Graphics 绘制气泡,而在游戏中,我使用 BufferedImages 模拟图层并使用来自的图形image.createGraphics。然后我可以成功地复制错误:

在这里,你看到左边的泡泡在消退时,它的圆角与消退前相比发生了变化,而右边的泡泡的圆角没有变化。实际上,左边的气泡是在 BufferedImage 上绘制的,然后在面板上绘制,而右边的气泡是直接在面板上绘制的。
我已经隔离了重现问题所需的代码:

public static void main(String[] args) {

    JFrame frame = new JFrame("Test");
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.setLocationRelativeTo(null);
    frame.setSize(400, 400);

    JPanel panel = new JPanel() {

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            BufferedImage image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
            Graphics graphics = image.createGraphics();

            paintExampleBubble(graphics, 50, 50);

            g.drawImage(image, 0, 0, this);

            paintExampleBubble(g, 250, 50);
        }
    };

    frame.getContentPane().add(panel);
    frame.setVisible(true);
}

private static final Color background = new Color(1f, 1f, 1f, 0.5f);
private static final Color foreground = new Color(0f, 0f, 0f, 0.5f);
private static final int borderRadius = 16;
private static final int width = 100;
private static final int height = 50;

private static void paintExampleBubble(Graphics g, int x, int y) {

    g.setColor(background);
    g.fillRoundRect(x, y, width, height, borderRadius, borderRadius);
    g.setColor(foreground);
    g.drawRoundRect(x, y, width, height, borderRadius, borderRadius);
}

以上代码生成的结果如下:

无论如何,这表明绘制到 BufferedImage 是导致问题的原因,但是目前不能放弃 BufferedImages。

我尝试调试代码以查看可能导致这种差异的原因,并且只注意到图形对象在涉及透明度时使用不同的组件进行绘制,但这并不能帮助我解决我的问题,因为,即使如果可以强制图形执行我希望它们执行的操作,我宁愿尽可能避免黑客攻击。

有谁知道解决或解决此问题的相对简单有效的方法吗?

无论如何,感谢您花时间阅读本文:)

PS : 由于是第一次提问,可能漏掉了一些东西,如果是这样的话请尽管告诉我!非常感谢。

编辑:正如我在评论中所说,该游戏是基于像素艺术的,因此我宁愿不使用抗锯齿,而是保留圆角矩形的基本像素化外观。

Here, you see that when the bubble on the left is fading, its rounded corners change shape compared to before fading, whereas the bubble on the right's rounded corners do not change. Indeed, the left bubble is drawn on a BufferedImage which is then drawn on the panel, whereas the right bubble is directly drawn on the panel.

与其每次都使用不同的 alpha 值重新绘制图像,不如创建一次并使用 AlphaComposite 来管理透明度。

下面是你的例子的一个改编,有三个'bubbles':最左边是每次改变前景颜色时绘制图像,右边的两个使用AlphaComposite(中间使用一个图像创建一次,最右边直接使用JPanel图形)。

public class Test {

    public static void main(String[] args) {

        JFrame frame = new JFrame("Test");
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setSize(600, 200);
        final BufferedImage image = new BufferedImage(600, 200, BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = image.createGraphics();
        paintExampleBubble(graphics, 250, 50, foreground);
        graphics.dispose();
        final JPanel panel = new JPanel() {

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D)g;

                final BufferedImage i2 = new BufferedImage(600, 200, BufferedImage.TYPE_INT_ARGB);
                Graphics2D graphics = i2.createGraphics();
                paintExampleBubble(graphics, 50, 50, alphaForeground);
                graphics.dispose();
                g.drawImage(i2, 0, 0, this);
                //use Alpha Composite for transparency
                Composite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER , alpha );
                g2d.setComposite(comp);
                g2d.drawImage(image, 0, 0, this);

                paintExampleBubble(g2d, 450, 50, foreground);
            }
        };
        javax.swing.Timer timer = new javax.swing.Timer(100, new ActionListener(){

            @Override
            public void actionPerformed(ActionEvent e) {
                alpha -= 0.05;

                if ( alpha < 0 ){
                    alpha = 1.0f;
                }
                alphaForeground = new Color(0f, 0f, 0f, alpha);
                panel.repaint();
            }

        });
        timer.start();
        frame.getContentPane().add(panel);
        frame.setVisible(true);
    }

    private static float alpha = 1.0f;
    private static final Color background = new Color(1f, 1f, 1f, 1f);
    private static final Color foreground = new Color(0f, 0f, 0f, 1f);
    private static Color alphaForeground = new Color(0f, 0f, 0f, alpha);
    private static final int borderRadius = 16;
    private static final int width = 100;
    private static final int height = 50;

    private static void paintExampleBubble(Graphics g, int x, int y, Color color) {
        g.setColor(background);
        g.fillRoundRect(x, y, width, height, borderRadius, borderRadius);
        g.setColor(color);
        g.drawRoundRect(x, y, width, height, borderRadius, borderRadius);
    }
}

在我的系统上,我看到最左边有失真(使用前景色管理透明度),但没有使用 AlphaComposite 透明度