You've already forked FrameTour-BE
画画的
This commit is contained in:
12
pom.xml
12
pom.xml
@ -24,6 +24,7 @@
|
|||||||
<fastjson.version>1.2.83</fastjson.version>
|
<fastjson.version>1.2.83</fastjson.version>
|
||||||
<knife4j-spring-boot-starter.version>2.0.7</knife4j-spring-boot-starter.version>
|
<knife4j-spring-boot-starter.version>2.0.7</knife4j-spring-boot-starter.version>
|
||||||
<pagehelper.version>5.3.1</pagehelper.version>
|
<pagehelper.version>5.3.1</pagehelper.version>
|
||||||
|
<tomcat.version>9.0.102</tomcat.version>
|
||||||
<!--跳过单元测试-->
|
<!--跳过单元测试-->
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
</properties>
|
</properties>
|
||||||
@ -198,6 +199,17 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
<resources>
|
||||||
|
<!-- 确保 src/main/resources 下的资源文件被包含 -->
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*.png</include> <!-- 明确包含 PNG 文件 -->
|
||||||
|
<include>**/*.ttf</include> <!-- 明确包含 TTF 文件 -->
|
||||||
|
<include>**/*</include> <!-- 包含其他所有资源文件 -->
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
4
src/main/java/com/ycwl/basic/image/util/ImageUtil.java
Normal file
4
src/main/java/com/ycwl/basic/image/util/ImageUtil.java
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package com.ycwl.basic.image.util;
|
||||||
|
|
||||||
|
public class ImageUtil {
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.ycwl.basic.image.watermark;
|
||||||
|
|
||||||
|
import com.ycwl.basic.image.watermark.enums.ImageWatermarkOperatorEnum;
|
||||||
|
import com.ycwl.basic.image.watermark.exception.ImageWatermarkUnsupportedException;
|
||||||
|
import com.ycwl.basic.image.watermark.operator.IOperator;
|
||||||
|
import com.ycwl.basic.image.watermark.operator.LeicaWatermarkOperator;
|
||||||
|
import com.ycwl.basic.image.watermark.operator.NormalWatermarkOperator;
|
||||||
|
|
||||||
|
public class ImageWatermarkFactory {
|
||||||
|
public static IOperator get(String watermarkType) {
|
||||||
|
ImageWatermarkOperatorEnum type = ImageWatermarkOperatorEnum.getByCode(watermarkType);
|
||||||
|
if (type == null) {
|
||||||
|
throw new ImageWatermarkUnsupportedException(watermarkType);
|
||||||
|
}
|
||||||
|
switch (type) {
|
||||||
|
case NORMAL:
|
||||||
|
return new NormalWatermarkOperator();
|
||||||
|
case LEICA:
|
||||||
|
return new LeicaWatermarkOperator();
|
||||||
|
default:
|
||||||
|
throw new ImageWatermarkUnsupportedException(watermarkType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.ycwl.basic.image.watermark.entity;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class WatermarkInfo {
|
||||||
|
private File originalFile;
|
||||||
|
private File watermarkedFile;
|
||||||
|
private File qrcodeFile;
|
||||||
|
private String scenicLine;
|
||||||
|
private String secondLine;
|
||||||
|
private String thirdLine;
|
||||||
|
private String fourthLine;
|
||||||
|
private Date datetime;
|
||||||
|
private String dtFormat;
|
||||||
|
private String datetimeLine;
|
||||||
|
|
||||||
|
public String getDatetimeLine() {
|
||||||
|
if (datetimeLine == null) {
|
||||||
|
datetimeLine = DateUtil.format(datetime, dtFormat);
|
||||||
|
}
|
||||||
|
return datetimeLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScenicLine() {
|
||||||
|
if (scenicLine == null) {
|
||||||
|
scenicLine = "";
|
||||||
|
}
|
||||||
|
return scenicLine;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.ycwl.basic.image.watermark.enums;
|
||||||
|
|
||||||
|
public enum ImageWatermarkOperatorEnum {
|
||||||
|
LEICA("leica"),
|
||||||
|
NORMAL("normal");
|
||||||
|
|
||||||
|
private final String type;
|
||||||
|
|
||||||
|
ImageWatermarkOperatorEnum(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImageWatermarkOperatorEnum getByCode(String type) {
|
||||||
|
for (ImageWatermarkOperatorEnum imageWatermarkOperatorEnum : ImageWatermarkOperatorEnum.values()) {
|
||||||
|
if (imageWatermarkOperatorEnum.type.equals(type)) {
|
||||||
|
return imageWatermarkOperatorEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.ycwl.basic.image.watermark.exception;
|
||||||
|
|
||||||
|
public class ImageWatermarkException extends RuntimeException {
|
||||||
|
public ImageWatermarkException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.ycwl.basic.image.watermark.exception;
|
||||||
|
|
||||||
|
public class ImageWatermarkUnsupportedException extends ImageWatermarkException {
|
||||||
|
public ImageWatermarkUnsupportedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.ycwl.basic.image.watermark.operator;
|
||||||
|
|
||||||
|
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
|
||||||
|
import com.ycwl.basic.image.watermark.exception.ImageWatermarkException;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public interface IOperator {
|
||||||
|
File process(WatermarkInfo info) throws ImageWatermarkException;
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
package com.ycwl.basic.image.watermark.operator;
|
||||||
|
|
||||||
|
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
|
||||||
|
import com.ycwl.basic.image.watermark.exception.ImageWatermarkException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 徕卡水印
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class LeicaWatermarkOperator implements IOperator {
|
||||||
|
private static final String FONT_PATH = "/PingFang_SC.ttf";
|
||||||
|
public static String defaultFontName;
|
||||||
|
public static float FONT_GLOBAL_OFFSET_PERCENT = 0;
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
// 加载字体文件流
|
||||||
|
InputStream fontStream = LeicaWatermarkOperator.class.getResourceAsStream(FONT_PATH);
|
||||||
|
if (fontStream == null) {
|
||||||
|
throw new RuntimeException("字体文件未找到!路径:" + FONT_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建字体对象
|
||||||
|
Font customFont = Font.createFont(Font.TRUETYPE_FONT, fontStream);
|
||||||
|
|
||||||
|
// 注册字体到系统
|
||||||
|
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||||
|
ge.registerFont(customFont);
|
||||||
|
|
||||||
|
// 更新默认字体名称为新字体的逻辑名称
|
||||||
|
defaultFontName = customFont.getName(); // 如 "PingFang SC"
|
||||||
|
FONT_GLOBAL_OFFSET_PERCENT = -0.3f;
|
||||||
|
} catch (FontFormatException | IOException e) {
|
||||||
|
log.error("加载字体文件失败", e);
|
||||||
|
defaultFontName = "宋体";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static int EXTRA_BOTTOM_PX = 140;
|
||||||
|
public static int EXTRA_BORDER_PX = 0;
|
||||||
|
public static Color BG_COLOR = Color.WHITE;
|
||||||
|
public static int LOGO_SIZE = 50;
|
||||||
|
public static int LOGO_EXTRA_BORDER = 10;
|
||||||
|
public static int LOGO_FONT_SIZE = 38;
|
||||||
|
public static Color logoTextColor = new Color(0x33, 0x33, 0x33);
|
||||||
|
public static int QRCODE_SIZE = 80;
|
||||||
|
public static int QRCODE_OFFSET_X = 5;
|
||||||
|
public static int OFFSET_X = 80;
|
||||||
|
public static int OFFSET_Y = 30;
|
||||||
|
public static int SCENIC_FONT_SIZE = 32;
|
||||||
|
public static Color scenicColor = new Color(0x33, 0x33, 0x33);
|
||||||
|
public static int DATETIME_FONT_SIZE = 28;
|
||||||
|
public static Color datetimeColor = new Color(0x99, 0x99, 0x99);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File process(WatermarkInfo info) throws ImageWatermarkException {
|
||||||
|
BufferedImage baseImage;
|
||||||
|
BufferedImage qrcodeImage;
|
||||||
|
BufferedImage logoImage;
|
||||||
|
// 从类路径加载 zt-logo.png
|
||||||
|
InputStream logoInputStream = getClass().getResourceAsStream("/zt-logo.png");
|
||||||
|
if (logoInputStream == null) {
|
||||||
|
throw new ImageWatermarkException("无法找到 zt-logo.png 资源文件");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
baseImage = ImageIO.read(info.getOriginalFile());
|
||||||
|
qrcodeImage = ImageIO.read(info.getQrcodeFile());
|
||||||
|
logoImage = ImageIO.read(logoInputStream);
|
||||||
|
logoInputStream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ImageWatermarkException("图片打开失败");
|
||||||
|
}
|
||||||
|
// 新图像画布
|
||||||
|
BufferedImage newImage = new BufferedImage(baseImage.getWidth() + 2 * EXTRA_BORDER_PX, baseImage.getHeight() + 2 * EXTRA_BORDER_PX + EXTRA_BOTTOM_PX, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D g2d = newImage.createGraphics();
|
||||||
|
g2d.setColor(BG_COLOR);
|
||||||
|
g2d.fillRect(0, 0, newImage.getWidth(), newImage.getHeight());
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2d.drawImage(baseImage, EXTRA_BORDER_PX, EXTRA_BORDER_PX, null);
|
||||||
|
int logoHeight = LOGO_SIZE;
|
||||||
|
int logoWidth = (int) (logoHeight * 1.0 / logoImage.getHeight() * logoImage.getWidth());
|
||||||
|
g2d.drawImage(logoImage, EXTRA_BORDER_PX + OFFSET_X, EXTRA_BORDER_PX + baseImage.getHeight() + OFFSET_Y + LOGO_EXTRA_BORDER, logoWidth, logoHeight, null);
|
||||||
|
Font logoTextFont = new Font(defaultFontName, Font.PLAIN, LOGO_FONT_SIZE);
|
||||||
|
g2d.setFont(logoTextFont);
|
||||||
|
g2d.setColor(logoTextColor);
|
||||||
|
FontMetrics logoFontMetrics = g2d.getFontMetrics(logoTextFont);
|
||||||
|
int logoTextHeight = logoFontMetrics.getHeight();
|
||||||
|
int logoTextOffsetY = (LOGO_SIZE - logoHeight) / 2;
|
||||||
|
g2d.drawString("帧途", EXTRA_BORDER_PX + OFFSET_X + logoWidth + 5, EXTRA_BORDER_PX + baseImage.getHeight() + OFFSET_Y + logoTextHeight + LOGO_EXTRA_BORDER + logoTextOffsetY + logoTextHeight * FONT_GLOBAL_OFFSET_PERCENT);
|
||||||
|
int newQrcodeHeight = QRCODE_SIZE;
|
||||||
|
int newQrcodeWidth = (int) (newQrcodeHeight * 1.0 / qrcodeImage.getHeight() * qrcodeImage.getWidth());
|
||||||
|
Font scenicFont = new Font(defaultFontName, Font.PLAIN, SCENIC_FONT_SIZE);
|
||||||
|
Font datetimeFont = new Font(defaultFontName, Font.PLAIN, DATETIME_FONT_SIZE);
|
||||||
|
FontMetrics scenicFontMetrics = g2d.getFontMetrics(scenicFont);
|
||||||
|
FontMetrics datetimeFontMetrics = g2d.getFontMetrics(datetimeFont);
|
||||||
|
int scenicLineHeight = scenicFontMetrics.getHeight();
|
||||||
|
int dtLineHeight = datetimeFontMetrics.getHeight();
|
||||||
|
int scenicLineWidth = scenicFontMetrics.stringWidth(info.getScenicLine());
|
||||||
|
int datetimeLineWidth = scenicFontMetrics.stringWidth(info.getDatetimeLine());
|
||||||
|
g2d.drawImage(qrcodeImage, newImage.getWidth() + EXTRA_BORDER_PX - OFFSET_X - newQrcodeWidth - QRCODE_OFFSET_X - Math.max(scenicLineWidth, datetimeLineWidth), EXTRA_BORDER_PX + baseImage.getHeight() + + OFFSET_Y, newQrcodeWidth, newQrcodeHeight, null);
|
||||||
|
g2d.setFont(scenicFont);
|
||||||
|
g2d.setColor(scenicColor);
|
||||||
|
g2d.drawString(info.getScenicLine(), newImage.getWidth() + EXTRA_BORDER_PX - OFFSET_X - Math.max(scenicLineWidth, datetimeLineWidth), EXTRA_BORDER_PX + baseImage.getHeight() + + OFFSET_Y + scenicLineHeight + scenicLineHeight * FONT_GLOBAL_OFFSET_PERCENT);
|
||||||
|
g2d.setFont(datetimeFont);
|
||||||
|
g2d.setColor(datetimeColor);
|
||||||
|
g2d.drawString(info.getDatetimeLine(), newImage.getWidth() + EXTRA_BORDER_PX - OFFSET_X - Math.max(scenicLineWidth, datetimeLineWidth), EXTRA_BORDER_PX + baseImage.getHeight() + + OFFSET_Y + scenicLineHeight + dtLineHeight + dtLineHeight * FONT_GLOBAL_OFFSET_PERCENT);
|
||||||
|
try {
|
||||||
|
ImageIO.write(newImage, "png", info.getWatermarkedFile());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ImageWatermarkException("图片保存失败");
|
||||||
|
} finally {
|
||||||
|
g2d.dispose();
|
||||||
|
}
|
||||||
|
return info.getWatermarkedFile();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
package com.ycwl.basic.image.watermark.operator;
|
||||||
|
|
||||||
|
import com.ycwl.basic.image.watermark.entity.WatermarkInfo;
|
||||||
|
import com.ycwl.basic.image.watermark.exception.ImageWatermarkException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class NormalWatermarkOperator implements IOperator {
|
||||||
|
private static final String FONT_PATH = "/PingFang_SC.ttf";
|
||||||
|
public static String defaultFontName;
|
||||||
|
public static float FONT_GLOBAL_OFFSET_PERCENT = 0;
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
// 加载字体文件流
|
||||||
|
InputStream fontStream = LeicaWatermarkOperator.class.getResourceAsStream(FONT_PATH);
|
||||||
|
if (fontStream == null) {
|
||||||
|
throw new RuntimeException("字体文件未找到!路径:" + FONT_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建字体对象
|
||||||
|
Font customFont = Font.createFont(Font.TRUETYPE_FONT, fontStream);
|
||||||
|
|
||||||
|
// 注册字体到系统
|
||||||
|
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||||
|
ge.registerFont(customFont);
|
||||||
|
|
||||||
|
// 更新默认字体名称为新字体的逻辑名称
|
||||||
|
defaultFontName = customFont.getName(); // 如 "PingFang SC"
|
||||||
|
FONT_GLOBAL_OFFSET_PERCENT = -0.3f;
|
||||||
|
} catch (FontFormatException | IOException e) {
|
||||||
|
log.error("加载字体文件失败", e);
|
||||||
|
defaultFontName = "宋体";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static int EXTRA_BORDER_PX = 0;
|
||||||
|
public static int OFFSET_Y = 90;
|
||||||
|
public static Color BG_COLOR = Color.WHITE;
|
||||||
|
public static int QRCODE_SIZE = 100;
|
||||||
|
public static int QRCODE_OFFSET_X = 10;
|
||||||
|
|
||||||
|
public static int SCENIC_FONT_SIZE = 42;
|
||||||
|
public static Color scenicColor = Color.white;
|
||||||
|
public static int DATETIME_FONT_SIZE = 42;
|
||||||
|
public static Color datetimeColor = Color.white;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File process(WatermarkInfo info) throws ImageWatermarkException {
|
||||||
|
BufferedImage baseImage;
|
||||||
|
BufferedImage qrcodeImage;
|
||||||
|
try {
|
||||||
|
baseImage = ImageIO.read(info.getOriginalFile());
|
||||||
|
qrcodeImage = ImageIO.read(info.getQrcodeFile());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ImageWatermarkException("图片打开失败");
|
||||||
|
}
|
||||||
|
// 新图像画布
|
||||||
|
BufferedImage newImage = new BufferedImage(baseImage.getWidth() + 2 * EXTRA_BORDER_PX, baseImage.getHeight() + 2 * EXTRA_BORDER_PX, BufferedImage.TYPE_INT_RGB);
|
||||||
|
Graphics2D g2d = newImage.createGraphics();
|
||||||
|
g2d.setColor(BG_COLOR);
|
||||||
|
g2d.fillRect(0, 0, newImage.getWidth(), newImage.getHeight());
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
g2d.drawImage(baseImage, EXTRA_BORDER_PX, EXTRA_BORDER_PX, null);
|
||||||
|
int newQrcodeHeight = QRCODE_SIZE;
|
||||||
|
int newQrcodeWidth = (int) (newQrcodeHeight * 1.0 / qrcodeImage.getHeight() * qrcodeImage.getWidth());
|
||||||
|
Font scenicFont = new Font(defaultFontName, Font.PLAIN, SCENIC_FONT_SIZE);
|
||||||
|
Font datetimeFont = new Font(defaultFontName, Font.PLAIN, DATETIME_FONT_SIZE);
|
||||||
|
FontMetrics scenicFontMetrics = g2d.getFontMetrics(scenicFont);
|
||||||
|
FontMetrics datetimeFontMetrics = g2d.getFontMetrics(datetimeFont);
|
||||||
|
int scenicLineHeight = scenicFontMetrics.getHeight();
|
||||||
|
int dtLineHeight = datetimeFontMetrics.getHeight();
|
||||||
|
int scenicLineWidth = scenicFontMetrics.stringWidth(info.getScenicLine());
|
||||||
|
int datetimeLineWidth = scenicFontMetrics.stringWidth(info.getDatetimeLine());
|
||||||
|
int offsetX = (newImage.getWidth() - newQrcodeWidth - QRCODE_OFFSET_X - Math.max(scenicLineWidth, datetimeLineWidth)) / 2;
|
||||||
|
int offsetY = EXTRA_BORDER_PX + baseImage.getHeight() - OFFSET_Y - newQrcodeHeight;
|
||||||
|
g2d.drawImage(qrcodeImage, offsetX, offsetY, newQrcodeWidth, newQrcodeHeight, null);
|
||||||
|
g2d.setFont(scenicFont);
|
||||||
|
g2d.setColor(scenicColor);
|
||||||
|
g2d.drawString(info.getScenicLine(), offsetX + newQrcodeWidth + QRCODE_OFFSET_X, offsetY + scenicLineHeight + FONT_GLOBAL_OFFSET_PERCENT * scenicLineHeight);
|
||||||
|
g2d.setFont(datetimeFont);
|
||||||
|
g2d.setColor(datetimeColor);
|
||||||
|
g2d.drawString(info.getDatetimeLine(), offsetX + newQrcodeWidth + QRCODE_OFFSET_X, offsetY + scenicLineHeight + dtLineHeight + FONT_GLOBAL_OFFSET_PERCENT * dtLineHeight);
|
||||||
|
try {
|
||||||
|
ImageIO.write(newImage, "png", info.getWatermarkedFile());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ImageWatermarkException("图片保存失败");
|
||||||
|
} finally {
|
||||||
|
g2d.dispose();
|
||||||
|
}
|
||||||
|
return info.getWatermarkedFile();
|
||||||
|
}
|
||||||
|
}
|
BIN
src/main/resources/PingFang_SC.ttf
Normal file
BIN
src/main/resources/PingFang_SC.ttf
Normal file
Binary file not shown.
BIN
src/main/resources/zt-logo.png
Normal file
BIN
src/main/resources/zt-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Reference in New Issue
Block a user