You've already forked FrameTour-BE
feat(watermark): 在二维码中央添加圆形头像
- 在 WatermarkInfo 实体中添加 faceFile 字段,用于存储头像文件 - 在 LeicaWatermarkOperator 和 NormalWatermarkOperator 中实现头像添加功能 - 在 GoodsServiceImpl 中下载用户头像并传递给 watermarkInfo
This commit is contained in:
@@ -14,6 +14,7 @@ public class WatermarkInfo {
|
||||
*/
|
||||
private File watermarkedFile;
|
||||
private File qrcodeFile;
|
||||
private File faceFile;
|
||||
private String scenicLine;
|
||||
private String secondLine;
|
||||
private String thirdLine;
|
||||
@@ -35,4 +36,5 @@ public class WatermarkInfo {
|
||||
}
|
||||
return scenicLine;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -4,11 +4,13 @@ import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
|
||||
import com.ycwl.basic.image.watermark.exception.ImageWatermarkException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -67,6 +69,7 @@ public class LeicaWatermarkOperator implements IOperator {
|
||||
BufferedImage baseImage;
|
||||
BufferedImage qrcodeImage;
|
||||
BufferedImage logoImage;
|
||||
BufferedImage faceImage = null;
|
||||
// 从类路径加载 zt-logo.png
|
||||
InputStream logoInputStream = getClass().getResourceAsStream("/zt-logo.png");
|
||||
if (logoInputStream == null) {
|
||||
@@ -77,6 +80,13 @@ public class LeicaWatermarkOperator implements IOperator {
|
||||
qrcodeImage = ImageIO.read(info.getQrcodeFile());
|
||||
logoImage = ImageIO.read(logoInputStream);
|
||||
logoInputStream.close();
|
||||
if (info.getFaceFile() != null && info.getFaceFile().isFile()) {
|
||||
try {
|
||||
faceImage = ImageIO.read(info.getFaceFile());
|
||||
} catch (IOException e) {
|
||||
log.warn("头像文件读取失败", e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ImageWatermarkException("图片打开失败");
|
||||
}
|
||||
@@ -110,9 +120,51 @@ public class LeicaWatermarkOperator implements IOperator {
|
||||
// 计算二维码的位置
|
||||
int qrcodeX = newImage.getWidth() + EXTRA_BORDER_PX - OFFSET_X - newQrcodeWidth - QRCODE_OFFSET_X - Math.max(scenicLineWidth, datetimeLineWidth);
|
||||
int qrcodeY = EXTRA_BORDER_PX + baseImage.getHeight() + OFFSET_Y - QRCODE_OFFSET_Y;
|
||||
|
||||
|
||||
g2d.drawImage(qrcodeImage, qrcodeX, qrcodeY, newQrcodeWidth, newQrcodeHeight, null);
|
||||
|
||||
// 在二维码中央绘制圆形头像
|
||||
if (faceImage != null) {
|
||||
// 计算圆形头像的尺寸和位置
|
||||
int avatarDiameter = (int) (newQrcodeHeight * 0.45);
|
||||
int avatarX = qrcodeX + (newQrcodeWidth - avatarDiameter) / 2;
|
||||
int avatarY = qrcodeY + (newQrcodeHeight - avatarDiameter) / 2;
|
||||
|
||||
// 保存当前的渲染设置和剪切区域
|
||||
RenderingHints originalHints = g2d.getRenderingHints();
|
||||
Shape originalClip = g2d.getClip();
|
||||
|
||||
// 设置高质量渲染
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// 创建圆形剪切区域
|
||||
Ellipse2D avatarCircle = new Ellipse2D.Double(avatarX, avatarY, avatarDiameter, avatarDiameter);
|
||||
g2d.setClip(avatarCircle);
|
||||
|
||||
// 实现CSS cover效果的缩放逻辑
|
||||
double faceWidth = faceImage.getWidth();
|
||||
double faceHeight = faceImage.getHeight();
|
||||
double scaleX = avatarDiameter / faceWidth;
|
||||
double scaleY = avatarDiameter / faceHeight;
|
||||
double scale = Math.max(scaleX, scaleY); // 使用较大的缩放比例以填满圆形
|
||||
|
||||
int scaledWidth = (int) (faceWidth * scale);
|
||||
int scaledHeight = (int) (faceHeight * scale);
|
||||
|
||||
// 计算居中位置
|
||||
int faceDrawX = avatarX + (avatarDiameter - scaledWidth) / 2;
|
||||
int faceDrawY = avatarY + (avatarDiameter - scaledHeight) / 2;
|
||||
|
||||
// 绘制缩放后的头像
|
||||
g2d.drawImage(faceImage, faceDrawX, faceDrawY, scaledWidth, scaledHeight, null);
|
||||
|
||||
// 恢复原始设置
|
||||
g2d.setClip(originalClip);
|
||||
g2d.setRenderingHints(originalHints);
|
||||
}
|
||||
|
||||
// 计算文字与二维码垂直居中对齐的Y坐标
|
||||
int qrcodeTop = qrcodeY;
|
||||
int qrcodeBottom = qrcodeTop + newQrcodeHeight;
|
||||
@@ -153,7 +205,7 @@ public class LeicaWatermarkOperator implements IOperator {
|
||||
writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
writeParam.setCompressionQuality(0.75f); // 设置写入质量为 75%
|
||||
}
|
||||
writer.write(null, new javax.imageio.IIOImage(newImage, null, null), writeParam);
|
||||
writer.write(null, new IIOImage(newImage, null, null), writeParam);
|
||||
} catch (IOException e) {
|
||||
throw new ImageWatermarkException("图片保存失败");
|
||||
}
|
||||
|
@@ -59,9 +59,17 @@ public class NormalWatermarkOperator implements IOperator {
|
||||
public File process(WatermarkInfo info) throws ImageWatermarkException {
|
||||
BufferedImage baseImage;
|
||||
BufferedImage qrcodeImage;
|
||||
BufferedImage faceImage = null;
|
||||
try {
|
||||
baseImage = ImageIO.read(info.getOriginalFile());
|
||||
qrcodeImage = ImageIO.read(info.getQrcodeFile());
|
||||
if (info.getFaceFile() != null && info.getFaceFile().isFile()) {
|
||||
try {
|
||||
faceImage = ImageIO.read(info.getFaceFile());
|
||||
} catch (IOException e) {
|
||||
log.warn("头像文件读取失败", e);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ImageWatermarkException("图片打开失败");
|
||||
}
|
||||
@@ -89,6 +97,47 @@ public class NormalWatermarkOperator implements IOperator {
|
||||
g2d.setClip(circle);
|
||||
g2d.drawImage(qrcodeImage, offsetX, offsetY + QRCODE_OFFSET_Y, newQrcodeWidth, newQrcodeHeight, null);
|
||||
g2d.setClip(originalClip);
|
||||
|
||||
// 在圆形二维码中央绘制圆形头像
|
||||
if (faceImage != null) {
|
||||
// 计算圆形头像的尺寸和位置
|
||||
int avatarDiameter = (int) (newQrcodeHeight * 0.45);
|
||||
int avatarX = offsetX + (newQrcodeWidth - avatarDiameter) / 2;
|
||||
int avatarY = offsetY + QRCODE_OFFSET_Y + (newQrcodeHeight - avatarDiameter) / 2;
|
||||
|
||||
// 保存当前的渲染设置
|
||||
RenderingHints originalHints = g2d.getRenderingHints();
|
||||
|
||||
// 设置高质量渲染
|
||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// 创建圆形剪切区域
|
||||
Ellipse2D avatarCircle = new Ellipse2D.Double(avatarX, avatarY, avatarDiameter, avatarDiameter);
|
||||
g2d.setClip(avatarCircle);
|
||||
|
||||
// 实现CSS cover效果的缩放逻辑
|
||||
double faceWidth = faceImage.getWidth();
|
||||
double faceHeight = faceImage.getHeight();
|
||||
double scaleX = avatarDiameter / faceWidth;
|
||||
double scaleY = avatarDiameter / faceHeight;
|
||||
double scale = Math.max(scaleX, scaleY); // 使用较大的缩放比例以填满圆形
|
||||
|
||||
int scaledWidth = (int) (faceWidth * scale);
|
||||
int scaledHeight = (int) (faceHeight * scale);
|
||||
|
||||
// 计算居中位置
|
||||
int faceDrawX = avatarX + (avatarDiameter - scaledWidth) / 2;
|
||||
int faceDrawY = avatarY + (avatarDiameter - scaledHeight) / 2;
|
||||
|
||||
// 绘制缩放后的头像
|
||||
g2d.drawImage(faceImage, faceDrawX, faceDrawY, scaledWidth, scaledHeight, null);
|
||||
|
||||
// 恢复原始设置
|
||||
g2d.setClip(originalClip);
|
||||
g2d.setRenderingHints(originalHints);
|
||||
}
|
||||
// 计算文字与二维码垂直居中对齐的Y坐标
|
||||
int qrcodeTop = offsetY + QRCODE_OFFSET_Y;
|
||||
int qrcodeBottom = qrcodeTop + newQrcodeHeight;
|
||||
|
@@ -691,6 +691,13 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
return defaultUrlList;
|
||||
}
|
||||
tmpFile.add(qrcode);
|
||||
File faceFile = new File("face_"+face.getId()+".jpg");
|
||||
try {
|
||||
HttpUtil.downloadFile(face.getFaceUrl().replace("oss.zhentuai.com", "frametour-assets.oss-cn-shanghai-internal.aliyuncs.com"), faceFile);
|
||||
tmpFile.add(faceFile);
|
||||
} catch (Exception e) {
|
||||
log.error("download face error", e);
|
||||
}
|
||||
List<GoodsUrlVO> collect = defaultUrlList.stream().peek(item -> {
|
||||
Optional<SourceWatermarkEntity> any = watermarkEntityList.stream()
|
||||
.filter(watermark -> watermark.getSourceId().equals(item.getGoodsId()))
|
||||
@@ -723,6 +730,7 @@ public class GoodsServiceImpl implements GoodsService {
|
||||
info.setQrcodeFile(qrcode);
|
||||
info.setScenicLine(text);
|
||||
info.setDatetime(item.getCreateTime());
|
||||
info.setFaceFile(faceFile);
|
||||
info.setDtFormat(scenicConfig.getWatermarkDtFormat());
|
||||
info.setWatermarkedFile(watermarkedFile);
|
||||
try {
|
||||
|
Reference in New Issue
Block a user