在 Java 開發中,動態生成帶有文字浮水印或合成多張圖片的需求非常常見。本文將透過完整的程式碼範例,示範如何使用 java.awt 套件中的 Graphics2D 和 BufferedImage 類別,實現「在圖片上寫字」以及「兩張圖片合成」的功能。
我們封裝一個 PicProcessor 類別,統一處理圖片載入、文字繪製、圖片合成與輸出儲存等操作,提高程式碼的復用性與可維護性。
/**
* 從本地檔案系統載入圖片至緩衝區
*/
public BufferedImage loadImageLocal(String imgPath) {
try {
return ImageIO.read(new File(imgPath));
} catch (IOException e) {
System.err.println("本地圖片載入失敗: " + e.getMessage());
}
return null;
}
/**
* 從網路 URL 載入圖片至緩衝區
*/
public BufferedImage loadImageUrl(String imgUrl) {
try {
URL url = new URL(imgUrl);
return ImageIO.read(url);
} catch (IOException e) {
System.err.println("網路圖片載入失敗: " + e.getMessage());
}
return null;
}
private Font font = new Font("微軟正黑體", Font.PLAIN, 36);
private Color fontColor = Color.ORANGE;
private int fontSize = 36;
/**
* 設定繪製文字的字體樣式、大小與顏色
*/
public void setFontStyle(String fontName, int fontSize, Color color) {
this.fontSize = fontSize;
this.font = new Font(fontName, Font.PLAIN, fontSize);
this.fontColor = color;
}
/**
* 在圖片上繪製單行文字,若座標超出範圍則自動調整至右下角
*/
public BufferedImage drawText(BufferedImage img, String text, int x, int y) {
if (img == null || text == null) return img;
Graphics2D g = img.createGraphics();
try {
// 開啟抗鋸齒,提升文字渲染品質
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setFont(font);
g.setColor(fontColor);
int imgW = img.getWidth();
int imgH = img.getHeight();
// 座標邊界檢查與修正
if (x >= imgW || y >= imgH) {
x = imgW - fontSize * text.length() - 10;
y = imgH - fontSize - 5;
}
// 確保座標不為負
x = Math.max(0, x);
y = Math.max(fontSize, y);
g.drawString(text, x, y);
} finally {
g.dispose(); // 釋放圖形資源
}
return img;
}
/**
* 繪製多段文字
* @param horizontal true: 橫向排列(同一行);false: 縱向換行
*/
public BufferedImage drawTextLines(BufferedImage img, String[] texts, int x, int y, boolean horizontal) {
if (img == null || texts == null) return img;
Graphics2D g = img.createGraphics();
try {
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setFont(font);
g.setColor(Color.RED); // 多行文字預設紅色
int imgW = img.getWidth();
int imgH = img.getHeight();
if (x >= imgW || y >= imgH) {
x = 10;
y = fontSize + 10;
}
for (String line : texts) {
if (line == null) continue;
g.drawString(line, x, y);
if (horizontal) {
// 橫向:x 軸位移(估算字寬)
x += (int)(line.length() * fontSize * 0.6) + 5;
} else {
// 縱向:y 軸位移
y += fontSize + 5;
}
}
} finally {
g.dispose();
}
return img;
}
/**
* 將前景圖合成到背景圖指定位置(支援透明度)
*/
public BufferedImage overlayImage(BufferedImage background, BufferedImage foreground, int x, int y, float alpha) {
if (background == null || foreground == null) return background;
Graphics2D g = background.createGraphics();
try {
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
g.drawImage(foreground, x, y, null);
} finally {
g.dispose();
}
return background;
}
/**
* 將兩張圖片水平並排合成新圖片
*/
public BufferedImage mergeHorizontal(BufferedImage left, BufferedImage right, int gap) {
if (left == null) return right;
if (right == null) return left;
int newW = left.getWidth() + right.getWidth() + gap;
int newH = Math.max(left.getHeight(), right.getHeight());
BufferedImage combined = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = combined.createGraphics();
try {
g.setColor(Color.WHITE);
g.fillRect(0, 0, newW, newH);
g.drawImage(left, 0, 0, null);
g.drawImage(right, left.getWidth() + gap, 0, null);
} finally {
g.dispose();
}
return combined;
}
/**
* 將 BufferedImage 寫入本地檔案(支援 jpg/png)
*/
public boolean saveImage(BufferedImage img, String outputPath, String format) {
if (img == null || outputPath == null) return false;
try {
File outFile = new File(outputPath);
// 確保父目錄存在
outFile.getParentFile().mkdirs();
return ImageIO.write(img, format, outFile);
} catch (IOException e) {
System.err.println("圖片儲存失敗: " + e.getMessage());
return false;
}
}
public static void main(String[] args) {
PicProcessor processor = new PicProcessor();
// 1. 載入背景圖
BufferedImage bg = processor.loadImageLocal("input/background.jpg");
// 2. 設定字體
processor.setFontStyle("標楷體", 48, Color.WHITE);
// 3. 繪製標題文字
bg = processor.drawText(bg, "Java 圖片處理範例", 50, 100);
// 4. 繪製多行說明文字
String[] lines = {"功能一:文字浮水印", "功能二:多圖合成", "功能三:自訂字體顏色"};
bg = processor.drawTextLines(bg, lines, 50, 160, false);
// 5. 載入 Logo 並疊加到右下角
BufferedImage logo = processor.loadImageLocal("input/logo.png");
if (logo != null) {
int logoX = bg.getWidth() - logo.getWidth() - 20;
int logoY = bg.getHeight() - logo.getHeight() - 20;
bg = processor.overlayImage(bg, logo, logoX, logoY, 0.9f);
}
// 6. 儲存結果
processor.saveImage(bg, "output/result.jpg", "jpg");
System.out.println("圖片生成完成!");
}
KEY_TEXT_ANTIALIASING) 可大幅提升文字顯示品質。Graphics2D 使用後務必呼叫 dispose() 釋放原生資源,避免記憶體洩漏。| 問題 | 原因 | 解決方法 |
|---|---|---|
| 中文顯示為方塊/亂碼 | 系統缺少指定字體 | 使用系統確定存在的字體(如「微軟正黑體」、「標楷體」),或嵌入 TTF 字體檔 |
| 文字位置不正確 | 座標系誤解(y 軸向下) | y 座標為基線位置,實際顯示需加上字體大小;使用 FontMetrics 精確計算 |
| 合成後圖片有黑色背景 | 目標圖片類型不支援透明 | 建立新圖片時使用 BufferedImage.TYPE_INT_ARGB |
| OutOfMemoryError | 圖片尺寸過大或未釋放資源 | 分塊處理大圖、及時 dispose()、調大 JVM 堆記憶體 |
此基礎框架可輕鬆擴展為:
完整原始碼已上傳至 GitHub 專案,歡迎參考與貢獻。