This discussion is archived
11 Replies Latest reply: Nov 10, 2011 2:17 AM by 804030 RSS

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

804030 Newbie
Currently Being Moderated
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:

[url http://dl.dropbox.com/u/15020526/Privat/Temp/java_swing_text-antialiasing-problem.png]java_swing_text-antialiasing-problem.png

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!
  • 1. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    800025 Explorer
    Currently Being Moderated
    Hi,

    I don't think that the Graphics2D object created from a BufferedImage is initialized with values from gui settings. So probably you should set the desired rendering hints explicitly before calling paint(Graphics).

    As far as I know, the wiring in Swing components prepare the Graphics object before calling paint(Graphics)

    By the way, did you see the (late) response to your earlier thread by Samuel? Re: Setting LCD-AA text rendering hint has no effect on my JLabel



    And, this has nothing to do with your actual question, one should execute virtually all code regarding swing components on the EDT, including creation of those components, not only setVisible().

    Hope this helps.

    Piet
  • 2. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    StanislavL Pro
    Currently Being Moderated
    Try to play with image types. E.g. change to BufferedImage.TYPE_INT_RGB. I heard alpha part conflicts with antialiasing.
  • 3. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    804030 Newbie
    Currently Being Moderated
    Hello,

    thanks a lot for your answers!
    I don't think that the Graphics2D object created from a BufferedImage is initialized with values from gui settings. So probably you should set the desired rendering hints explicitly before calling paint(Graphics).
    As I wrote above, I already tried to set appropriate rendering hints, but that did not help.
    By the way, did you see the (late) response to your earlier thread by Samuel? Re: Setting LCD-AA text rendering hint has no effect on my JLabel
    Thanks for the hint - I did not see the answer. The explanation of Samuel is very helpful.

    And, this has nothing to do with your actual question, one should execute virtually all code regarding swing components on the EDT, including creation of those components, not only setVisible().
    Ok, thanks for the advice!

    >
    StanislavL
    Try to play with image types. E.g. change to BufferedImage.TYPE_INT_RGB. I heard alpha part conflicts with antialiasing.
    >

    Thanks a lot StanislavL, that is the solution, well, partly.

    My goal is to use that rendered image to transparently blend it in / fade in, by rendering the image on a JPanel with an AlphaComposite and changing alpha value. With an image type of "TYPE_INT_RGB", that means that there are black parts, which are transparent with an image type of "TYPE_INT_ARGB". So with those black areas, a fade-in effect is just not possible.

    Well, I really hope I'm not in some kind of dead end here, because it seems I need to have an image type with an alpha channel, but correct LCD-antialiasing of text seems not possible :(


    I really appreciate any further ideas... thanks a lot!
  • 4. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    Maxideon Explorer
    Currently Being Moderated
    Just paint the JPanel onto the image to serve as the background, instead of the black

    or

    Create an intermediate RGB image the size of your largest component. Paint a widget onto the RGB image and then the RGB image onto the ARGB one in the appropriate location. Rinse and repeat with the other widgets.

    or

    Overide the JPanel's paintChildren to paint the children normally, and then paint a solid color over it all. Link the painting of the solid color to an AlphaComposite and timer. No images needed.

    or

    Use JXLayer from the Swinglabs or JLayer in java 7. I'm guessing fade effects can be achieved easily with those classes.
  • 5. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    StanislavL Pro
    Currently Being Moderated
    As the first step create the opaque image. Then create one more image with alpha. The render the first one on the second and fill the rectangle with a semitransprent color (new Color(r, g, b, alpha))
  • 6. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    804030 Newbie
    Currently Being Moderated
    Thanks again for your suggestion.

    Maxideon:

    >
    Just paint the JPanel onto the image to serve as the background, instead of the black
    >

    Well, I don't know how to do this at the moment. Since the JPanel itself is non-opaque (or could be, at least), because I would like to fade in the children components, I would have to paint the parent container with all it's children but without the fading-in-panel first to the image. Sounds rather complicated...

    >
    Create an intermediate RGB image the size of your largest component. Paint a widget onto the RGB image and then the RGB image onto the ARGB one in the appropriate location. Rinse and repeat with the other widgets.
    >

    This will only work for rectangle-child-components. But what, if one of the child components has transparent areas? Let's say there is a JPAnel that paints itself just as a circle. In those cases this will not work (and those cases are not rare).

    >
    Overide the JPanel's paintChildren to paint the children normally, and then paint a solid color over it all. Link the painting of the solid color to an AlphaComposite and timer. No images needed.
    >

    I don't understand this idea - why should this result in fading in a JPanel? Think about it - when I draw a alpha blended rectangle over the JPanel, it will cover the parent component also. This will only work if I have a solid color background behind the fade-in JPanel.

    >
    Use JXLayer from the Swinglabs or JLayer in java 7. I'm guessing fade effects can be achieved easily with those classes.
    >

    I think this solution has another problem: Using JXLayer (I've done this before), I could manipulate the Graphics2D object, setting an AlphaComposite with an appropriate alpha value, so the painting operations for the children would use transparancy. But: If those child components use different alpha values with an own AlphaComposite itself in their paint methods, the result will be a weird effect when the AlphaComposite set in the JXLayer will be overidden by the childs. Furthermore, even when a child does not set an AlphaComposite itself, the "relative transparencies" (the different alpha values) will not be consistent, depending on the paint operations of the childs (if they just use alpha values in their colors, for example). It is hard to explain, though.

    However, this is all not applicable, if the fade-in with JXLayer does not involve setting an AlphaComposite on the Graphics2D object, of course. If so, please explain how JXLayer should be used then.

    For those reasons I startet using an image which I wanted to fade in, after all. Well, it turned out that this works well, with the exception of text-lcd-antialiasing :-/


    StanislavL:

    >
    As the first step create the opaque image. Then create one more image with alpha. The render the first one on the second and fill the rectangle with a semitransprent color (new Color(r, g, b, alpha))
    >


    I understood your suggestion until "and fill the rectangle with a semitransprent color (new Color(r, g, b, alpha))". What I understand is that I (1.) should render the JPanel to an image of the RGB (without alpha channel). Then (2.) I create a second image of the same size, this time with an alpha channel (type ARGB). Next (3.) I paint the first image into the second image. And then, what's the final step? :)


    Thanks a lot!
  • 7. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    StanislavL Pro
    Currently Being Moderated
    g.setColor(new Color(...)); //color with the alpha
    g.fillRect(...); //pass 0,0, imgWidth, imgHeight
  • 8. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    804030 Newbie
    Currently Being Moderated
    Hello StanislavL,

    >
    g.setColor(new Color(...)); //color with the alpha
    g.fillRect(...); //pass 0,0, imgWidth, imgHeight
    >

    this is, when I understand this right, the same suggestion as Maxideon has posted above. Just paint an alpha blendet rectangle over the image.

    But, in your first step "As the first step create the opaque image", if the component to render is transparent itself or has transparent areas (e.g. the component is just a circle or has rounded corners etc.), there are black opaque areas in that image. So, painting an alpha blended rectangle over it just makes no sense, because the real problem is here the opaque-ness of the first image, which will cover all other child components. In other words, those black opaque areas will stay black and opaque, covering underlying child components completely, no matter if you paint an alpha blended rectangle over it.

    Currently, I think about some kind of color masking, so filling the first opaque image with a special color which will later on be exchanged by an alpha channel. But I doubt that the result will be of any high quality.

    It seems more and more that this is just not possible with Java2D...
  • 9. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    StanislavL Pro
    Currently Being Moderated
    You cn use this
    http://www.java2s.com/Code/Android/2D-Graphics/Createatransparentbitmapfromanexistingbitmapbyreplacingcertaincolorwithtransparent.htm

    or this
    http://stackoverflow.com/questions/2369809/how-to-replace-colors-in-bufferedimage-in-java
  • 10. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    Maxideon Explorer
    Currently Being Moderated
    >
    Overide the JPanel's paintChildren to paint the children normally, and then paint a solid color over it all. Link the painting of the solid color to an AlphaComposite and timer. No images needed.
    >

    I don't understand this idea - why should this result in fading in a JPanel? Think about it - when I draw a alpha blended rectangle over the JPanel, it will cover the parent component also. This will only work if I have a solid color background behind the fade-in JPanel.
    Isn't something fading in visually equivalent to what's already there fading out? I mispoke when I said solid color; it's just that panels in most LAF's are a solid color. I meant paint the panel over all of its children. Here's a demonstration.
    import javax.swing.*;
    import java.awt.AlphaComposite;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Component;
    
    public class SSCCE {
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    createAndShowGUI();
                }
            });
        }
        private static void createAndShowGUI() {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            }catch(Exception e) {
                e.printStackTrace();
            }
            
            JFrame frame = new JFrame();
            final FadePanel contentPane = new FadePanel();
            JButton clickMe = new JButton("Click me!");
            clickMe.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    contentPane.fade();
                }
            });
            
            contentPane.setLayout(new java.awt.GridBagLayout());
            contentPane.add(clickMe);
            
            frame.setContentPane(contentPane);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setVisible(true);
        }
        
        public static class FadePanel extends JPanel implements ActionListener{
            Timer t = new Timer(30,this);
            float alpha = 0f;
            
            @Override
            protected void addImpl(Component comp, Object constraints, int index) {
                super.addImpl(comp, constraints, index);
                if(comp instanceof JComponent) {
                    ((JComponent) comp).setOpaque(false);
                }
            }
            public void paintChildren(Graphics g) {
               super.paintChildren(g);
               
               if(alpha > 0) {
                   Graphics2D g2d = (Graphics2D) g;
                   g2d.setComposite(AlphaComposite.getInstance(
                           AlphaComposite.SRC_OVER, alpha));
                   paintComponent(g2d);
                   g2d.setComposite(AlphaComposite.SrcOver);
               }
            }
            public void fade() {
                alpha = 1f;
                t.restart();
            }
            public void actionPerformed(ActionEvent e) {
                alpha -= .03f;
                if(alpha <= 0f) {
                    t.stop();
                    alpha = 0;
                }
                repaint();
            }
            
        }
    }
    I can forsee a few problems, some which are easily fixed, some not so easily fixed. But for most cases, the technique should work.
  • 11. Re: Java2D/Swing: Rendering a component with text anti aliasing to a BufferedIm
    804030 Newbie
    Currently Being Moderated
    Hello Maxideon,

    thank your for your answer, especially for the example code!

    But unfortunately, your code exactly demonstrates the problem, I tried to explain in my furhter posts. As soon as you paint something with an AlphaComposite and an alpha value lower 1.0, and the component includes rendering Text, LCD anti aliasing does not work. So, if your example JPanel's rendering process includes text, the blend-effect does work, but rendering LCD anti-aliased text is just not possible.

    More and more I believe that this does not seem to be possible in Java 6, due to the LCD text-anti-aliasing limitation with alpha involved.

    Maybe Java 7 will fix this, I don't know...

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points