画画的
This commit is contained in:
parent
072a1a6131
commit
0ae47d3f71
12
pom.xml
12
pom.xml
@ -24,6 +24,7 @@
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<knife4j-spring-boot-starter.version>2.0.7</knife4j-spring-boot-starter.version>
|
||||
<pagehelper.version>5.3.1</pagehelper.version>
|
||||
<tomcat.version>9.0.102</tomcat.version>
|
||||
<!--跳过单元测试-->
|
||||
<skipTests>true</skipTests>
|
||||
</properties>
|
||||
@ -198,6 +199,17 @@
|
||||
</dependencies>
|
||||
|
||||
<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>
|
||||
<plugin>
|
||||
<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);
|
||||
}
|
||||
}
|
7
src/main/java/com/ycwl/basic/image/watermark/exception/ImageWatermarkUnsupportedException.java
Normal file
7
src/main/java/com/ycwl/basic/image/watermark/exception/ImageWatermarkUnsupportedException.java
Normal file
@ -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 ![]() (image error) Size: 39 KiB |
Loading…
x
Reference in New Issue
Block a user