31

Is there out any Java library that allows converting text content to image files? I only know of ImageMagick (JMagick in this case) but I wouldn't like to install any external binaries (my app will be deployed as a .war file in a Tomcat server so I don't want any other dependencies more than Java).

For example, from the string "Hello", I would like to generate this simple image:

Basic image from string "hello"

Andrew Thompson
  • 163,965
  • 36
  • 203
  • 405
jarandaf
  • 4,069
  • 4
  • 35
  • 64
  • See also these examples of using a `GlyphVector` as seen in [image in shape of text](http://stackoverflow.com/a/6296381/418556) & [Unicode chessboard](http://stackoverflow.com/a/18686753/418556). And an example of [wrapping text using a label](http://stackoverflow.com/a/7861833/418556). – Andrew Thompson Sep 14 '13 at 12:42

6 Answers6

72

The Graphics 2D API should be capable of achieving what you need. It has some complex text handling capabilities as well.

enter image description here

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

public class TextToGraphics {

    public static void main(String[] args) {
        String text = "Hello";

        /*
           Because font metrics is based on a graphics context, we need to create
           a small, temporary image so we can ascertain the width and height
           of the final image
         */
        BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        Font font = new Font("Arial", Font.PLAIN, 48);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();
        int width = fm.stringWidth(text);
        int height = fm.getHeight();
        g2d.dispose();

        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.setFont(font);
        fm = g2d.getFontMetrics();
        g2d.setColor(Color.BLACK);
        g2d.drawString(text, 0, fm.getAscent());
        g2d.dispose();
        try {
            ImageIO.write(img, "png", new File("Text.png"));
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

}

Also check out Writing/Saving and Image

WARNING I used this to generate 90k PNG images only to find that they can be viewed in IE but not in Chrome Version 70.0.3538.77

The above code works just fine for me (I changed the text color to WHITE so I could see it in chrome)

Running in Chrome

I was using Chrome 70.0.3538.77 on Mac OS Mojave 10.14 using Java 10.0.2. The resulting image was 4778x2411 pixels ...

Updated...

On IE that is black on white but on Chrome that is black on black. Yet I set background to white.

So what you're telling me is, a transparent PNG is been displayed differently on different browsers, because the browsers use different default backgrounds ... why are you surprised by this?

The original solution, deliberately, used a transparent based image. This is evident by the use of BufferedImage.TYPE_INT_ARGB when creating the image, which is applying a Alpha (A) based RGB color model.

This is unexpected as there is g2d.setBackground(Color.white).

No, actually, it is entirely expected, if only you understood what setBackground actually does and how it should be used

From the JavaDocs

Sets the background color for the Graphics2D context. The background color is used for clearing a region. When a Graphics2D is constructed for a Component, the background color is inherited from the Component. Setting the background color in the Graphics2D context only affects the subsequent clearRect calls and not the background color of the Component. To change the background of the Component, use appropriate methods of the Component.

From the "sounds" of things, you want a non-transparent image, with a filled background color. So, once again, it's off to the JavaDocs and a little bit of reading would have lead you to BufferedImage.TYPE_INT_RGB, which removes the Alpha channel, but you'd still have to fill the background of the image.

For this, I'd use Graphics2D#setColor and Graphics2D#fillRect, only because it works.

So, you'd end up with a modified version of the above which might look something like...

img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
g2d = img.createGraphics();
//...
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, img.getWidth(), img.getHeight());
g2d.setColor(Color.BLACK);
g2d.drawString(text, 0, fm.getAscent());
g2d.dispose();
try {
    ImageIO.write(img, "png", new File("Text.png"));
} catch (IOException ex) {
    ex.printStackTrace();
}

If I change to "jpg" then I get orange/pink text on black background on both IE and Chrome

Well, this is related to a well known, and sadly, common issue/bug in ImageIO, which attempts to apply the alpha channel of transparent color models to the JPG, which doesn't support alpha channels.

See Issue using ImageIO.write jpg file: pink background for more details.

But the basic solution is to either use PNG, which supports alpha channels, or to use a non-transparent image.

So, the long and short of all this is. The problem is NOT with the original answer, nor is it with ImageIO, BufferedImage, Graphics, the AWT library, Chrome or IE, but with your lack of understanding of how these APIs (and the example) works.

MadProgrammer
  • 323,026
  • 21
  • 204
  • 329
  • Thanks for the example and final link resource, much appreciated – jarandaf Sep 14 '13 at 11:35
  • 1
    Any clues on how to handle multi-line strings to fit fixed areas? – jarandaf Sep 24 '13 at 12:36
  • 3
    [Drawing MultiPle ones of text](http://docs.oracle.com/javase/tutorial/2d/text/drawmulstring.html) – MadProgrammer Sep 24 '13 at 19:59
  • does not work with Multi-Lines :i.e : String text="Hello \n word" – Abdennour TOUMI Dec 25 '13 at 15:18
  • @AbdennourToumi Take a look at [Working with Text APIs](http://docs.oracle.com/javase/tutorial/2d/text/index.html) which discuses how to draw multi lines of text using the Graphics 2D API – MadProgrammer Dec 25 '13 at 20:28
  • For jpg it gives green background. – Patriotic Aug 25 '16 at 17:07
  • 2
    @Patriotic This is known issue with ImageIO and alpha based images, instead use TYPE_INT_RGB instead (remove the alpha support) – MadProgrammer Aug 25 '16 at 20:07
  • @simbo1905 Then I would suggest that’s an issue with chrome as this generates standard png files. Have you tried using jog instead? – MadProgrammer Nov 06 '18 at 18:37
  • @simbo1905 `ImageIO` has a bug when you try and save JPGs with an alpha channel. What resolution was the image? Since JavaFX uses the same pipeline, I'm not sure how this would be an issue – MadProgrammer Nov 06 '18 at 20:39
  • @simbo1905 I did a test with a 130k PNG image (using the code above, using Java 10) and it renders find in Chrome 70.0.3538.77 under MacOS – MadProgrammer Nov 06 '18 at 20:43
  • @simbo1905 Setting the background won't do anything, it fact as a general rule, I avoid it and simply use `setColor`. But then you'd need to actually fill the image. Instead, you'd need to set the actual HTML page's background color itself. Also, as I said, `ImageIO` has a bug in it that will incorrectly modify the color patellate of a JPG if you try and save it with an alpha channel. This is known and common issue - you'd need to change the `BufferedImage`'s type to something like `TYPE_INT_RGB` - but you still end up with black on black, unless you fill the image yourself. – MadProgrammer Nov 06 '18 at 21:35
  • @simbo1905 Right now, this isn't an issue with either `ImageIO`, AWT or Chrome, but your lack of understanding of how the API works – MadProgrammer Nov 06 '18 at 21:36
  • @simbo1905 I want to point out that the intention of the this code is to generate an image with a transparent background. You will want to have a look at https://stackoverflow.com/questions/4386446/issue-using-imageio-write-jpg-file-pink-background to better understand the issue of JPEG and alpha channels – MadProgrammer Nov 06 '18 at 21:44
  • @simbo1905 I've added another update to outline your misunderstanding and some possible solutions – MadProgrammer Nov 06 '18 at 22:07
  • @MadProgrammer Thanks! I have deleted my comment as they are now all in the answer. My code now generates 90k images fine .Thanks again! – simbo1905 Nov 07 '18 at 10:31
  • How draw text to picture without top and bottom indents? – ilw Oct 14 '20 at 20:47
  • 1
    @ilw You'd need to know the size of text - for [example](https://stackoverflow.com/questions/23729944/java-how-to-visually-center-a-specific-string-not-just-a-font-in-a-rectangle/23730104#23730104) and [example](https://stackoverflow.com/questions/34690321/java-graphics-random-text/34691463#34691463) – MadProgrammer Oct 14 '20 at 23:11
  • @MadProgrammer, so `int height = fm.getAscent() - fm.getDescent() + fm.getLeading();` ? – ilw Oct 15 '20 at 09:30
  • @ilw I was thinking something more like `fm.getStringBounds(word, g2d);` – MadProgrammer Oct 15 '20 at 10:26
7

Without any external libraries, do the following:

  1. Measure the text size in pixels (see Measuring Text)
  2. Create a java.awt.image.BufferedImage in the right size for the text
  3. Acquire the graphics object for the BufferedImage using the createGraphics() method
  4. Draw the text
  5. Save the image using the javax ImageIO class

Edit - fixed the link

Barak Itkin
  • 4,379
  • 19
  • 26
2

Consider the following snippet:

public static final HashMap<RenderingHints.Key, Object> RenderingProperties = new HashMap<>();

static{
    RenderingProperties.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    RenderingProperties.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
    RenderingProperties.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}

public static BufferedImage textToImage(String Text, Font f, float Size){
    //Derives font to new specified size, can be removed if not necessary.
    f = f.deriveFont(Size);

    FontRenderContext frc = new FontRenderContext(null, true, true);

    //Calculate size of buffered image.
    LineMetrics lm = f.getLineMetrics(Text, frc);

    Rectangle2D r2d = f.getStringBounds(Text, frc);

    BufferedImage img = new BufferedImage((int)Math.ceil(r2d.getWidth()), (int)Math.ceil(r2d.getHeight()), BufferedImage.TYPE_INT_ARGB);

    Graphics2D g2d = img.createGraphics();

    g2d.setRenderingHints(RenderingProperties);

    g2d.setBackground(Color.WHITE);
    g2d.setColor(Color.BLACK);

    g2d.clearRect(0, 0, img.getWidth(), img.getHeight());

    g2d.setFont(f);

    g2d.drawString(Text, 0, lm.getAscent());

    g2d.dispose();

    return img;
}

Uses only the java Graphics API to create a image based on a font rendered onto a bufferedimage.

initramfs
  • 7,625
  • 2
  • 32
  • 56
1

Here is a simple Program to write Graphics contents to png format.

import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;
import java.io.File;
import javax.imageio.ImageIO;

class ImageWriteEx extends JPanel{

    public void paint(Graphics g){

        Image img = createImageWithText();
        g.drawImage(img, 20, 20, this);

    }

    private static BufferedImage createImageWithText(){ 

        BufferedImage bufferedImage = new BufferedImage(200, 200, BufferedImage.TYPE_INT_RGB);
        Graphics g = bufferedImage.getGraphics();

        g.drawString("www.stackoverflow.com", 20, 20);
        g.drawString("www.google.com", 20, 40);
        g.drawString("www.facebook.com", 20, 60);
        g.drawString("www.youtube.com", 20, 80);
        g.drawString("www.oracle.com", 20, 1000);

        return bufferedImage;

    }

    public static void main(String[] args){

        try{
            BufferedImage bi = createImageWithText();
            File outputfile = new File("save.png");
            ImageIO.write(bi, "png", outputfile);
        } catch(Exception e){
            e.printStackTrace();
        }

        JFrame frame = new JFrame();
        frame.getContentPane().add(new ImageWriteEx());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(300,300);
        frame.setVisible(true);

    }

}
Martin Carpenter
  • 5,743
  • 26
  • 32
smamran
  • 603
  • 1
  • 14
  • 18
1

In case someone wants TextImages with several lines. I made some and displayed them with

new ImageIcon(*here the image*)

in JOptionPane (without adding text). That fills the whole JOptionPane nicely. Here 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.image.BufferedImage;

public class TextImage
{
   public static BufferedImage make(String...textrows)
   {
      BufferedImage helperImg = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
      Graphics2D g2d = helperImg.createGraphics();
      Font font = *here some font*;
      g2d.setFont(font);
      FontMetrics fm = g2d.getFontMetrics();
      String longestText = "";
      for(String row: textrows)
      {
         if(row.length()>longestText.length())
         {
            longestText = row;
         }
      }
      int width = fm.stringWidth(longestText);
      int height = fm.getHeight()*textrows.length;
      g2d.dispose();


      BufferedImage finalImg = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
      g2d = finalImg.createGraphics();
      g2d.setColor(*here some Color*);
      g2d.fillRect(0, 0, width, height);
      g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
      g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
      g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
      g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
      g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
  g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
      g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
      g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
      g2d.setFont(font);
      fm = g2d.getFontMetrics();
      g2d.setColor(Color.BLACK);
      int y = fm.getAscent();
      for(String row: textrows)
      {
         g2d.drawString(row, 0, y);
         y += fm.getHeight();
      }
      g2d.dispose();
      return finalImg;
   }
}
gute Fee
  • 85
  • 2
  • 15
1

demo# For multi line texts #

Pass the file as argument to the program

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;

public class TextToGraphics {
    
    public static void main(String[] args){
        System.out.println(args[0]);
        File file = new File(args[0]);
        try{
        BufferedReader br = new BufferedReader(new FileReader(file));
        StringBuilder sb = new StringBuilder();
        String line;
        while((line = br.readLine())!=null){

            sb.append(line).append("\n");
        }
        convert(sb.toString(),args[0]+"_img");
        System.out.println("Done.");
    }
    catch(FileNotFoundException e){
        e.printStackTrace();
    }

    }

    public static void convert(String text, String img_name) {
        String[] text_array = text.split("[\n]");
        BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = img.createGraphics();
        Font font = new Font("Consolas", Font.BOLD, 12);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();
        int width = fm.stringWidth(getLongestLine(text_array));
        int lines = getLineCount(text);
        int height = fm.getHeight() * (lines + 4);
        g2d.dispose();
        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
        g2d.setFont(font);
        fm = g2d.getFontMetrics();
        g2d.setColor(Color.BLACK);

        for (int i = 1; i <= lines; ++i) {
            g2d.drawString(text_array[i - 1], 0, fm.getAscent() * i);
        }
        g2d.dispose();
        try {
            String img_path = System.getProperty("user.dir") + "/" + img_name + ".png";
            ImageIO.write(img, "png", new File(img_path));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static int getLineCount(String text) {
        return text.split("[\n]").length;
    }

    private static String getLongestLine(String[] arr) {
        String max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (max.length() < arr[i].length()) {
                max = arr[i];
            }
        }
        return max;
    }
}