最近的需求要把用戶設置的不同文案渲染到上傳的圖片上,類似圖片水印的效果,文案渲染的位置也需要系統(tǒng)控制,并且需要能支持一次批量處理 1000 張圖片。首先想到的是讓前端 js 來生成圖片,生成完成后將圖片文件流上傳到圖片服務器,這樣的好處有:
- 它可以實時看到生成的效果,效果不好的時候可以實時的調(diào)整文案內(nèi)容
- 前端的處理圖片框架強大,更善于處理圖片,這樣后端的邏輯就能保持簡單。有沒有感覺甩鍋...
但前端如果要同時處理 1000 張圖片,也夠嗆,用戶不可能等在那里幾分鐘,然后等到瀏覽器崩潰;生成圖片多的時候也不可能去預覽這么多的圖片,故決定采用后端生產(chǎn)圖片的方案。
下面記錄 Java 后端使用 Graphics2D 生成圖片的幾個關鍵點。
- 導入所用的字體
- 計算坐標,drawString 打印文案
- 其他
導入字體
商業(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) 坐標打印的,而不是矩形的左下角和左上角。
對于需求,知道打印字符串 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);
-
打印字符實際寬、高大于矩形的情況
Snip20210715_3.png -
打印字符實際寬、高小于矩形的情況
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