Graphics2D 文字居中打印

最近的需求要把用户设置的不同文案渲染到上传的图片上,类似图片水印的效果,文案渲染的位置也需要系统控制,并且需要能支持一次批量处理 1000 张图片。首先想到的是让前端 js 来生成图片,生成完成后将图片文件流上传到图片服务器,这样的好处有:

  1. 它可以实时看到生成的效果,效果不好的时候可以实时的调整文案内容
  2. 前端的处理图片框架强大,更善于处理图片,这样后端的逻辑就能保持简单。有没有感觉甩锅...

但前端如果要同时处理 1000 张图片,也够呛,用户不可能等在那里几分钟,然后等到浏览器崩溃;生成图片多的时候也不可能去预览这么多的图片,故决定采用后端生产图片的方案。
下面记录 Java 后端使用 Graphics2D 生成图片的几个关键点。

  1. 导入所用的字体
  2. 计算坐标,drawString 打印文案
  3. 其他

导入字体

商业上尽量用免费的、好看字体,下面方法可以通过 ttf 导入字体文件,并使用

public static void importAlibabaFont() {
        try {
            GraphicsEnvironment ge =
                GraphicsEnvironment.getLocalGraphicsEnvironment();

            // 字体文件,放在 resources 目录的 fonts 文件下
            ge.registerFont(Font.createFont(Font.TRUETYPE_FONT,
                WaterMarkUtils222.class.getResourceAsStream("/fonts/Alibaba-PuHuiTi-B.ttf")
            ));
        } catch (IOException e) {
            log.error("导入字体异常: {}", e);
        } catch (FontFormatException e) {
            log.error("字体格式异常: {}", e);
        }
    }

导入系统文件后,可以通过下面方法找到字体的名称

Font[] fonts = GraphicsEnvironment .getLocalGraphicsEnvironment().getAllFonts();
for (Font f : fonts) {
    System.out.println(f.getFontName());
}

这里的字体名称就是在初始化字体对象时传入的字体名称。

// fontName 就是前面打印出来的字体名称
Font font1 = new Font(fontName, Font.BOLD, 30);

计算坐标,打印文案

首先要了解图片坐标原点 (0, 0) 在左上角顶点, x 轴向右逐渐增大,y 轴向下逐渐增加。
然后是理解 Graphics2D 对象的 drawString(AttributedCharacterIterator iterator, int x, int y)中的 x, y 坐标是文字打印基线与 x 轴的交点。
如下图,打印的时候是从 (x, y) 坐标打印的,而不是矩形的左下角和左上角。

graphics2D-基线.png

对于需求,知道打印字符串 string 的 (x, y) 坐标和打印区域的宽、高,那怎么打印可以是字符串刚好打印在指定的矩形中,这里目标是字符串位置在矩形中左右居中、上下居中。

左右、上下居中

给定矩形起点坐标 (rectX, rectY),及矩形的宽、高。在矩形中打印给定字符串,让字符串相对于矩形左右居中、上下居中。
实现代码如下:

// 定义图片的宽、高
int imgWidth = 800;
int imgHeight = 800;
BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_BGR);
Graphics2D g2d = bufferedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setBackground(Color.WHITE);
// 将图片用白色填充
g2d.clearRect(0, 0, imgWidth, imgHeight);

// rectX, rectY 为打印文字矩形的起点,rectWidth, rectHeight 为矩形的宽、高
// 现在需要在矩形中,居中打印定义的字符
int rectX = 100;
int rectY = 200;
int rectWidth = 400;
int rectHeight = 80;
g2d.setColor(Color.BLACK);
// 在图片上画出矩形图,便于观察啊
g2d.drawRect(rectX, rectY, rectWidth, rectHeight);

String string1 = "Big Big 树";
// 设置打印的字体
Font font1 = new Font("Serif", Font.BOLD, 120);
g2d.setFont(font1);
FontMetrics metrics = g2d.getFontMetrics(font1);

// 字符打印实际需要的宽度,可能小于矩形的宽,也可能大于矩形的宽
int stringWidth = metrics.stringWidth(string1);
// 字符打印的x坐标 = 矩形的起点坐标 + 字符居中打印情况下字符的边与矩形边的距离(可能是正,也可能是负数)
int x = rectX + (rectWidth-stringWidth)/2;
// 字符打印的y坐标(基线的y坐标)= 矩形的起点y坐标 + 字符居中打印下字符的上面与矩形上面距离 + 字符上面到基线的距离
int y = rectY + (rectHeight-metrics.getHeight())/2 + metrics.getAscent();
g2d.drawString(string1, x, y);

g2d.setColor(Color.RED);
// 画出基线
g2d.drawLine(x, y, x+metrics.stringWidth(string1), y);

g2d.dispose();

// 输出图片
String path = DrawString.class.getResource("/images").getPath();
File file = new File(path+"/result.png");
if (!file.exists()) {
    file.createNewFile();
}
ImageIO.write(bufferedImage, "PNG", file);
  1. 打印字符实际宽、高大于矩形的情况


    Snip20210715_3.png
  2. 打印字符实际宽、高小于矩形的情况


    Snip20210715_4.png

其他

  • antiAlias 字体线条是否使用 抗锯齿功能,当设置的字体过大的时候,会出现锯齿,如果抗锯齿,会使绘图速度变慢
  • strikethrough 删除线

参考来源

https://www.coder.work/article/6608148
https://www.docs4dev.com/docs/zh/java/java8/tutorials/2d-text-index.html

你可能感兴趣的:(Graphics2D 文字居中打印)