From 5ab288277754e02cc7aa774c8f68f21a21cf0928 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 16 Jan 2026 17:42:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(watermark):=20=E5=B0=86=E6=B0=B4?= =?UTF-8?q?=E5=8D=B0=E5=B8=83=E5=B1=80=E9=85=8D=E7=BD=AE=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E7=99=BE=E5=88=86=E6=AF=94=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将固定像素配置改为基于1920x1080的百分比配置 - 添加底部距离、二维码大小、位置等百分比常量 - 修改二维码位置计算逻辑为基于百分比的方式 - 调整景区名和日期时间文字的布局和对齐方式 - 移除原有的固定偏移计算方法 - 优化文字区域的垂直居中对齐效果 --- .../edge/NormalWatermarkTemplateBuilder.java | 103 +++++++++--------- .../service/mobile/impl/GoodsServiceImpl.java | 2 + 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/src/main/java/com/ycwl/basic/image/watermark/edge/NormalWatermarkTemplateBuilder.java b/src/main/java/com/ycwl/basic/image/watermark/edge/NormalWatermarkTemplateBuilder.java index 21bab7de..b14eff45 100644 --- a/src/main/java/com/ycwl/basic/image/watermark/edge/NormalWatermarkTemplateBuilder.java +++ b/src/main/java/com/ycwl/basic/image/watermark/edge/NormalWatermarkTemplateBuilder.java @@ -11,24 +11,35 @@ import java.util.Map; * Normal 风格水印模板构建器 * 对应 NormalWatermarkOperator * - * 布局说明: + * 布局说明(百分比基于1920x1080量化,精度0.5%): * - 白色背景 + 原图元素(COVER模式) - * - 左下角:圆形二维码(带白色圆形背景) + * - 左下角:圆形二维码(右边界在宽度45%位置) * - 二维码中央:圆形头像(可选) - * - 二维码右侧:景区名 + 日期时间 两行文字(白色) + * - 二维码右侧:景区名 + 日期时间 两行文字(白色,左对齐) */ @Component public class NormalWatermarkTemplateBuilder extends AbstractWatermarkTemplateBuilder { public static final String STYLE = "normal"; - // 常量配置(与 NormalWatermarkOperator 保持一致) - private static final int OFFSET_Y = 90; - private static final int QRCODE_SIZE = 150; - private static final int QRCODE_OFFSET_X = 10; - private static final int QRCODE_OFFSET_Y = -20; - private static final int SCENIC_FONT_SIZE = 42; - private static final int DATETIME_FONT_SIZE = 42; + // 百分比常量配置(基于1920x1080量化,精度0.5%) + /** 底部距离占高度百分比 */ + private static final double BOTTOM_OFFSET_PERCENT = 0.085; // 8.5% + /** 二维码大小占宽度百分比 */ + private static final double QRCODE_SIZE_PERCENT = 0.08; // 8% + /** 二维码右边界占宽度百分比 */ + private static final double QRCODE_RIGHT_PERCENT = 0.45; // 45% + /** 二维码Y方向偏移(向上)占高度百分比 */ + private static final double QRCODE_OFFSET_Y_PERCENT = 0.02; // 2% + /** 文字区域起始X位置占宽度百分比 */ + private static final double TEXT_START_X_PERCENT = 0.455; // 45.5% + /** 字体大小占高度百分比 */ + private static final double FONT_SIZE_PERCENT = 0.04; // 4% + /** 文字行间距占高度百分比 */ + private static final double LINE_SPACING_PERCENT = 0.005; // 0.5% + /** 文字区域右边距占宽度百分比 */ + private static final double TEXT_RIGHT_MARGIN_PERCENT = 0.01; // 1% + private static final String FONT_COLOR = "#FFFFFF"; @Override @@ -41,6 +52,16 @@ public class NormalWatermarkTemplateBuilder extends AbstractWatermarkTemplateBui int imageWidth = request.getImageWidth(); int imageHeight = request.getImageHeight(); + // 根据百分比计算实际像素值 + int bottomOffset = (int) (imageHeight * BOTTOM_OFFSET_PERCENT); + int qrcodeSize = (int) (imageWidth * QRCODE_SIZE_PERCENT); + int qrcodeRightX = (int) (imageWidth * QRCODE_RIGHT_PERCENT); + int qrcodeOffsetY = (int) (imageHeight * QRCODE_OFFSET_Y_PERCENT); + int textStartX = (int) (imageWidth * TEXT_START_X_PERCENT); + int fontSize = (int) (imageHeight * FONT_SIZE_PERCENT); + int lineSpacing = (int) (imageHeight * LINE_SPACING_PERCENT); + int textRightMargin = (int) (imageWidth * TEXT_RIGHT_MARGIN_PERCENT); + // 创建模板(白色背景,原图作为元素实现 COVER 模式) PuzzleTemplateEntity template = createTemplateWithColor( "watermark_normal_" + System.currentTimeMillis(), @@ -62,35 +83,24 @@ public class NormalWatermarkTemplateBuilder extends AbstractWatermarkTemplateBui elements.add(originalImageElement); dynamicData.put("originalImage", request.getOriginalImageUrl()); - // 计算二维码位置 - int qrcodeHeight = QRCODE_SIZE; - int qrcodeWidth = QRCODE_SIZE; // 假设二维码是正方形 - int offsetX = calculateQrcodeOffsetX(imageWidth, qrcodeWidth, request); - int qrcodeY = imageHeight - OFFSET_Y - qrcodeHeight; - int qrcodeX = offsetX; + // 计算二维码位置(右边界在45%位置,向左推算左边界) + int qrcodeX = qrcodeRightX - qrcodeSize; + int qrcodeY = imageHeight - bottomOffset - qrcodeSize - qrcodeOffsetY; - // 1. 白色圆形背景(比二维码大10像素) - int whiteCircleSize = qrcodeHeight + 10; - int whiteCircleX = qrcodeX - (whiteCircleSize - qrcodeWidth) / 2; - int whiteCircleY = qrcodeY + QRCODE_OFFSET_Y - (whiteCircleSize - qrcodeHeight) / 2; - - // 使用图片元素模拟白色圆形(边缘端需要支持纯色圆形或使用白色圆形图片) - // 这里暂时跳过白色背景,让二维码直接显示 - - // 2. 二维码元素(圆形裁切) + // 1. 二维码元素(圆形裁切) PuzzleElementEntity qrcodeElement = createCircleImageElement( "qrcode", "二维码", - qrcodeX, qrcodeY + QRCODE_OFFSET_Y, - qrcodeHeight, 10 + qrcodeX, qrcodeY, + qrcodeSize, 10 ); elements.add(qrcodeElement); dynamicData.put("qrcode", request.getQrcodeUrl()); - // 3. 头像元素(圆形,二维码中央,可选) + // 2. 头像元素(圆形,二维码中央,可选) if (request.getFaceUrl() != null && !request.getFaceUrl().isEmpty()) { - int avatarDiameter = (int) (qrcodeHeight * 0.45); - int avatarX = qrcodeX + (qrcodeWidth - avatarDiameter) / 2; - int avatarY = qrcodeY + QRCODE_OFFSET_Y + (qrcodeHeight - avatarDiameter) / 2; + int avatarDiameter = (int) (qrcodeSize * 0.45); + int avatarX = qrcodeX + (qrcodeSize - avatarDiameter) / 2; + int avatarY = qrcodeY + (qrcodeSize - avatarDiameter) / 2; PuzzleElementEntity faceElement = createCircleImageElement( "face", "头像", @@ -101,28 +111,29 @@ public class NormalWatermarkTemplateBuilder extends AbstractWatermarkTemplateBui dynamicData.put("face", request.getFaceUrl()); } - // 4. 景区名文字 - int textX = qrcodeX + qrcodeWidth + QRCODE_OFFSET_X; - int textY = qrcodeY + QRCODE_OFFSET_Y + qrcodeHeight / 2 - SCENIC_FONT_SIZE; + // 3. 景区名文字(在二维码右侧,从45.5%位置开始,左对齐) + // 文字垂直居中于二维码区域 + int textAreaHeight = fontSize * 2 + lineSpacing; + int textY = qrcodeY + (qrcodeSize - textAreaHeight) / 2; PuzzleElementEntity scenicTextElement = createTextElement( "scenicLine", "景区名", - textX, textY, - imageWidth - textX - 20, SCENIC_FONT_SIZE + 10, 30, - "PingFang SC", SCENIC_FONT_SIZE, FONT_COLOR, + textStartX, textY, + imageWidth - textStartX - textRightMargin, fontSize + lineSpacing, 30, + "PingFang SC", fontSize, FONT_COLOR, "NORMAL", TEXT_ALIGN_LEFT ); elements.add(scenicTextElement); dynamicData.put("scenicLine", request.getScenicLine() != null ? request.getScenicLine() : ""); - // 5. 日期时间文字 - int datetimeY = textY + SCENIC_FONT_SIZE + 5; + // 4. 日期时间文字(在景区名下方,左对齐) + int datetimeY = textY + fontSize + lineSpacing; PuzzleElementEntity datetimeTextElement = createTextElement( "datetimeLine", "日期时间", - textX, datetimeY, - imageWidth - textX - 20, DATETIME_FONT_SIZE + 10, 30, - "PingFang SC", DATETIME_FONT_SIZE, FONT_COLOR, + textStartX, datetimeY, + imageWidth - textStartX - textRightMargin, fontSize + lineSpacing, 30, + "PingFang SC", fontSize, FONT_COLOR, "NORMAL", TEXT_ALIGN_LEFT ); elements.add(datetimeTextElement); @@ -130,12 +141,4 @@ public class NormalWatermarkTemplateBuilder extends AbstractWatermarkTemplateBui return createResult(template, elements, dynamicData); } - - /** - * 计算二维码的X偏移位置(居中于文字和二维码整体) - */ - private int calculateQrcodeOffsetX(int imageWidth, int qrcodeWidth, WatermarkRequest request) { - // 简化处理:固定距离左边一定比例 - return (int) (imageWidth * 0.1); - } } diff --git a/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java b/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java index 23812383..7679ffa0 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java @@ -59,6 +59,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Strings; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import java.io.File; @@ -112,6 +113,7 @@ public class GoodsServiceImpl implements GoodsService { @Autowired private WatermarkEdgeService watermarkEdgeService; @Autowired + @Lazy private FaceService faceService; @Override