diff --git a/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java b/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java index 066c80dc..d74a74aa 100644 --- a/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java +++ b/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java @@ -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,12 +124,55 @@ 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 (!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()); + } } } @@ -64,7 +181,19 @@ public class ImageConfig implements ElementConfig { 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" + "}"; } }