用于将二维码图片(或任何其他图片)叠加在底图上的指定位置,并在二维码下方添加可调整位置的编号文本,最终合成一张新图片。我们将创建一个可复用的工具类,并详细解释每个步骤和依赖。
1. 实现思路
-
加载底图:读取作为背景的底图文件。
-
加载覆盖图:读取需要叠加的二维码图片文件。
-
计算覆盖位置 :确定二维码图片在底图上的起始坐标
(x, y)。 -
创建合成画布:创建一个与底图尺寸相同的空白图像作为画布。
-
绘制底图:将底图绘制到画布上。
-
绘制覆盖图 :在计算好的位置
(x, y)将二维码图片绘制到画布上。 -
绘制编号文本:
-
计算文本绘制的位置(相对于二维码图片的位置)。
-
设置文本字体、颜色、大小。
-
将文本绘制到画布上(二维码图片下方)。
-
-
保存合成图:将绘制完成的画布保存为新的图片文件。
-
封装工具类:将上述逻辑封装在一个易于使用的工具类中,允许传入位置参数、编号文本、字体样式等。
2. 完整工具类代码:ImageOverlayUtil.java
java
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
/**
* 图片叠加工具类
* 功能:将一张图片(如二维码)叠加到另一张底图的指定位置,并在叠加图下方添加可定位的编号文本。
*/
public class ImageOverlayUtil {
/**
* 合成图片并添加编号文本
*
* @param baseImagePath 底图文件路径
* @param overlayImagePath 要叠加的图片文件路径 (如二维码)
* @param outputImagePath 合成后图片的输出路径
* @param overlayX 叠加图片在底图上的左上角 X 坐标
* @param overlayY 叠加图片在底图上的左上角 Y 坐标
* @param serialNumber 要显示的编号文本 (如 "SN:12345")
* @param textX 文本相对于叠加图片左上角的 X 偏移量 (用于精确定位文本)
* @param textY 文本相对于叠加图片左上角的 Y 偏移量 (通常为叠加图高度 + 间距)
* @param fontSize 文本字体大小
* @param fontName 文本字体名称 (如 "SansSerif", "Serif")
* @param fontStyle 文本字体样式 (Font.PLAIN, Font.BOLD, Font.ITALIC)
* @param textColor 文本颜色 (如 Color.BLACK)
* @throws IOException 如果图片加载或保存失败
*/
public static void createCompositeImage(
String baseImagePath,
String overlayImagePath,
String outputImagePath,
int overlayX,
int overlayY,
String serialNumber,
int textX,
int textY,
int fontSize,
String fontName,
int fontStyle,
Color textColor) throws IOException {
// 1. 加载底图和叠加图
BufferedImage baseImage = ImageIO.read(new File(baseImagePath));
BufferedImage overlayImage = ImageIO.read(new File(overlayImagePath));
// 2. 创建与底图尺寸相同的画布 (使用底图的类型)
BufferedImage combined = new BufferedImage(
baseImage.getWidth(),
baseImage.getHeight(),
BufferedImage.TYPE_INT_ARGB); // 使用 ARGB 支持透明背景
// 3. 获取画布的 Graphics2D 对象 (用于绘制)
Graphics2D g2d = combined.createGraphics();
try {
// 4. 设置合成选项 (启用抗锯齿和文本抗锯齿)
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 5. 将底图绘制到画布上 (覆盖整个画布)
g2d.drawImage(baseImage, 0, 0, null);
// 6. 将二维码图片绘制到画布上的指定位置
g2d.drawImage(overlayImage, overlayX, overlayY, null);
// 7. 设置文本字体
Font font = new Font(fontName, fontStyle, fontSize);
g2d.setFont(font);
g2d.setColor(textColor);
// 8. 计算文本绘制的绝对坐标 (相对于整个画布)
// 文本位置 = 叠加图位置 + 文本偏移量
//int textAbsoluteX = overlayX + textX;
//int textAbsoluteY = overlayY + textY;
// (可选: 居中文本 - 需要计算文本宽度)
// 如果需要将文本在叠加图下方居中显示,可以计算文本宽度并调整 textAbsoluteX
// FontMetrics fontMetrics = g2d.getFontMetrics();
// int textWidth = fontMetrics.stringWidth(serialNumber);
// int centerX = overlayX + (overlayImage.getWidth() - textWidth) / 2;
// g2d.drawString(serialNumber, centerX, textAbsoluteY); // 使用居中坐标
// 9. 绘制文本到画布
//g2d.drawString(serialNumber, textAbsoluteX, textAbsoluteY);
// 8. 计算文本绘制的绝对坐标 (相对于整个画布),并在叠加图下方居中显示文本
FontMetrics fontMetrics = g2d.getFontMetrics();
int textWidth = fontMetrics.stringWidth(serialNumber);
int centerX = overlayX + (overlayImage.getWidth() - textWidth) / 2; // 计算文本的X轴居中位置
int centerY = overlayY + overlayImage.getHeight() + textY; // Y轴位置为叠加图像底部加上指定偏移
// 9. 绘制文本到画布(使用计算出的居中位置)
g2d.drawString(serialNumber, centerX, centerY);
} finally {
// 10. 释放 Graphics2D 资源 (重要!)
g2d.dispose();
}
// 11. 保存合成后的图片到文件
String format = outputImagePath.substring(outputImagePath.lastIndexOf('.') + 1).toLowerCase();
ImageIO.write(combined, format, new File(outputImagePath));
}
/**
* 计算文本在叠加图下方居中时所需的 X 偏移量。
* 辅助方法,用于简化调用。
*
* @param g2d Graphics2D 对象 (需提前设置好字体)
* @param overlayImage 叠加的图片对象
* @param text 要计算的文本
* @return 居中文本所需的 X 偏移量 (相对于叠加图左上角)
*/
private static int calculateCenterTextOffsetX(Graphics2D g2d, BufferedImage overlayImage, String text) {
if (text == null || text.isEmpty()) return 0;
FontMetrics fontMetrics = g2d.getFontMetrics();
int textWidth = fontMetrics.stringWidth(text);
return (overlayImage.getWidth() - textWidth) / 2;
}
}
3. 代码详细解释
-
方法签名 (
createCompositeImage):-
接收所有必要的参数:底图路径、叠加图路径、输出路径、叠加图位置
(overlayX, overlayY)、编号文本serialNumber、文本位置偏移(textX, textY)、字体参数(fontSize, fontName, fontStyle)、文本颜色textColor。 -
textX和textY是相对于叠加图左上角的偏移量。例如,如果textY = overlayImage.getHeight() + 10,则文本将出现在二维码图片下方 10 像素处。textX可以设为 0 表示左对齐,或者用calculateCenterTextOffsetX计算居中。
-
-
加载图片 (
ImageIO.read):- 使用
ImageIO.read(File)加载底图和叠加图到BufferedImage对象中。这是 Java 标准库读取图片的标准方式。
- 使用
-
创建合成画布 (
new BufferedImage(...)):-
创建一个新的
BufferedImage,其宽度和高度与底图相同。 -
BufferedImage.TYPE_INT_ARGB指定使用带 Alpha 通道(透明度)的格式,这对于叠加带透明度的图片(如某些二维码)很有用。如果底图和叠加图都是不透明 JPG,使用TYPE_INT_RGB也可以。
-
-
获取
Graphics2D对象 (combined.createGraphics()):Graphics2D是 Java 2D API 的核心类,用于在图像上执行绘图操作(绘制图片、文本、形状等)。
-
设置渲染提示 (
setRenderingHint):-
KEY_ANTIALIASING和VALUE_ANTIALIAS_ON:开启图形抗锯齿,使叠加图的边缘更平滑。 -
KEY_TEXT_ANTIALIASING和VALUE_TEXT_ANTIALIAS_ON:开启文本抗锯齿,使绘制的文本更清晰。
-
-
绘制底图 (
g2d.drawImage(baseImage, 0, 0, null)):- 将底图绘制到画布的
(0, 0)位置,覆盖整个画布,作为背景。
- 将底图绘制到画布的
-
绘制叠加图 (
g2d.drawImage(overlayImage, overlayX, overlayY, null)):- 在底图上的指定坐标
(overlayX, overlayY)处绘制二维码图片。overlayX和overlayY是相对于底图左上角的位置。
- 在底图上的指定坐标
-
设置文本样式:
-
创建
Font对象指定字体名称、样式(普通、粗体、斜体)和大小。 -
设置
Graphics2D的当前字体和颜色。
-
-
计算文本绝对坐标:
-
文本的绝对坐标 = 叠加图位置
(overlayX, overlayY)+ 文本偏移量(textX, textY)。 -
textX:通常用于水平对齐(0 左对齐,居中需要计算,见辅助方法)。 -
textY:通常设置为overlayImage.getHeight() + verticalSpacing,其中verticalSpacing是希望文本距离二维码底部的像素数(例如 10px)。
-
-
居中文本辅助方法 (
calculateCenterTextOffsetX):-
这是一个可选方法。它计算给定文本在叠加图宽度范围内居中显示时所需的水平偏移量
textX。 -
它使用
FontMetrics.stringWidth(text)获取文本在当前字体下的像素宽度。 -
然后计算
(overlayImageWidth - textWidth) / 2得到居中的偏移量。
-
-
绘制文本 (
g2d.drawString(serialNumber, textAbsoluteX, textAbsoluteY)):- 使用
drawString方法在计算好的绝对坐标处绘制编号文本。
- 使用
-
释放资源 (
g2d.dispose()):- 在
finally块中调用dispose()非常重要。Graphics2D对象使用了系统资源,使用完毕后必须释放,否则可能导致资源泄漏(如 Windows 上的 GDI 句柄耗尽)。
- 在
-
保存合成图 (
ImageIO.write(combined, format, new File(outputImagePath))):-
根据输出文件路径的扩展名(
.png,.jpg)确定图片格式。 -
使用
ImageIO.write将合成后的BufferedImage对象写入到指定文件中。
-
4. 使用示例
假设有以下文件:
-
底图:
/path/to/background.jpg -
二维码图:
/path/to/qrcode.png -
输出:
/path/to/output.png -
希望二维码放在底图的
(100, 200)位置。 -
编号文本
"SN:20240401",放在二维码下方居中位置,距离二维码底部 15 像素。 -
字体:黑体,粗体,18号,黑色。
java
public class Main {
public static void main(String[] args) {
try {
// 定义参数
String baseImage = "/path/to/background.jpg";
String overlayImage = "/path/to/qrcode.png";
String outputImage = "/path/to/output.png";
int qrX = 100; // 二维码 X 坐标
int qrY = 200; // 二维码 Y 坐标
String serial = "SN:20240401";
int textYOffset = 0; // 先设为0,后面计算居中时会调整
int fontSize = 18;
String fontName = "黑体"; // 或 "SimHei"
int fontStyle = Font.BOLD;
Color textColor = Color.BLACK;
// 临时加载叠加图以计算文本居中偏移量 (实际工具类内部处理)
BufferedImage qrCodeImg = ImageIO.read(new File(overlayImage));
// 创建一个临时Graphics2D环境计算文本宽度 (模拟)
BufferedImage tempImg = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D tempG2d = tempImg.createGraphics();
tempG2d.setFont(new Font(fontName, fontStyle, fontSize));
int centerTextX = ImageOverlayUtil.calculateCenterTextOffsetX(tempG2d, qrCodeImg, serial);
tempG2d.dispose();
// 设置文本的 Y 偏移量 = 二维码高度 + 间距 (15px)
int textYOffsetFinal = qrCodeImg.getHeight() + 15;
// 调用工具方法合成图片
ImageOverlayUtil.createCompositeImage(
baseImage,
overlayImage,
outputImage,
qrX,
qrY,
serial,
centerTextX, // 使用计算出的居中偏移量作为 textX
textYOffsetFinal, // 二维码高度 + 15px
fontSize,
fontName,
fontStyle,
textColor);
System.out.println("合成图片成功生成: " + outputImage);
} catch (IOException e) {
e.printStackTrace();
System.err.println("图片合成失败: " + e.getMessage());
}
}
}
解释示例代码:
-
加载二维码图片:用于获取其宽度和高度,以便计算文本位置。
-
创建临时 Graphics2D :为了使用
calculateCenterTextOffsetX方法计算文本居中所需的textX偏移量。注意在实际工具类内部调用时,Graphics2D对象已经创建并设置了字体。 -
计算
textYOffsetFinal:textY偏移量设置为二维码高度qrCodeImg.getHeight()加上想要的间距15。 -
调用
createCompositeImage:传入所有参数,特别是计算好的centerTextX(居中) 和textYOffsetFinal(二维码底部下方15px)。 -
错误处理 :捕获并打印
IOException。
5. 工具类增强点 (实际项目建议)
-
异常处理细化 :工具类目前抛出
IOException。在实际项目中,可以定义更具体的自定义异常(如ImageLoadException,ImageWriteException)。 -
日志记录:添加日志记录(如 SLF4J + Logback)记录操作过程和潜在错误。
-
图片格式支持 :确保
ImageIO支持输入/输出的图片格式(.jpg,.png,.bmp等)。如需更多格式,可能需要使用javax.imageio插件或第三方库(如 TwelveMonkeys ImageIO)。 -
内存优化:
-
处理大图时注意内存消耗。
-
确保
Graphics2D对象在finally块中释放 (dispose())。 -
考虑使用
try-with-resources管理ImageInputStream等资源(但ImageIO.read通常内部处理关闭)。
-
-
文本绘制增强:
-
支持多行文本(需要计算行高和换行)。
-
支持文本边框 (
Stroke) 或背景填充。 -
支持文本旋转。
-
更精确的文本位置计算(考虑基线)。
-
-
叠加图缩放 :如果需要调整二维码大小,可以在绘制前使用
g2d.drawImage(overlayImage, x, y, width, height, null)进行缩放。 -
透明度处理 :如果叠加图有透明度(PNG),
TYPE_INT_ARGB格式能正确显示。底图如果是 PNG 带透明,合成时也需注意。 -
性能:如果需要合成大量图片,考虑性能优化(如对象复用、异步处理)。
-
输入校验:在工具方法开头添加参数校验(非空、文件存在、坐标有效、字体有效等)。
6. 总结
这个 ImageOverlayUtil 工具类提供了一个灵活、可复用的解决方案,用于在 Java 应用程序中合成图片,包括将二维码(或其他图片)叠加到底图的指定位置,并在其下方添加可定位的编号文本。它完全基于 Java 标准库 (java.awt, javax.imageio),无需额外依赖。通过参数化控制位置、文本内容和样式,可以适应各种业务场景。示例代码展示了如何调用该工具类,特别是如何计算文本位置以实现居中显示。
在将其集成到实际项目时,请考虑增强点(如异常处理、日志、性能优化)以适应具体需求和环境。