Search code examples
javaimagefontsawt

Font in Java AWT not rendered correctly when it's larger than 100 points


I'm trying to render the text into an image using JAVA AWT. When I use a font smaller than 100 points, everything works as expected. But when I use, for example, 240 points, then some parts of the text that overlap are transparent like in this image. In the sample image, I used this font. I used this font in a text editor without any problem.

The font is loaded using this code:

Font font = Font.createFont(Font.TRUETYPE_FONT, new File("src/main/resources/Autumn in November.ttf"));
Font derived = font.deriveFont(240f);

To render the text in the image I use this code:

 public static void saveTextToImage(String text, Font font) throws IOException {

        # Temp image to get the text dimensions
        BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
        Graphics2D tempGraphics = tempImage.createGraphics();
        tempGraphics.setFont(font);
        FontMetrics fontMetrics = tempGraphics.getFontMetrics();
        FontRenderContext fontRenderContext = tempGraphics.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(fontRenderContext, text);
        Area area = new Area(gv.getOutline());
        Rectangle2D rectangle2D = area.getBounds2D();
        int width = (int) rectangle2D.getWidth();
        int height = (int) rectangle2D.getHeight();

        // Create a new image with white background
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = img.createGraphics();
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);

        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // Draw the text on the image
        graphics.setColor(Color.BLACK);
        graphics.setFont(font);
        graphics.drawString(text, 10, fontMetrics.getAscent() + 5);

        // Save the image
        File outputFile = new File(text + ".jpg");
        ImageIO.write(img, "jpg", outputFile);
}

I've tried changing the font type to OTF, adding and changing RenderingHints and also switching the image type to PNG, but without success. I don't know what else I could try.


Solution

  • This is indeed a strange behavior. I can’t tell you why this happens, but I can offer you a work-around; the problem does not occur when rendering the string’s glyphs one by one:

    static final boolean USE_WORKAROUND = true;
    
    public static void saveTextToImage(String text, Font font) throws IOException {
        // Temp image to get the GlyphVector and text dimensions
        BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
        Graphics2D tempGraphics = tempImage.createGraphics();
        tempGraphics.setFont(font);
        tempGraphics.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        FontRenderContext fontRenderContext = tempGraphics.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(fontRenderContext, text);
        Rectangle2D rectangle2D = gv.getVisualBounds();
        int width = (int)(rectangle2D.getWidth());
        int height = (int)(rectangle2D.getHeight());
    
        // Create a new image with white background
        BufferedImage img
            = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = img.createGraphics();
        graphics.setBackground(Color.WHITE);
        graphics.clearRect(0, 0, width, height);
        graphics.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setColor(Color.BLACK);
    
        // using GlyphVector avoids repeating the work already done above
        if(!USE_WORKAROUND) {
            graphics.drawGlyphVector(gv,
                (float)-rectangle2D.getX(), (float)-rectangle2D.getY());
        } else {
            graphics.translate(-rectangle2D.getX(), -rectangle2D.getY());
            for(int gix = 0, gnum = gv.getNumGlyphs(); gix < gnum; gix++) {
                graphics.fill(gv.getGlyphOutline(gix));
            }
        }
    
        // Save the image
        File outputFile = new File(text + ".jpg");
        ImageIO.write(img, "jpg", outputFile);
    }
    

    As a side note, I would use PNG instead of JPG as it is better suited for such images, but perhaps you don’t have this choice.

    Further note that rendering an existing GlyphVector via drawGlyphVector has been documented to be “the fastest way to render a set of characters to the screen”, so if we have to perform this preparation work anyway for calculating the bounds, why not use it for the rendering…