You've already forked FrameTour-BE
feat(image): 添加水印缩放功能支持
- 在 WatermarkConfig 中新增 scale 字段用于控制整体缩放倍数 - 在 WatermarkStage 中读取并传递 scale 参数到 WatermarkInfo - 在 PrinterDefaultWatermarkOperator 中实现所有位置和尺寸的缩放逻辑 - 对偏移量、边距、字体大小、二维码尺寸等应用缩放因子 - 更新图像绘制相关参数计算方式以支持动态缩放 - 优化二维码圆形背景和头像绘制的缩放处理 - 确保缩放后的水印元素保持相对位置和视觉一致性
This commit is contained in:
@@ -34,4 +34,11 @@ public class WatermarkConfig {
|
|||||||
* 二维码文件
|
* 二维码文件
|
||||||
*/
|
*/
|
||||||
private final File qrcodeFile;
|
private final File qrcodeFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩放倍数,用于将所有定位和大小乘以该倍数
|
||||||
|
* 默认值为 1.0(不缩放)
|
||||||
|
*/
|
||||||
|
@Builder.Default
|
||||||
|
private final Double scale = 1.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,6 +170,12 @@ public class WatermarkStage extends AbstractPipelineStage<PhotoProcessContext> {
|
|||||||
info.setQrcodeFile(qrcodeFile);
|
info.setQrcodeFile(qrcodeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从 config 读取缩放倍数
|
||||||
|
Double scale = config.getScale();
|
||||||
|
if (scale != null) {
|
||||||
|
info.setScale(scale);
|
||||||
|
}
|
||||||
|
|
||||||
// 根据旋转状态自己处理 offsetLeft
|
// 根据旋转状态自己处理 offsetLeft
|
||||||
if (context.isRotationApplied()) {
|
if (context.isRotationApplied()) {
|
||||||
if (context.getImageRotation() == 90) {
|
if (context.getImageRotation() == 90) {
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ public class WatermarkInfo {
|
|||||||
private Integer offsetLeft;
|
private Integer offsetLeft;
|
||||||
private Integer offsetRight;
|
private Integer offsetRight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缩放倍数,用于将所有定位和大小乘以该倍数
|
||||||
|
* 例如: scale=2.0 表示所有尺寸和位置都放大2倍
|
||||||
|
* null 表示使用默认值1.0(不缩放)
|
||||||
|
*/
|
||||||
|
private Double scale;
|
||||||
|
|
||||||
public String getDatetimeLine() {
|
public String getDatetimeLine() {
|
||||||
if (datetimeLine == null) {
|
if (datetimeLine == null) {
|
||||||
datetimeLine = DateUtil.format(datetime, dtFormat);
|
datetimeLine = DateUtil.format(datetime, dtFormat);
|
||||||
|
|||||||
@@ -64,11 +64,14 @@ public class PrinterDefaultWatermarkOperator implements IOperator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File process(WatermarkInfo info) throws ImageWatermarkException {
|
public File process(WatermarkInfo info) throws ImageWatermarkException {
|
||||||
// 获取四边偏移值,优先使用传入的值,否则使用默认值
|
// 获取缩放倍数,默认为1.0(不缩放)
|
||||||
int offsetTop = info.getOffsetTop() != null ? info.getOffsetTop() : DEFAULT_OFFSET_TOP;
|
double scale = info.getScale() != null ? info.getScale() : 1.0;
|
||||||
int offsetBottom = info.getOffsetBottom() != null ? info.getOffsetBottom() : DEFAULT_OFFSET_BOTTOM;
|
|
||||||
int offsetLeft = info.getOffsetLeft() != null ? info.getOffsetLeft() : DEFAULT_OFFSET_LEFT;
|
// 获取四边偏移值,优先使用传入的值,否则使用默认值,并应用缩放
|
||||||
int offsetRight = info.getOffsetRight() != null ? info.getOffsetRight() : DEFAULT_OFFSET_RIGHT;
|
int offsetTop = (int) ((info.getOffsetTop() != null ? info.getOffsetTop() : DEFAULT_OFFSET_TOP) * scale);
|
||||||
|
int offsetBottom = (int) ((info.getOffsetBottom() != null ? info.getOffsetBottom() : DEFAULT_OFFSET_BOTTOM) * scale);
|
||||||
|
int offsetLeft = (int) ((info.getOffsetLeft() != null ? info.getOffsetLeft() : DEFAULT_OFFSET_LEFT) * scale);
|
||||||
|
int offsetRight = (int) ((info.getOffsetRight() != null ? info.getOffsetRight() : DEFAULT_OFFSET_RIGHT) * scale);
|
||||||
|
|
||||||
BufferedImage baseImage;
|
BufferedImage baseImage;
|
||||||
BufferedImage qrcodeImage;
|
BufferedImage qrcodeImage;
|
||||||
@@ -86,17 +89,26 @@ public class PrinterDefaultWatermarkOperator implements IOperator {
|
|||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ImageWatermarkException("图片打开失败");
|
throw new ImageWatermarkException("图片打开失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 应用缩放到所有常量
|
||||||
|
int scaledExtraBorder = (int) (EXTRA_BORDER_PX * scale);
|
||||||
|
int scaledOffsetY = (int) (OFFSET_Y * scale);
|
||||||
|
int scaledQrcodeSize = (int) (QRCODE_SIZE * scale);
|
||||||
|
int scaledQrcodeOffsetY = (int) (QRCODE_OFFSET_Y * scale);
|
||||||
|
int scaledScenicFontSize = (int) (SCENIC_FONT_SIZE * scale);
|
||||||
|
int scaledDatetimeFontSize = (int) (DATETIME_FONT_SIZE * scale);
|
||||||
|
|
||||||
// 新图像画布
|
// 新图像画布
|
||||||
BufferedImage newImage = new BufferedImage(baseImage.getWidth() + 2 * EXTRA_BORDER_PX, baseImage.getHeight() + 2 * EXTRA_BORDER_PX, BufferedImage.TYPE_INT_RGB);
|
BufferedImage newImage = new BufferedImage(baseImage.getWidth() + 2 * scaledExtraBorder, baseImage.getHeight() + 2 * scaledExtraBorder, BufferedImage.TYPE_INT_RGB);
|
||||||
Graphics2D g2d = newImage.createGraphics();
|
Graphics2D g2d = newImage.createGraphics();
|
||||||
g2d.setColor(BG_COLOR);
|
g2d.setColor(BG_COLOR);
|
||||||
g2d.fillRect(0, 0, newImage.getWidth(), newImage.getHeight());
|
g2d.fillRect(0, 0, newImage.getWidth(), newImage.getHeight());
|
||||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
g2d.drawImage(baseImage, EXTRA_BORDER_PX, EXTRA_BORDER_PX, null);
|
g2d.drawImage(baseImage, scaledExtraBorder, scaledExtraBorder, null);
|
||||||
int newQrcodeHeight = QRCODE_SIZE;
|
int newQrcodeHeight = scaledQrcodeSize;
|
||||||
int newQrcodeWidth = (int) (newQrcodeHeight * 1.0 / qrcodeImage.getHeight() * qrcodeImage.getWidth());
|
int newQrcodeWidth = (int) (newQrcodeHeight * 1.0 / qrcodeImage.getHeight() * qrcodeImage.getWidth());
|
||||||
Font scenicFont = new Font(defaultFontName, Font.BOLD, SCENIC_FONT_SIZE);
|
Font scenicFont = new Font(defaultFontName, Font.BOLD, scaledScenicFontSize);
|
||||||
Font datetimeFont = new Font(defaultFontName, Font.BOLD, DATETIME_FONT_SIZE);
|
Font datetimeFont = new Font(defaultFontName, Font.BOLD, scaledDatetimeFontSize);
|
||||||
FontMetrics scenicFontMetrics = g2d.getFontMetrics(scenicFont);
|
FontMetrics scenicFontMetrics = g2d.getFontMetrics(scenicFont);
|
||||||
FontMetrics datetimeFontMetrics = g2d.getFontMetrics(datetimeFont);
|
FontMetrics datetimeFontMetrics = g2d.getFontMetrics(datetimeFont);
|
||||||
int scenicLineHeight = scenicFontMetrics.getHeight();
|
int scenicLineHeight = scenicFontMetrics.getHeight();
|
||||||
@@ -106,13 +118,14 @@ public class PrinterDefaultWatermarkOperator implements IOperator {
|
|||||||
|
|
||||||
// 二维码放置在左下角,距离左边缘图片宽度的5%,再加上左侧偏移
|
// 二维码放置在左下角,距离左边缘图片宽度的5%,再加上左侧偏移
|
||||||
int qrcodeOffsetX = (int) (newImage.getWidth() * QRCODE_LEFT_MARGIN_RATIO) + offsetLeft;
|
int qrcodeOffsetX = (int) (newImage.getWidth() * QRCODE_LEFT_MARGIN_RATIO) + offsetLeft;
|
||||||
int qrcodeOffsetY = EXTRA_BORDER_PX + baseImage.getHeight() - OFFSET_Y - newQrcodeHeight - offsetBottom;
|
int qrcodeOffsetY = scaledExtraBorder + baseImage.getHeight() - scaledOffsetY - newQrcodeHeight - offsetBottom;
|
||||||
Shape originalClip = g2d.getClip();
|
Shape originalClip = g2d.getClip();
|
||||||
|
|
||||||
// 创建比二维码大10像素的白色圆形背景
|
// 创建比二维码大10像素的白色圆形背景(10像素也要缩放)
|
||||||
int whiteCircleSize = Math.max(newQrcodeWidth, newQrcodeHeight) + 10;
|
int whiteCirclePadding = (int) (10 * scale);
|
||||||
|
int whiteCircleSize = Math.max(newQrcodeWidth, newQrcodeHeight) + whiteCirclePadding;
|
||||||
int whiteCircleX = qrcodeOffsetX - (whiteCircleSize - newQrcodeWidth) / 2;
|
int whiteCircleX = qrcodeOffsetX - (whiteCircleSize - newQrcodeWidth) / 2;
|
||||||
int whiteCircleY = qrcodeOffsetY + QRCODE_OFFSET_Y - (whiteCircleSize - newQrcodeHeight) / 2;
|
int whiteCircleY = qrcodeOffsetY + scaledQrcodeOffsetY - (whiteCircleSize - newQrcodeHeight) / 2;
|
||||||
|
|
||||||
// 绘制白色圆形背景
|
// 绘制白色圆形背景
|
||||||
g2d.setColor(Color.WHITE);
|
g2d.setColor(Color.WHITE);
|
||||||
@@ -122,7 +135,7 @@ public class PrinterDefaultWatermarkOperator implements IOperator {
|
|||||||
// 用白色圆形尺寸裁切二维码(保持二维码原始尺寸,但用大圆裁切)
|
// 用白色圆形尺寸裁切二维码(保持二维码原始尺寸,但用大圆裁切)
|
||||||
Ellipse2D qrcodeCircle = new Ellipse2D.Double(whiteCircleX, whiteCircleY, whiteCircleSize, whiteCircleSize);
|
Ellipse2D qrcodeCircle = new Ellipse2D.Double(whiteCircleX, whiteCircleY, whiteCircleSize, whiteCircleSize);
|
||||||
g2d.setClip(qrcodeCircle);
|
g2d.setClip(qrcodeCircle);
|
||||||
g2d.drawImage(qrcodeImage, qrcodeOffsetX, qrcodeOffsetY + QRCODE_OFFSET_Y, newQrcodeWidth, newQrcodeHeight, null);
|
g2d.drawImage(qrcodeImage, qrcodeOffsetX, qrcodeOffsetY + scaledQrcodeOffsetY, newQrcodeWidth, newQrcodeHeight, null);
|
||||||
g2d.setClip(originalClip);
|
g2d.setClip(originalClip);
|
||||||
|
|
||||||
// 在圆形二维码中央绘制圆形头像
|
// 在圆形二维码中央绘制圆形头像
|
||||||
@@ -130,7 +143,7 @@ public class PrinterDefaultWatermarkOperator implements IOperator {
|
|||||||
// 计算圆形头像的尺寸和位置
|
// 计算圆形头像的尺寸和位置
|
||||||
int avatarDiameter = (int) (newQrcodeHeight * 0.45);
|
int avatarDiameter = (int) (newQrcodeHeight * 0.45);
|
||||||
int avatarX = qrcodeOffsetX + (newQrcodeWidth - avatarDiameter) / 2;
|
int avatarX = qrcodeOffsetX + (newQrcodeWidth - avatarDiameter) / 2;
|
||||||
int avatarY = qrcodeOffsetY + QRCODE_OFFSET_Y + (newQrcodeHeight - avatarDiameter) / 2;
|
int avatarY = qrcodeOffsetY + scaledQrcodeOffsetY + (newQrcodeHeight - avatarDiameter) / 2;
|
||||||
|
|
||||||
// 保存当前的渲染设置
|
// 保存当前的渲染设置
|
||||||
RenderingHints originalHints = g2d.getRenderingHints();
|
RenderingHints originalHints = g2d.getRenderingHints();
|
||||||
@@ -149,10 +162,10 @@ public class PrinterDefaultWatermarkOperator implements IOperator {
|
|||||||
double faceHeight = faceImage.getHeight();
|
double faceHeight = faceImage.getHeight();
|
||||||
double scaleX = avatarDiameter / faceWidth;
|
double scaleX = avatarDiameter / faceWidth;
|
||||||
double scaleY = avatarDiameter / faceHeight;
|
double scaleY = avatarDiameter / faceHeight;
|
||||||
double scale = Math.max(scaleX, scaleY); // 使用较大的缩放比例以填满圆形
|
double faceScale = Math.max(scaleX, scaleY); // 使用较大的缩放比例以填满圆形
|
||||||
|
|
||||||
int scaledWidth = (int) (faceWidth * scale);
|
int scaledWidth = (int) (faceWidth * faceScale);
|
||||||
int scaledHeight = (int) (faceHeight * scale);
|
int scaledHeight = (int) (faceHeight * faceScale);
|
||||||
|
|
||||||
// 计算居中位置
|
// 计算居中位置
|
||||||
int faceDrawX = avatarX + (avatarDiameter - scaledWidth) / 2;
|
int faceDrawX = avatarX + (avatarDiameter - scaledWidth) / 2;
|
||||||
@@ -167,7 +180,7 @@ public class PrinterDefaultWatermarkOperator implements IOperator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 计算文字与二维码垂直居中对齐的Y坐标
|
// 计算文字与二维码垂直居中对齐的Y坐标
|
||||||
int qrcodeTop = qrcodeOffsetY + QRCODE_OFFSET_Y;
|
int qrcodeTop = qrcodeOffsetY + scaledQrcodeOffsetY;
|
||||||
int qrcodeBottom = qrcodeTop + newQrcodeHeight;
|
int qrcodeBottom = qrcodeTop + newQrcodeHeight;
|
||||||
int qrcodeCenter = (qrcodeTop + qrcodeBottom) / 2;
|
int qrcodeCenter = (qrcodeTop + qrcodeBottom) / 2;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user