JDK-6964747 : Text is not rendered properly if subpixel AA and scaling transformation is used
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 6u10
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_vista
  • CPU: x86
  • Submitted: 2010-06-28
  • Updated: 2012-03-20
  • Resolved: 2010-08-09
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) Client VM (build 16.3-b01, mixed mode, sharing)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.0.6002]

A DESCRIPTION OF THE PROBLEM :
If subpixel antialiasing and scaling transformation (both on Graphics2D) is used then sometimes text is not rendered properly. The issue seems to occur only if the resulting font size (initial font size plus scaling) is large. It seems that the Java VM caches some font related data which becomes invalid if this cached font object is used in conjunction with a Graphics2D.drawString(AttributedCharacterIterator) call. After it has become invalid then all Graphics2D.drawString() methods do not render text property anymore if used with this font object.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the following test application. There are two panels which render text. Each panel renders two strings. The upper string is rendered with subpixel antialiasing and the lower text is rendered with gray-scaled antialiasing. The upper panel uses a scaled font object to scale the font size. The lower panel uses the Graphcis2D.scale(double,double) method to scale the font size. The upper string of the lower panel is not rendered properly due to subpixel antialiasing. If you move the slider then you can see that on small font sizes the issue seems not to occur.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Text is rendered properly in all cases (independant of scaling or AA settings).
ACTUAL -
Text is not rendered properly if subpixel AA is enabled and font is scaled via Graphics2D.scale(double,double) method.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.font.TextAttribute;
import java.awt.geom.AffineTransform;
import java.text.AttributedString;

import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

/**
 *
 */
public class SubpixelAATest extends JFrame {
    
    TestComponent testComponent1;
    TestComponent testComponent2;
    JSlider slider;
    JLabel scaleLabel;
    JTextField textField;
    
    public SubpixelAATest() {
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("Subpixel Antialiasing Render Bug");
        
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BorderLayout());
        getContentPane().add(mainPanel);
        
        JPanel topPanel = new JPanel();
        topPanel.setLayout(new BoxLayout(topPanel,BoxLayout.Y_AXIS));
        topPanel.setBorder(new EmptyBorder(4,4,4,4));
        mainPanel.add(topPanel,BorderLayout.NORTH);
        
        JLabel descriptionLabel = new JLabel("The first string of each panel is painted with subpixel antialiasing and the second string with normal gray-scaled antialiasing.");
        descriptionLabel.setBorder(new EmptyBorder(8,0,8,0));
        topPanel.add(descriptionLabel);
        
        textField = new JTextField();
        textField.setText("Test123 ABC abc");
        textField.getDocument().addDocumentListener(new DocumentListener() {
            
            public void removeUpdate(DocumentEvent e) {
                refreshPanels();
            }
            
            public void insertUpdate(DocumentEvent e) {
                refreshPanels();
            }
            
            public void changedUpdate(DocumentEvent e) {
                refreshPanels();
            }
        });
        topPanel.add(textField);
        
        JPanel testCompPanel = new JPanel();
        testCompPanel.setLayout(new GridLayout(2,1));
        mainPanel.add(testCompPanel,BorderLayout.CENTER);
        
        testComponent1 = new TestComponent(TestComponent.MODE_FONT);
        testComponent1.setBorder(new TitledBorder("Method \"drawString\" with scaled font:"));
        testCompPanel.add(testComponent1);
        
        testComponent2 = new TestComponent(TestComponent.MODE_G2D);
        testComponent2.setBorder(new TitledBorder("Method \"drawString\" with scaled Graphics2D:"));
        testCompPanel.add(testComponent2);
        
        JPanel scalePanel = new JPanel();
        scalePanel.setLayout(new BorderLayout());
        mainPanel.add(scalePanel,BorderLayout.SOUTH);
        
        scaleLabel = new JLabel();
        scaleLabel.setBorder(new EmptyBorder(0,10,0,0));
        
        scalePanel.add(scaleLabel,BorderLayout.NORTH);
        
        slider = new JSlider(0,100);
        slider.setMajorTickSpacing(10);
        slider.setPaintLabels(true);
        slider.getModel().addChangeListener(new ChangeListener() {
            
            public void stateChanged(ChangeEvent e) {
                scaleLabel.setText("Scale factor: "+slider.getValue());
                refreshPanels();
            }
        });
        
        scalePanel.add(slider,BorderLayout.SOUTH);
        slider.setValue(100);
    }
    
    void refreshPanels() {
        testComponent1.repaint();
        testComponent2.repaint();
    }
    
    class TestComponent extends JComponent {
        
        static final int MODE_FONT = 0;
        static final int MODE_G2D = 1;
        
        int drawMode;
        Font font = new Font("SansSerif",Font.PLAIN,1);
        
        public TestComponent(int drawMode) {
            this.drawMode = drawMode;
            setBackground(Color.WHITE);
        }
        
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            
            Graphics2D g2d = (Graphics2D)g.create();
            
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                            RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            
            float scale = slider.getValue();
            String string = textField.getText();
            
            if ((string == null) || (string.length() == 0)) {
                string = " ";
            }
            
            if (scale > 0) {
                switch(drawMode) {
                case MODE_FONT:
                    setAntialiasing(g2d,false);
                    drawString(g2d,10,100,1.0f,string,font.deriveFont(scale));
                    
                    setAntialiasing(g2d,true);
                    drawString(g2d,10,200,1.0f,string,font.deriveFont(scale));
                    break;
                
                case MODE_G2D:
                    setAntialiasing(g2d,false);
                    drawString(g2d,10,100,scale,string,font);
                    
                    setAntialiasing(g2d,true);
                    drawString(g2d,10,200,scale,string,font);
                    break;
                }
            }
            
            g2d.dispose();
        }
        
        private void setAntialiasing(Graphics2D g, boolean subpixelAA) {
            if (subpixelAA) {
                g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            }
            
            else {
                g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                                RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
            }
        }
        
        private void drawString(Graphics2D g, int x, int y, float scale,
                        String string, Font font) {
            
            AffineTransform t = g.getTransform();
            
            g.translate(x,y);
            g.scale(scale,scale);
            
            g.setColor(Color.BLACK);
            AttributedString attrString = new AttributedString(string);
            attrString.addAttribute(TextAttribute.FONT,font);
            g.drawString(attrString.getIterator(),0,0);
            
            g.setColor(Color.GREEN);
            
            // This second draw string call is broken. It seems that the font
            // object on native-level itself is broken because all methods which
            // can draw strings produce broken output even for a new Java Font
            // object with the same font properties like in the following
            // example:
            //g.setFont(new Font("SansSerif",Font.PLAIN,1));
            g.setFont(font); // font = { "SansSerif", Font.PLAIN, 1 }
            g.drawString(string,0.0f,0.0f);
            
            g.setTransform(t);
        }
    }
    
    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        }
        
        catch(Throwable throwable) {
            throwable.printStackTrace();
        }
        
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                SubpixelAATest test = new SubpixelAATest();
                test.setBounds(10,10,1024,640);
                test.setVisible(true);
            }
        });
    }
}

---------- END SOURCE ----------

Comments
EVALUATION neatly demonsrates the exact problem fixed in 6u21 as bug 6925760
09-08-2010