You've already forked FrameTour-BE
refactor(watermark): 将水印布局配置改为百分比方式
- 将固定像素配置改为基于1920x1080的百分比配置 - 添加底部距离、二维码大小、位置等百分比常量 - 修改二维码位置计算逻辑为基于百分比的方式 - 调整景区名和日期时间文字的布局和对齐方式 - 移除原有的固定偏移计算方法 - 优化文字区域的垂直居中对齐效果
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user