码迷,mamicode.com
首页 > 其他好文 > 详细

GraphicsMagick文字水印及中文乱码问题解决

时间:2021-06-30 18:10:21      阅读:0      评论:0      收藏:0      [点我收藏+]

标签: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
      技术图片

问题分析

  • 首先出现中文乱码应该是我们本机环境的字体库问题,但是我mac电脑上的字体集是支持中文字体的,更换字体类型并不能解决这个问题。
  • 目前文字水印的命令是否可以满足需求?
    • 查看文档,没有找到可以指定文字透明度
    • 文字位置对比现有服务商的水印位置有出入:
  • 总结: 目前根据官方文档,并未找到对于文字水印透明度的参数设置,以及文字水印的位置与预期位置相差较大。这种方式无法满足需求,而且需要解决中文乱码问题,所以暂时不再往文字水印命令的方向去尝试。目前的想法是:先根据文字生成透明的临时图片,再将临时图片作为图片水印打到原图上,这种方式可以设置透明度。

图片水印

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

技术图片

位置对了! 那接下来就是解决中文乱码问题了,生成临时文字图片还是需要解决字体库问题。

代码实现

1、安装字体库

为了方便管理,我直接把需要的字体集放到项目中,在项目启动时,将字体集拷贝到操作系统,并进行 Map 映射。

技术图片

字体集文件需要可以自取:

/**
 * 生成默认的中文字体
 */
@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;
    }

}

2、将文字转换为透明的临时图片

逛了一圈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());
    }

}

3、使用GM合成图片

  • 将水印文字生成透明的背景图片
    • 提交任务到线程池,并获取结果
  • 使用GM合成图片
    // 将文字生成图片,再合成
    // 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());

4、最终结果

测试需求: 文字位置:(100, 100)、文字内容:“你好,世界”,透明度:60%,颜色:ff0000,文字类型:黑体,文字大小:40px。

技术图片

技术图片

GraphicsMagick文字水印及中文乱码问题解决

标签:stat   rect   初始化   ecb   byte   ORC   透明度   point   html   

原文地址:https://www.cnblogs.com/Shanbw/p/14952589.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!