将文本内容转换为图像

34
有没有任何Java库可以将文本内容转换为图像文件?我只知道ImageMagick(在这种情况下是JMagick),但我不想安装任何外部二进制文件(我的应用程序将部署为Tomcat服务器上的.war文件,因此除了Java之外,我不想有任何其他依赖项)。
例如,从字符串“Hello”中,我想生成这个简单的图像:

Basic image from string "hello"


还可以参考以下使用GlyphVector的示例,如文本形状图像Unicode棋盘。以及使用标签包装文本的示例,如此处 - Andrew Thompson
6个回答

78

Graphics 2D API应该能够实现您所需的功能。它还具有一些复杂的文本处理能力。

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();
        }

    }

}

请查看编写/保存图像

警告:我使用这个工具生成了9万个PNG图像,结果发现它们可以在IE中查看,但无法在Chrome版本70.0.3538.77中查看。

上述代码对我来说运行正常(我将文本颜色更改为WHITE,以便在Chrome中查看)。

Running in Chrome

我在Mac OS Mojave 10.14上使用Chrome 70.0.3538.77和Java 10.0.2。生成的图像大小为4778x2411像素...

更新...

在IE浏览器中是黑底白字,但在Chrome浏览器中是黑底黑字。尽管我将背景设置为白色。

所以你告诉我,在不同的浏览器上会以不同的方式显示透明PNG,因为浏览器使用不同的默认背景...你为什么会感到惊讶呢?

最初的解决方案故意使用了基于透明度的图像。这可以通过创建图像时使用BufferedImage.TYPE_INT_ARGB来证明,它应用了基于Alpha(A)的RGB颜色模型。

这是出乎意料的,因为有g2d.setBackground(Color.white)。

实际上,如果您理解setBackground的实际作用及其用法,这完全是预期的。

来自JavaDocs的说明:

设置Graphics2D上下文的背景颜色。背景颜色用于清除区域。当为组件构建Graphics2D时,背景颜色从组件继承。在Graphics2D上下文中设置背景颜色仅影响随后的clearRect调用,而不影响组件的背景颜色。要更改组件的背景,请使用组件的适当方法。
从“声音”的东西来看,您想要一个非透明图像,具有填充的背景颜色。因此,再次访问JavaDocs并阅读一些内容,将会引导您到BufferedImage.TYPE_INT_RGB,它删除了Alpha通道,但您仍然必须填充图像的背景。
为此,我会使用Graphics2D#setColor和Graphics2D#fillRect,只是因为它有效。
因此,您最终会得到以上内容的修改版本,可能看起来像...
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();
}

如果我改成“jpg”,那么在IE和Chrome上都会得到黑色背景上的橙色/粉红色文本。嗯,这与ImageIO中已知且常见的问题/错误有关,它试图将透明颜色模型的alpha通道应用于不支持alpha通道的JPG格式。更多细节请参见Issue using ImageIO.write jpg file: pink background。但基本解决方案是要么使用支持alpha通道的PNG格式,要么使用非透明图像。因此,所有这些的长短是:问题不在于原始答案,也不在于ImageIO、BufferedImage、Graphics、AWT库、Chrome或IE,而在于您对这些API(以及示例)如何工作的理解不足。

感谢提供的示例和最终链接资源,非常感激。 - jarandaf
1
有关如何处理多行字符串以适应固定区域的任何提示吗? - jarandaf
3
绘制多个文本的方法本文介绍了如何使用Java 2D API在屏幕上绘制多个文本。有时候,您可能需要将一些文本放置到相同区域中。在这种情况下,您可以使用drawString()方法。然而,如果您想要在同一区域中显示多个字符串,则可以使用drawGlyphVector()方法。drawGlyphVector()方法是基于字形的,它允许您指定要绘制的字符串、其位置和颜色。您还可以设置渲染提示,以便优化性能。此外,drawGlyphVector()方法还支持在形状路径上绘制文本,从而实现更高级的文本效果。在使用drawGlyphVector()方法之前,您需要创建一个GlyphVector对象。该对象包含了要绘制的字符串及其格式信息。一旦创建了GlyphVector对象,您就可以使用Graphics2D对象的drawGlyphVector()方法将其绘制出来。下面是一个示例代码,演示了如何使用drawGlyphVector()方法绘制多个字符串:Font font = new Font("Serif", Font.PLAIN, 48); FontRenderContext frc = g2.getFontRenderContext(); GlyphVector gv = font.createGlyphVector(frc, "JavaFX"); int x = 100; int y = 100; for (int i = 0; i < gv.getNumGlyphs(); i++) { Shape glyph = gv.getGlyphOutline(i); Rectangle bounds = glyph.getBounds(); g2.translate(x - bounds.x, y - bounds.y); g2.fill(glyph); g2.translate(bounds.x - x, bounds.y - y); x += bounds.width; }上述代码使用了Serif字体,创建了一个包含字符串“JavaFX”的GlyphVector对象,并在屏幕上绘制出来。通过循环处理GlyphVector中的每个字形,我们将其填充到Graphics2D对象中。希望这篇文章对您有所帮助! - MadProgrammer
2
@Patriotic 这是ImageIO和基于alpha的图像的已知问题,建议使用TYPE_INT_RGB代替(移除alpha支持)。 - MadProgrammer
1
@ilw 你需要知道文本的大小 - 例如:(https://dev59.com/M2Ag5IYBdhLWcg3wU5m8#23730104)和(https://dev59.com/4JLea4cB1Zd3GeqP7dZw#34691463) - MadProgrammer
显示剩余14条评论

9

不需要任何外部库,完成以下操作:

  1. 以像素为单位测量文本大小(参见测量文本
  2. 创建正确大小的java.awt.image.BufferedImage
  3. 使用createGraphics()方法获取BufferedImage的图形对象
  4. 绘制文本
  5. 使用javax ImageIO类保存图像

编辑-修复链接


3
考虑以下代码片段:

考虑以下代码片段:

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;
}

使用Java Graphics API,基于渲染到BufferedImage上的字体创建图像。


2

演示# 对于多行文本 #

将文件作为参数传递给程序

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;
    }
}

1
这是一个简单的程序,用于将图形内容写入 png 格式。
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);

    }

}

什么是 ImageWrite - yshahak
应该是 ImageWriteEx(刚刚定义的类)。我已经修正了。 - Martin Carpenter

1

如果有人想要多行文本图像,我制作了一些并用以下方式显示:

new ImageIcon(*here the image*)

在JOptionPane中(不添加文本),它会很好地填充整个JOptionPane。这里是代码:
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;
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接