标签:stat rect 初始化 ecb byte ORC 透明度 point html
需求背景: 图片服务需要提供文字水印功能。支持自定义字体颜色、类型、透明度以及水印位置,对外暴露的字体类型是:楷体、宋体、黑体。
这需求看似很简单,其实会有很多坑。下面一起来看下。下面是个人在实现过程中遇到的问题,在查看官方文档以及相应博客下并没有找到很好的解决办法,如果其他大佬有更好的实现,欢迎在评论区一起讨论。
官方文档:
gm convert -font ${fontType} -fill ${color} -pointsize ${fontSize} -draw "text ${dx},${dy} ‘${textContent}‘" ${sourceImgPath} ${distImgPath}
参数 | 定义 |
---|---|
fontType | 字体类型 |
color | 字体颜色 |
fontSize | 字体大小 |
dx | 水印位置 |
dy | 水印位置 |
textContent | 文字内容 |
sourceImgPath | 源图片路径 |
distImgPath | 目标图片路径 |
原图外网访问地址:https://cvte-dev-public.seewo.com/shan-test/f60e65b7440840708c3bc827d69adcca
英文文字水印
gm convert -font Helvetica -fill red -pointsize 50 -draw "text 100,100 ‘Hello World‘" example.jpg test.jpg
中文文字水印(乱码)
gm convert -font Helvetica -fill red -pointsize 50 -draw "text 100,100 ‘你好‘" example.jpg test.jpg
gm composite -gravity ${gravity} -dissolve ${dissolve} -geometry +${dx}+${dy} ${tmpImgPath} ${sourceImgPath} ${distImgPath}
参数 | 定义 | 参数范围 |
---|---|---|
gravity | 水印相对位置 | NorthWest:左上 North:中上 NorthEast:右上 West:左中 Center:中部 East:右中 SouthWest:左下 South:中下 SouthEast:右下 |
dissolve | 水印透明度 | [1, 100] 默认值:100,不透明 |
dx | 指定水印的水平边距, 即距离图片边缘的水平距离。这个参数只有当水印位置是左上、左中、左下、右上、右中、右下才有意义 | |
dy | 指定水印的垂直边距,即距离图片边缘的垂直距离, 这个参数只有当水印位置是左上、中上、右上、左下、中下、右下才有意义 | |
tmpImgPath | 临时图片的路径 | |
sourceImgPath | 源图片路径 | |
distImgPath | 目标图片路径 |
事先生成了临时文件(后面再教你们怎么玩):text.png
图片处理命令:gm composite -gravity NorthWest -dissolve 100 -geometry +100+100 text.png example.jpg test.jpg
位置对了! 那接下来就是解决中文乱码问题了,生成临时文字图片还是需要解决字体库问题。
为了方便管理,我直接把需要的字体集放到项目中,在项目启动时,将字体集拷贝到操作系统,并进行 Map 映射。
字体集文件需要可以自取:
- HeiTi.ttc(黑体)
- KaiTi.ttf(楷体)
- SongTi.ttc(宋体)
/**
* 生成默认的中文字体
*/
@Component
@Slf4j
public class ChineseFontComponent {
private static Map<String, Font> chineseFontMap;
@PostConstruct
private void initFont() throws IOException {
chineseFontMap = new HashMap<>();
String heiTiFontPath = "font/HeiTi.ttc";
String kaiTiFontPath = "font/KaiTi.ttf";
String songTiFontPath = "font/SongTi.ttc";
createChineseFont("HeiTi", heiTiFontPath);
createChineseFont("KaiTi", kaiTiFontPath);
createChineseFont("SongTi", songTiFontPath);
}
/**
* 创建中文字体
*
* @param key 字体类型的key
* @param fontPath 字体路径
*/
private static void createChineseFont(String key, String fontPath) throws IOException {
log.info("初始化中文字体: {}, path: {}", key, fontPath);
InputStream fisInJar = new ClassPathResource(fontPath).getInputStream();
File file = File.createTempFile("Font-", ".ttf");
//将jar包里的字体文件复制到操作系统的目录里
OutputStream fosInOs = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int readLength = fisInJar.read(buffer);
while (readLength != -1) {
fosInOs.write(buffer, 0, readLength);
readLength = fisInJar.read(buffer);
}
IOUtils.closeQuietly(fosInOs);
IOUtils.closeQuietly(fisInJar);
Font font;
try {
font = Font.createFont(Font.TRUETYPE_FONT, file);
} catch (Exception e) {
log.error("加载默认中文字体失败", e);
throw new ImageServiceException(ErrorCode.COMMON_SERVER_ERROR, "加载默认中文字体失败: "+e.getMessage());
}
chineseFontMap.put(key, font);
log.info("初始化中文字体完成: {}", key);
}
/**
* 获取中文字体
*/
public Font getChineseFont(String key) {
Font font = chineseFontMap.get(key);
return font == null ? chineseFontMap.get(FontTypeEnum.KaiTi.toString()) : font;
}
}
逛了一圈GraphicsMagick
官网,没看到有将文字转换为图片的操作,这里使用JDK自带的ImageIO
生成临时文件。
/**
* 将文字转换为图片(解决中文字符乱码以及文字透明度问题)
*
* @param text 文本内容
* @param fontType 字体类型(HeiTi、KaiTi、SongTi)
* @param fontSize 字体大小
* @param fontColor 字体颜色
* @param outFile 输出文件路径
*/
private boolean convertFontToImage(String text, String fontType, Integer fontSize, String fontColor, String outFile) {
long startTime = System.currentTimeMillis();
Color textColor = Color.BLACK;
if (StringUtils.isNotBlank(fontColor)) {
textColor = new Color(Integer.valueOf(fontColor, 16));
}
// 获取字体库映射字体集
Font font = chineseFontComponent.getChineseFont(fontType).deriveFont(Font.PLAIN, fontSize);
File file = new File(outFile);
// 获取font的样式应用在str上的整个矩形
Rectangle2D r = font.getStringBounds(text, new FontRenderContext(AffineTransform.getScaleInstance(1, 1),false,false));
// 获取单个字符的高度
int unitHeight = (int) Math.floor(r.getHeight());
// 获取整个str用了font样式的宽度这里用四舍五入后+1保证宽度绝对能容纳这个字符串作为图片的宽度
int width = (int)Math.round(r.getWidth())+1;
// 把单个字符的高度+3保证高度绝对能容纳字符串作为图片的高度
int height = unitHeight+3;
// 创建图片
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
image = g2d.getDeviceConfiguration().createCompatibleImage(width, height, Transparency.TRANSLUCENT);
g2d.dispose();
g2d = image.createGraphics();
g2d.setStroke(new BasicStroke(1));
g2d.setColor(textColor); // 在换成所需要的字体颜色
g2d.setFont(font);
g2d.drawString(text, 0, font.getSize());
try {
ImageIO.write(image, "png", file); // 输出png图片
} catch (IOException e) {
log.error("文字水印生成临时文件出现异常", e);
return false;
}
log.info("生成临时文件时间:{} ms", (System.currentTimeMillis()-startTime));
return true;
}
??注意: ImageIO在高并发的情况下容易造成OOM,所以生成临时文件这个操作需要使用线程池控制并发数。
/**
* 线程池配置类
*/
@Configuration
@Slf4j
public class ThreadPoolExecutorConfig {
private int textWaterMarkThreadPoolSize = 100;
private int textWaterMarkThreadPoolQueueSize = 1000;
/**
* 文字水印线程池
*/
@Bean(name = "textWaterMarkThreadPool")
public ExecutorService getTextWaterMarkThreadPoolTaskServiceExecutor(){
log.info("textWaterMarkThreadPool线程池开始初始化,线程池中线程数:{}", textWaterMarkThreadPoolSize);
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("TextWaterMark-task-%d").build();
return new ThreadPoolExecutor(textWaterMarkThreadPoolSize, textWaterMarkThreadPoolSize, 0L,
TimeUnit.SECONDS, new LinkedBlockingDeque<>(textWaterMarkThreadPoolQueueSize),
threadFactory, new ThreadPoolExecutor.AbortPolicy());
}
}
// 将文字生成图片,再合成
// 1、生成透明的背景图片
File tmpTextFile = File.createTempFile("TmpText-", ".png");
boolean res = textWaterMarkThreadPool.submit(() -> this.convertFontToImage(text, fontType, fontSize, color, tmpTextFile.getAbsolutePath())).get();
if (! res) {
throw new ImageServiceException(ErrorCode.IMAGE_HANDLER_ERROR, "文字水印生成临时文件异常");
}
// 2、合成图片
CompositeCommand compositeCommand = new CompositeCommand();
compositeCommand.watermarkImg(gravityEnum, dx, dy, dissolve, tmpTextFile.getAbsolutePath()).addImage(sourceImg, distPath);
pooledGMService.execute(compositeCommand.getCmdArgs());
测试需求: 文字位置:(100, 100)、文字内容:“你好,世界”,透明度:60%,颜色:ff0000,文字类型:黑体,文字大小:40px。
标签:stat rect 初始化 ecb byte ORC 透明度 point html
原文地址:https://www.cnblogs.com/Shanbw/p/14952589.html