Search code examples
javabufferedimagejava-2dantialiasinggraphics2d

Java2D/Swing: Rendering a component with text anti aliasing to a BufferedImage


I would like to render a Java Swing component, e.g. a JButton, which I also put on a JFrame, to a BufferedImage. This works in general, but with a major drawback: Text anti aliasing, especially "LCD" anti aliasing mode, is not working when rendering to a BufferedImage.

I've put some example code together to demonstrate the problem, but first my system information:

  • OS: Windows 7 64 Bit
  • JVM: 1.6.0_26-b03 (32 Bit)

The following example code will create a simple JFrame, put a JButton on it and then renders the JButton to a file "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();
  }
}

The following image shows the difference between the JButton in the JFrame on screen, and the same rendered JButton in the image file:

enter image description here

Actually there is some text anti aliasing in the image, but not the LCD optimized anti aliasing which is shown on screen in the JFrame (this problem occurs also with the default LookAndFeel, not only with the "WindowsLookAndFeel").

I already tried to explicitely set the RenderingHint for text anti aliasing on the "g2d", the Graphics2D context of the BufferedImage like so:

g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, 
    RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB );

But it had no effect at all.

I urgently need to render the JButton to an image file like it is rendered on screen (this is just an example, actually I need to render some more complex components, which all suffer from that anti aliasing problem) and I hope there is a real solution without any nasty workaround like taking a screenshot or something.

I really appreciate any help - thanks a lot!


Solution

  • This is a JVM bug

    I have encountered the same problem as you. And I have concluded that the problem is indeed with drawing to a translucent bitmap. I'm fairly certain that we're hitting: https://bugs.java.com/bugdatabase/view_bug?bug_id=6749069

    Workaround

    For now I draw into an opaque bitmap and blit it into my destination bitmap. If the background color behind your text is plain, this should work:

    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);
    }
    

    If your background is not a plain color, you'll have to render the text as white on black to a bitmap, A. Then fill another bitmap with your text color C. Then blit that to your destination image, D as: D += A*C or D = D*(1-A)+A*C or some other suitable blending function.