Graphics2D 文字居中打印

最近的需求要把用戶設置的不同文案渲染到上傳的圖片上,類似圖片水印的效果,文案渲染的位置也需要系統(tǒng)控制,并且需要能支持一次批量處理 1000 張圖片。首先想到的是讓前端 js 來生成圖片,生成完成后將圖片文件流上傳到圖片服務器,這樣的好處有:

  1. 它可以實時看到生成的效果,效果不好的時候可以實時的調(diào)整文案內(nèi)容
  2. 前端的處理圖片框架強大,更善于處理圖片,這樣后端的邏輯就能保持簡單。有沒有感覺甩鍋...

但前端如果要同時處理 1000 張圖片,也夠嗆,用戶不可能等在那里幾分鐘,然后等到瀏覽器崩潰;生成圖片多的時候也不可能去預覽這么多的圖片,故決定采用后端生產(chǎn)圖片的方案。
下面記錄 Java 后端使用 Graphics2D 生成圖片的幾個關鍵點。

  1. 導入所用的字體
  2. 計算坐標,drawString 打印文案
  3. 其他

導入字體

商業(yè)上盡量用免費的、好看字體,下面方法可以通過 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);
        }
    }

導入系統(tǒng)文件后,可以通過下面方法找到字體的名稱

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) 坐標和打印區(qū)域的寬、高,那怎么打印可以是字符串剛好打印在指定的矩形中,這里目標是字符串位置在矩形中左右居中、上下居中。

左右、上下居中

給定矩形起點坐標 (rectX, rectY),及矩形的寬、高。在矩形中打印給定字符串,讓字符串相對于矩形左右居中、上下居中。
實現(xiàn)代碼如下:

// 定義圖片的寬、高
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 為矩形的寬、高
// 現(xiàn)在需要在矩形中,居中打印定義的字符
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坐標 = 矩形的起點坐標 + 字符居中打印情況下字符的邊與矩形邊的距離(可能是正,也可能是負數(shù))
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 字體線條是否使用 抗鋸齒功能,當設置的字體過大的時候,會出現(xiàn)鋸齒,如果抗鋸齒,會使繪圖速度變慢
  • strikethrough 刪除線

參考來源

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容