Java2D/Swing: 将带有文本抗锯齿的组件渲染到 BufferedImage 中

5

我想把一个Java Swing组件(如JButton),同时放在JFrame上,渲染成BufferedImage。这个一般都没有问题,但有一个主要的缺陷:当渲染到BufferedImage时,文本反锯齿,特别是“LCD”反锯齿模式不起作用。

我写了一些示例代码来演示这个问题,首先是我的系统信息:

  • 操作系统:Windows 7 64位
  • JVM:1.6.0_26-b03(32位)

以下示例代码将创建一个简单的JFrame,在上面放置一个JButton,并将JButton渲染到文件“test.png”中:

public class TextAntiAliasingTest
{
  public TextAntiAliasingTest() throws IOException
  {
    // Create Test-Button which will be rendered to an image
    JButton button = new JButton( "The Test-Button" );
    button.setSize( 200, 70 );
    button.setLocation( 200, 150 );

    // Create JFrame
    final JFrame frame = new JFrame();
    frame.setSize( 800, 600 );
    frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    frame.setLayout( null );
    frame.setLocationRelativeTo( null );
    frame.add( button );

    // Show JFrame
    SwingUtilities.invokeLater( new Runnable() {
        @Override public void run() {
            frame.setVisible( true );
        }
    });

    // Render JButton to an BufferedImage
    BufferedImage image = new BufferedImage( 800, 600, BufferedImage.TYPE_INT_ARGB );
    Graphics2D g2d = (Graphics2D)image.getGraphics();
    button.paint( g2d );

    // Write BufferedImage to a PNG file
    ImageIO.write( image, "PNG", new File( "test.png" ) );
  }

  public static void main( String[] args ) throws Exception
  {
    UIManager.setLookAndFeel( "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" );
    System.setProperty( "awt.useSystemAAFontSettings", "lcd" );

    new TextAntiAliasingTest();
  }
}

以下图片显示了JFrame中的JButton和图像文件中渲染的相同JButton之间的差异: enter image description here 实际上,图像中存在一些文本抗锯齿,但没有在JFrame中显示的LCD优化的抗锯齿(这个问题也发生在默认的LookAndFeel中,不仅仅是在"WindowsLookAndFeel"中)。
我已经尝试明确设置"g2d"的文本抗锯齿的RenderingHint,即BufferedImage的Graphics2D上下文,如下所示:
g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, 
    RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB );

但这完全没有效果。

我急需将JButton渲染成屏幕上显示的图像文件(这只是一个示例,实际上我需要渲染一些更复杂的组件,它们都遭受了抗锯齿问题),我希望有一个真正的解决方案,而不是采取什么可怕的变通方法,比如截屏。

非常感谢任何帮助-非常感谢!


为什么不发布一个SSCCE呢?UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); 特别是那些可以在Mac或*nix上进行测试的。 - Andrew Thompson
谢谢您的评论 - 除了Windows外观之外,还有什么原因使我的示例不是SSCCE吗?我很乐意发布在Mac或*nix下可测试的示例,但我不明白为什么(除了外观之外)这些操作系统不能运行此示例? - It's Leto
我注意到的第一件事是缺少导入。 - Andrew Thompson
你的问题得到了回答吗? - Emily L.
3个回答

4

这是一个JVM的bug

我遇到了和你一样的问题。我得出结论,问题确实与绘制半透明位图有关。我非常确定我们正在遇到:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6749069

解决方法

现在,我会先绘制到不透明的位图中,然后再将其复制到目标位图中。如果文本背景色是纯色的话,这个方法应该有效:

private void drawString(String text, int x, int y, Graphics2D g, Color bg){    
    // Prepare an off-screen image to draw the string to
    Rectangle2D bounds = g.getFontMetrics().getStringBounds(text, g);
    BufferedImage image = configuration.createCompatibleImage(
                              (int)(bounds.getWidth() + 1.0f), 
                              (int)(bounds.getHeight() + 1.0f),
                              Transparency.OPAQUE);
    Graphics2D ig = image.createGraphics();

    // Fill the background color
    ig.setColor(bg);
    ig.fillRect(0, 0, image.getWidth(), image.getHeight());

    // Draw the string
    int x0 = 0;
    int y0 = ig.getFontMetrics().getAscent();
    ig.setColor(g.getColor());
    ig.setRenderingHints(g.getRenderingHints());
    ig.setFont(g.getFont());
    ig.drawString(text, x0, y0);
    ig.dispose();

    // Blit the image to the destination
    g.drawImage(image, x-x0, y-y0, null);
}

如果你的背景不是纯色,你需要将文本渲染成黑底白字的位图A。然后用文本颜色C填充另一个位图。最后使用适当的混合函数将其混合到目标图像D上,如:D += A*CD = D*(1-A)+A*C等。

2

可能已经太晚了。问题可能与您正在使用的半透明BufferedImage图像有关。以下似乎可以正常工作:

BufferedImage image = new BufferedImage( 800, 600, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = (Graphics2D)image.getGraphics();
g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);

在查找时,我发现了类似的投诉


是的,你当然是对的 - 当我渲染到一个半透明的BufferedImage时,LCD抗锯齿会失败;我已经有很长一段时间遇到这个问题了(我的目标是淡入一个不规则形状的JComponent(至少是“渲染内容”),所以我将其渲染到一个半透明的BufferedImage中并尝试淡入它,这基本上是同样的问题),我不知道为什么我没有在我的初始帖子中包含这些信息。 - It's Leto

0

你可能想要 (a) 使你的半透明 BufferedImage 看起来和你的 JButton 一样好,或者 (b) 使你的 JButton 和 BufferedImage 看起来相同。

如果是 (b),你能否有一个半透明的 JButton?它会看起来和你的半透明 BufferedImage 相同吗?

如果是 (a),那么一个困难的方法是,你可以将 JButton 绘制到一个非半透明的 BufferedImage 上(如果 Java 有灰度 BufferedImage,则使用此选项)。这最终将成为你最终 BufferedImage 的 alpha 通道的反色(黑色像素变为完全不透明,白色完全透明)。要获取 BufferedImage 中像素的颜色,你需要将任何不透明的像素设置为黑色 - 像素的透明度应该使其变成你想要的灰色。


欢迎来到 SO,如果您可以,请添加一些代码示例来解释您的解决方案。 - Cristiano Fontes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接