1

I'm having troubles scaling font to fit in background width. I have a 1000 height and 350 width background, and I'm trying to scale font when it's bigger than background. I've done several test with different font and results are the same, some letters missed or blank spaces at the end of text.

This is the code:

 import java.awt.Color;
 import java.awt.Font;
 import java.awt.FontMetrics;
 import java.awt.Graphics2D;
 import java.awt.RenderingHints;
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
 import javax.imageio.ImageIO;

public class PruebaStackoverflow {

 public static void main(String[] args) {

    String titleText = null;
    Graphics2D g2D = null;
    Font testFont = null;

    File imageGrayBackgroundFile = new File(
            "resources/pruebaAltaResolucionGris.png");
    File destinationImageGray = new File("resources/outputTextGray.png");

    BufferedImage background = readImage(imageGrayBackgroundFile);

    titleText = "Lorem ipsum dolor sit amet asdkf sdm";
    testFont = new Font("Lucida Console", Font.PLAIN, 50);

    g2D = background.createGraphics();
    g2D.setColor(Color.BLACK);
    g2D.setFont(testFont);

    g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

    g2D = scaleFontFromFontMetrics(g2D, background, titleText);

    g2D.drawString(titleText, 0, 150);
    g2D.dispose();

    writeImage(destinationImageGray, background);

}

private static Graphics2D scaleFontFromFontMetrics(Graphics2D g2D,
        BufferedImage backgroundImage, String text) {
    double xScale;
    double yScale;
    double scale;
    Integer backgroundWidth = null;
    Integer backgroundHeight = null;
    Integer textWidth = null;
    Integer textHeigth = null;

    backgroundWidth = backgroundImage.getWidth();
    backgroundHeight = backgroundImage.getHeight();

    Font f = g2D.getFont();
    FontMetrics fm = g2D.getFontMetrics(f);

    textWidth = fm.stringWidth(text);
    textHeigth = fm.getHeight();

    xScale = backgroundWidth / (double) textWidth;
    yScale = backgroundHeight / (double) textHeigth;

    if (xScale > yScale) {
        scale = yScale;
    } else {
        scale = xScale;
    }

    g2D.setFont(f.deriveFont(AffineTransform.getScaleInstance(scale, scale)));

    return g2D;
}

private static BufferedImage readImage(File sourceImage) {
    BufferedImage bufferedImage = null;
    try {
        bufferedImage = ImageIO.read(sourceImage);
    } catch (IOException e1) {
        e1.printStackTrace();
    }
    return bufferedImage;
}

private static void writeImage(File destinationImage,
        BufferedImage bufferedImage) {
    try {
        ImageIO.write(bufferedImage, "png", destinationImage);
    } catch (IOException e) {
        e.printStackTrace();
    }

    System.out.println("Image Saved");
}

}

this is the text to scale "Lorem ipsum dolor sit amet asdkf sdm" and this is text scaled with affine transformation.

output image with font scaled and 'm' letter missed

I hope that you may help me, thanks

SheenStvz
  • 11
  • 1
  • 3
  • The reason it's behaving as it is is due to it choosing the closest font pt size to the width. In this case 14pt rather than 13pt, but 13pt won't 'fill' the entire width. You may need to try creating an image with the text and then scaling the image. – d.j.brown Nov 02 '16 at 09:20

3 Answers3

0

you can measure the length of a string and verify if it fits in your content.

int lengthInPixel = graphics.getFontMetrics().stringWidth("Lorem ipsum dolor sit amet asdkf sdm")
Martin Frank
  • 3,445
  • 1
  • 27
  • 47
0

Following on from my comment earlier, here's a solution where the closest font size to the image width is used. The text is drawn to a separate image, resized, then drawn to the final image. This is all done in the createTextImage() method. Note, I have created a background image rather than using a file.

The outcome may not be as crisp as desired, but you could experiment with different algorithms for resizing. Hopefully it'll give you a starting point.

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

public class Main {

    public static void main(String[] args) {
        String titleText = "Lorem ipsum dolor sit amet asdkf sdm";
        Font initialFont = new Font("Lucida Console", Font.PLAIN, 50);
        BufferedImage textImg = createTextImage(titleText, 350, 1000, 150,
                initialFont, Color.BLACK, Color.GRAY);
        writeImage(new File("outputTextGray.png"), textImg);
    }

    private static BufferedImage createTextImage(String text, int targetWidth,
            int targetHeight, int textYOffset, Font font, Color textColor, Color bgColor) {

        // The final image
        BufferedImage finalImg = createBackgroundImg(targetWidth, targetHeight, bgColor);
        Graphics2D finalImgG = finalImg.createGraphics();        
        Font closestFont = scaleFont(finalImg, font, text);
        finalImgG.setFont(closestFont);

        // Create new image to fit text
        int textWidth = finalImgG.getFontMetrics().stringWidth(text);
        int textHeight = finalImgG.getFontMetrics().getHeight();
        BufferedImage textImg = createBackgroundImg(textWidth, textHeight * 2, bgColor);

        // Draw text
        Graphics2D textImgG = textImg.createGraphics();
        textImgG.setFont(closestFont);
        textImgG.setColor(textColor);
        textImgG.drawString(text, 0, textHeight);

        // Scale text image
        double scale = getScale(textImg.getWidth(), textImg.getHeight(),
                targetWidth, targetHeight);
        Image resized = textImg.getScaledInstance((int) (textImg.getWidth() * scale),
                (int) (textImg.getHeight() * scale), Image.SCALE_SMOOTH);

        // Draw text image onto final image
        finalImgG.drawImage(resized, 0, textYOffset, null);
        return finalImg;
    }

    private static Font scaleFont(BufferedImage img, Font font, String text) {
        Graphics2D g2D = img.createGraphics();
        g2D.setFont(font);
        double scale = getScale(g2D.getFontMetrics().stringWidth(text), 
                g2D.getFontMetrics().getHeight(), img.getWidth(), 
                img.getHeight());
        return g2D.getFont().deriveFont(AffineTransform.getScaleInstance(scale, scale));
    }

    private static double getScale(int width, int height, int targetWidth, int targetHeight) {
        assert width > 0 && height > 0 : "width and height must be > 0";
        double scaleX = (double) targetWidth / width;
        double scaleY = (double) targetHeight / height;
        return scaleX > scaleY ? scaleY : scaleX;
    }

    private static BufferedImage createBackgroundImg(int width, int height, Color color) {
        BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < bufferedImage.getWidth(); x++) {
            for (int y = 0; y < bufferedImage.getHeight(); y++) {
                bufferedImage.setRGB(x, y, color.getRGB());
            }
        }
        return bufferedImage;
    }

    private static void writeImage(File destinationImage, 
            BufferedImage bufferedImage) {
        try {
            ImageIO.write(bufferedImage, "png", destinationImage);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("Image Saved");
    }
}
d.j.brown
  • 1,822
  • 1
  • 12
  • 14
  • Thanks , it's works fine for me but I've another question. If I have to do the same with some image as a background I would have to do the first scale operation with transparent background isn't it? or is there another way ? – SheenStvz Nov 08 '16 at 19:23
  • @SheenStvz Check this question http://stackoverflow.com/questions/17271812/save-buffered-image-with-transparent-background – d.j.brown Nov 08 '16 at 19:33
0

Something I do to fit a game-screen to a certain resolution, is to pre-calculate the ratio, after testing its maximum bound. Something like this (I changed my actual code to your case):

double calculateScaling( BufferedImage image, String text, Font font ){

    //These are final to avoid accidental mending, since they are 
    //the base for our calculations.
    //Belive me, it took me a while to debug the 
    //scale calculation when I made this for my games :P

    /**
     * imageWidth and imageHeight are the bounds
     */
    final int imageWidth = image.getWidth();
    final int imageHeight = image.getHeight();

    Graphics2D g2 = image.createGraphics();
    FontMetrics fm = g2.getFontMetrics( font );


    /**
     * requestedStringWidthSize e requestedStringHeightSize are the measures needed 
     * to draw the text WITHOUT resizing.
     */
    final int requestedStringWidthSize = fm.stringWidth( text );
    final int requestedStringHeightSize = fm.getHeight();


    double stringHeightSizeToUse = imageHeight;
    double stringWidthSizeToUse;

    double scale = stringHeightSizeToUse/requestedStringHeightSize;
    stringWidthSizeToUse = scale*requestedStringWidthSize;

    /**
     * Checking if fill in height makes the text go out of bound in width,
     * if it does, it rescalates it to size it to maximum width.
     */
    if( imageWidth < ((int)(Math.rint(stringWidthSizeToUse))) ) {
        stringWidthSizeToUse = imageWidth;

        scale = stringWidthSizeToUse/requestedStringWidthSize;
        //stringHeightSizeToUse = scale*requestedStringHeightSize;
    }

    g2.dispose(); //we created this to use fontmetrics, now we don't need it.

    return scale;
}

In my game, I would store the scale as float instead of double to avoid heavy calculations on the run, but for you it's just a simple scaling, right?

All you have to do now is to study the code and implement it to yours.

I hope I have helped.

Have a nice day. :)

Saclyr Barlonium
  • 453
  • 4
  • 12