How to fix text quality in Java graphics?

2019-02-04 19:51发布

问题:

Code:

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

import javax.swing.JFrame;

public class TextRectangle extends JFrame {

    private static final long serialVersionUID = 1L;

    public static void main(String[] a) {
        TextRectangle f = new TextRectangle();
        f.setSize(300, 300);
        f.setVisible(true);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(
            RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2d.setRenderingHint(
            RenderingHints.KEY_RENDERING,
            RenderingHints.VALUE_RENDER_QUALITY);

        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, getWidth(), getHeight());

        g2d.setColor(Color.BLACK);
        g2d.setFont(new Font("Calibri", Font.BOLD, 18));
        g2d.drawString("Why does this text look so ugly?", 30, 150);
    }

}

Result:

As you can see the letter spacing is uneven. Sometimes it's 1px, sometimes it's 2 or even 3 pixels. Another issue is the dot above 'i' is wider than the bottom part.

The same text written in a text editor looks much better. Do you know how to fix this problem?

My environment is Windows 8.1, Java 1.8.

回答1:

The graphics context uses integer metrics by default - meaning that rounding is applied to the location vectors of the glyph shapes.

You can switch to fractional metrics using:

g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
    RenderingHints.VALUE_FRACTIONALMETRICS_ON);

As you can see, no subpixel antialiasing was used (only gray antialias pixel). You can enable subpixel antialiasing for better legibility on LCD screens:

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

There are 4 modes:

  • VALUE_TEXT_ANTIALIAS_LCD_HRGB: horizontally oriented RGB
  • VALUE_TEXT_ANTIALIAS_LCD_HBGR: horizontally oriented BGR
  • VALUE_TEXT_ANTIALIAS_LCD_VRGB: vertically oriented RGB
  • VALUE_TEXT_ANTIALIAS_LCD_VBGR: vertically oriented BGR

I haven't found out where to query the appropriate value of the current display and orientation. Some displays can be tilted (landscape/portrait) or even rotated, requiring to redetect the mode when painting.

EDIT

I found something in the Filthy Rich Clients book: apparently, the Java AWT Toolkit can provide appropriate rendering hints:

Map<?, ?> desktopHints = 
    (Map<?, ?>) Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");

Graphics2D g2d = (Graphics2D) g;
if (desktopHints != null) {
    g2d.setRenderingHints(desktopHints);
}
// no need to set more rendering hints

On my system, this renders the text with fractional metrics and LCD HRGB antialiasing, same as with the code above. The hints on my system are:

  • Text-specific antialiasing enable key: LCD HRGB antialiasing text mode
  • Text-specific LCD contrast key: 120


标签: java graphics