5

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!

It's Leto
  • 968
  • 4
  • 17
  • 25
  • Why not post an [SSCCE](http://sscce.org/)? `UIManager.setLookAndFeel( "com.sun.java.swing.plaf.windows.WindowsLookAndFeel" );` and particularly one that might be testable on Mac. or *nix. – Andrew Thompson Nov 03 '11 at 09:23
  • Thanks for your comment - is there anything else besides the windows look and feel that makes my example not a SSCCE? I would be glad to post an example testable under Mac or *nix, but I do not see why (besides the look and feel) this should not run on those operating systems? – It's Leto Nov 03 '11 at 14:24
  • The 1st thing I noticed was a lack of imports. – Andrew Thompson Nov 06 '11 at 02:32
  • Has your question been answered? – Emily L. Feb 27 '14 at 08:36

3 Answers3

4

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: http://bugs.sun.com/bugdatabase/view_bug.do?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.

Emily L.
  • 5,673
  • 2
  • 40
  • 60
2

It's probably too late anyway. The problem could be related to a translucent BufferedImage image you're using. The following seems to work Ok:

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

While looking around I have found similar complains.

Community
  • 1
  • 1
tenorsax
  • 21,123
  • 9
  • 60
  • 107
  • Yes your are right of course - the LCD anti aliasing will fail when I render into an translucent BufferedImage; I have the problem for a long time now (my goal was to fade-in an JComponent, which is not rectangular (at least the "rendered content"), so I rendered it into a translucent BufferedImage and tried to fade it in, which is basically the same problem) and I don't know why I did not include this information in my initial post. – It's Leto Mar 26 '12 at 06:55
0

Either you want (a) your translucent BufferedImage to look as good as your JButton, or (b) you want your JButton and BufferedImage to look the same.

If (b), can you have a translucent JButton? Will it look the same as your translucent BufferedImage?

If (a) then a hard way to do it is, you could maybe paint the JButton to a non-translucent BufferedImage (if Java has a greyscale BufferedImage, use this). This will end up being the negative of your final BufferedImage's alpha channel (a black pixel becomes fully opaque, white fully transparent.) To get the colour for the pixels in the BufferedImage, you'll need to set any pixel that isn't transparent to black - the pixel's transparency should make it come out to be the grey you want.

slofo
  • 21
  • 2