United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4912220 : 1.4 REGRESSION: Flipping with asymmetric scaling often distorts fonts

Details
Type:
Bug
Submit Date:
2003-08-26
Status:
Resolved
Updated Date:
2006-03-22
Project Name:
JDK
Resolved Date:
2006-03-22
Component:
client-libs
OS:
windows_xp
Sub-Component:
2d
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
1.4.1,1.4.2
Fixed Versions:

Related Reports
Relates:

Sub Tasks

Description
Name: jk109818			Date: 08/25/2003


FULL PRODUCT VERSION :
java version "1.4.1_02"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.1_02-b06)
Java HotSpot(TM) Client VM (build 1.4.1_02-b06, mixed mode)

FULL OS VERSION :
Microsoft Windows XP [Version 5.1.2600]

EXTRA RELEVANT SYSTEM CONFIGURATION :
Adapter Type	S3 Savage/IX, S3 Graphics, Inc. compatible
Adapter Description	S3 Graphics Savage/IX 103C
Adapter RAM	4.00 MB (4,194,304 bytes)


A DESCRIPTION OF THE PROBLEM :
Some combinations of scaling parameters and font sizes cause severe font distortion when applying a flip transform as well.  That is, some values of "xscale" and "yscale" used in the following transformation with some font sizes will cause severe font rendering problems:

    | xscale   0        0  |
    | 0       -yscale   0  |

where as the "non-flipped" transformation does not cause any problems:

    | xscale   0        0  |
    | 0        yscale   0  |

Examples are given in the sample program below.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the sample program below.

EXPECTED VERSUS ACTUAL BEHAVIOR :
Flipped fonts should look like mirror images of non-flipped fonts when the same (assymetric) scaling is applied to both.
Fonts are severely distorted when flipped for many combinations of scaling factors and font sizes.

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.awt.*;
import java.awt.geom.*;

public class TextFlipBug extends Component {
    static final float PAD = 15.0f;
    static final String STR = "J  Q  K";

    Font font1 = null;
    Font font2 = null;
    float xscale1, xscale2, yscale1, yscale2;
    static final Font IFONT = new Font("Dialog", Font.PLAIN, 12);

    TextFlipBug(float xs1, float ys1, int fs1,
              float xs2, float ys2, int fs2) {
        xscale1 = xs1;
        yscale1 = ys1;
        font1 = new Font("serif", Font.PLAIN, fs1);

        xscale2 = xs2;
        yscale2 = ys2;
        font2 = new Font("serif", Font.PLAIN, fs2);
    }

    private void draw_flip(Graphics2D g2,
                           Font font,                    // Font to use
                           String str,                   // String to point
                           float xscale, float yscale,   // Scale for X and Y
                           float midx, float midy,       // Mid point to draw around
                           boolean left)                 // Print to left or right of midx?
    {
        AffineTransform oldtx = g2.getTransform();

        g2.setFont(font);
        FontMetrics fm = g2.getFontMetrics();

        // If printing left of
        float len = xscale * (float) fm.stringWidth(str);
        float lshift = PAD;
        if (left) {
            lshift = -PAD - len;
        }
        // Scale X and Y and draw string:
        g2.translate(midx + lshift, midy - PAD);
        g2.scale(xscale, yscale);
        g2.drawString(str, 0f, 0f);

        // Flip about Y and draw string:
        g2.setTransform(oldtx);
        g2.translate(midx + lshift, midy + PAD);
        g2.scale(xscale, -yscale);
        g2.drawString(str, 0f, 0f);

        g2.setTransform(oldtx);

        g2.setFont(IFONT);
        String istr = "(XS=" + xscale + ", YS=" + yscale + ", FS=" + font.getSize() + ")";
        fm = g2.getFontMetrics();
        lshift = 2*PAD + len;
        if (left) {
            lshift = - 2*PAD - len - (float) fm.stringWidth(istr);
        }
        g2.drawString(istr, midx + lshift , midy - PAD);


    }

    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;

        // Turning these off/on makes no difference in distortion:
        g2.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS,
                             RenderingHints.VALUE_FRACTIONALMETRICS_ON );
        g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
                             RenderingHints.VALUE_ANTIALIAS_ON );
        g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
                             RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
        
        Dimension d = this.getSize();
        float midx = (float) d.getWidth()/2;
        float midy = (float) d.getHeight()/2;

        g2.drawLine((int) midx, 0, (int) midx, d.height);
        g2.drawLine(0, (int) midy, d.width, (int) midy);

        draw_flip(g2, font1, STR, xscale1, yscale1, midx, midy, true);
        draw_flip(g2, font2, STR, xscale2, yscale2, midx, midy, false);
    }

    public static void main(String args[]) {
        Frame f;
        int loc = 0;

        f = new Frame("Yscale under 10.1 is very bad for font size 10 (Xscale closer to 10 helps).");
        TextFlipBug tf = new TextFlipBug(2f, 10.1f, 10, 2f, 10.0f, 10);
        f.add("Center", tf);
        f.pack();
        f.setLocation(loc, loc);
        f.show();
        loc += 50;

        f = new Frame("Font size under 26 is bad for Xscale=2 and Yscale=4.");
        // Any font size below 26 is bad with xscale=2, yscale=4
        tf = new TextFlipBug(2f, 4f, 26, 2f, 4f, 25);
        f.add("Center", tf);
        f.pack();
        f.setLocation(loc, loc);
        f.show();
        loc += 50;

        f = new Frame("Xscale less than 0.9 is bad for Yscale=1.0.");
        //
        tf = new TextFlipBug(0.9f, 1f, 50, 0.5f, 1f, 50);
        f.add("Center", tf);
        f.pack();
        f.setLocation(loc, loc);
        f.show();
        loc += 50;
    }

    public Dimension getPreferredSize() {
        return new Dimension (700, 200);
    }

    public Dimension getMinimumSize() {
        return getPreferredSize();
    }

}


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

CUSTOMER SUBMITTED WORKAROUND :
I couldn't find a satisfactory one.  In some cases drawing to a BufferedImage and then flipping the image with a BufferedImageOp might be sufficient, unless you are drawing within the rectangle which bounds the text.  I've tried making the image transparent, but this introduces other artifacts.

Release Regression From : 1.3.1_08
The above release value was the last known release where this 
bug was known to work. Since then there has been a regression.

(Incident Review ID: 184900) 
======================================================================

                                    

Comments
EVALUATION

This bug was fixed as part of changes for 4654540.
Now we perform hinting with stretch transformation matrix that preserves 
proportions of the glyph. 

See attachments for before/after examples.
                                     
2006-03-14
EVALUATION

Truetype hinting is designed without support for arbitrary transforms.
To overcome this limitation we are trying to apply hints with 
"safe" identity matrix and then transform hinted outline
when non-trival transform is requested.

Reported problem is sideeffect of using identity transform - 
it does not preserve glyph proportions which are still 
available as ppem values to the hinting engine.
This cause wrong results of computations.

Another side of this bug is that glyphs are not symmetrical 
when user will expect them to be. E.g. using negative scale (say of size -10)
will cause hinting with different proportions/ppem values 
and result will likely differ from flipped version of same 
glyph but with positive scale transform (size 10).

Scan conversion may also contribute to these differences. 
In particular some of dropout control methods assume usage of 
"round to left/top" rules.
                                     
2006-01-16
EVALUATION

Not for tiger.
###@###.### 2003-11-23
                                     
2003-11-23



Hardware and Software, Engineered to Work Together