feat(image): 添加图片叠加功能支持

- 新增 OverlayImageConfig 类用于配置叠加图片参数
- 支持通过 imageKey 从 dynamicData 动态获取图片 URL
- 提供默认图片 URL 配置选项
- 支持设置叠加图片宽高比例(0.0-1.0 范围)
- 实现图片适配模式配置(CONTAIN、COVER、FILL、SCALE_DOWN)
- 添加圆角半径配置,支持自动圆形效果
- 支持水平垂直对齐方式设置(CENTER、LEFT、RIGHT、TOP、BOTTOM)
- 提供水平垂直偏移量调节功能
- 更新配置验证逻辑,增加叠加图片配置校验
- 修改图片 URL 校验规则,支持动态数据填充
- 更新 JSON Schema 配置模板,包含叠加图片配置项
This commit is contained in:
2026-01-15 13:36:13 +08:00
parent fed92c5445
commit 2fb6aa42cf

View File

@@ -29,9 +29,83 @@ public class ImageConfig implements ElementConfig {
/**
* 圆角半径(像素,0为直角)
* 注意:当 borderRadius >= min(width, height) / 2 时,效果为圆形
*/
private Integer borderRadius = 0;
/**
* 叠加图片配置
* 用于在主图上叠加另一张图片(如二维码中心的头像)
*/
private OverlayImageConfig overlayImage;
/**
* 叠加图片配置类
*/
@Data
public static class OverlayImageConfig {
/**
* 叠加图片的数据源 key(从 dynamicData 获取 URL)
* 例如:faceAvatar
*/
private String imageKey;
/**
* 叠加图片的默认 URL(当 dynamicData 中无对应值时使用)
*/
private String defaultImageUrl;
/**
* 叠加图片宽度占主图宽度的比例(0.0 - 1.0)
* 默认 0.45(与现有水印实现一致)
*/
private Double widthRatio = 0.45;
/**
* 叠加图片高度占主图高度的比例(0.0 - 1.0)
* 默认 0.45
*/
private Double heightRatio = 0.45;
/**
* 叠加图片的适配模式
* 默认 COVER(与现有水印实现一致)
*/
private String imageFitMode = "COVER";
/**
* 叠加图片的圆角半径
* 默认 -1 表示自动计算为圆形(min(width, height) / 2)
*/
private Integer borderRadius = -1;
/**
* 叠加图片的水平对齐方式
* CENTER - 居中(默认)
* LEFT - 左对齐
* RIGHT - 右对齐
*/
private String horizontalAlign = "CENTER";
/**
* 叠加图片的垂直对齐方式
* CENTER - 居中(默认)
* TOP - 顶部对齐
* BOTTOM - 底部对齐
*/
private String verticalAlign = "CENTER";
/**
* 水平偏移量(像素),正值向右,负值向左
*/
private Integer offsetX = 0;
/**
* 垂直偏移量(像素),正值向下,负值向上
*/
private Integer offsetY = 0;
}
@Override
public void validate() {
// 校验圆角半径
@@ -50,21 +124,76 @@ public class ImageConfig implements ElementConfig {
}
}
// 校验图片URL
if (StrUtil.isBlank(defaultImageUrl)) {
throw new IllegalArgumentException("默认图片URL不能为空");
}
// 校验图片URL(注意:现在可以通过 dynamicData 动态填充,所以允许为空)
if (StrUtil.isNotBlank(defaultImageUrl)) {
if (!defaultImageUrl.startsWith("http://") && !defaultImageUrl.startsWith("https://")) {
throw new IllegalArgumentException("图片URL必须以http://或https://开头: " + defaultImageUrl);
}
}
// 校验叠加图片配置
if (overlayImage != null) {
validateOverlayImage(overlayImage);
}
}
private void validateOverlayImage(OverlayImageConfig overlay) {
// 校验比例范围
if (overlay.getWidthRatio() != null && (overlay.getWidthRatio() <= 0 || overlay.getWidthRatio() > 1)) {
throw new IllegalArgumentException("叠加图片宽度比例必须在 0-1 之间: " + overlay.getWidthRatio());
}
if (overlay.getHeightRatio() != null && (overlay.getHeightRatio() <= 0 || overlay.getHeightRatio() > 1)) {
throw new IllegalArgumentException("叠加图片高度比例必须在 0-1 之间: " + overlay.getHeightRatio());
}
// 校验对齐方式
if (StrUtil.isNotBlank(overlay.getHorizontalAlign())) {
String align = overlay.getHorizontalAlign().toUpperCase();
if (!"CENTER".equals(align) && !"LEFT".equals(align) && !"RIGHT".equals(align)) {
throw new IllegalArgumentException("水平对齐方式只能是CENTER、LEFT或RIGHT: " + overlay.getHorizontalAlign());
}
}
if (StrUtil.isNotBlank(overlay.getVerticalAlign())) {
String align = overlay.getVerticalAlign().toUpperCase();
if (!"CENTER".equals(align) && !"TOP".equals(align) && !"BOTTOM".equals(align)) {
throw new IllegalArgumentException("垂直对齐方式只能是CENTER、TOP或BOTTOM: " + overlay.getVerticalAlign());
}
}
// 校验叠加图片URL
if (StrUtil.isNotBlank(overlay.getDefaultImageUrl())) {
if (!overlay.getDefaultImageUrl().startsWith("http://") && !overlay.getDefaultImageUrl().startsWith("https://")) {
throw new IllegalArgumentException("叠加图片URL必须以http://或https://开头: " + overlay.getDefaultImageUrl());
}
}
// 校验适配模式
if (StrUtil.isNotBlank(overlay.getImageFitMode())) {
String mode = overlay.getImageFitMode().toUpperCase();
if (!"CONTAIN".equals(mode) && !"COVER".equals(mode) && !"FILL".equals(mode) && !"SCALE_DOWN".equals(mode)) {
throw new IllegalArgumentException("叠加图片适配模式只能是CONTAIN、COVER、FILL或SCALE_DOWN: " + overlay.getImageFitMode());
}
}
}
@Override
public String getConfigSchema() {
return "{\n" +
" \"defaultImageUrl\": \"https://example.com/image.jpg\",\n" +
" \"imageFitMode\": \"CONTAIN|COVER|FILL|SCALE_DOWN\",\n" +
" \"borderRadius\": 0\n" +
" \"borderRadius\": 0,\n" +
" \"overlayImage\": {\n" +
" \"imageKey\": \"faceAvatar\",\n" +
" \"defaultImageUrl\": \"https://example.com/default-avatar.png\",\n" +
" \"widthRatio\": 0.45,\n" +
" \"heightRatio\": 0.45,\n" +
" \"imageFitMode\": \"COVER\",\n" +
" \"borderRadius\": -1,\n" +
" \"horizontalAlign\": \"CENTER\",\n" +
" \"verticalAlign\": \"CENTER\",\n" +
" \"offsetX\": 0,\n" +
" \"offsetY\": 0\n" +
" }\n" +
"}";
}
}