开发者社区> hresh> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

手把手教你如何通过Java给图片添加文字和图片水印(一)

简介: 手把手教你如何通过Java给图片添加文字和图片水印(一)
+关注继续查看

前言


最近工作上有个需求,动态生成一张图片,具体来说就是基于模版图片动态添加文字和图片(文字内容不同,图片数目不同),其中文字大小不全一样,且对位置有所要求。

本文将剖析多个技术方案来实现水印生成,并最终抉择出最优方案。


技术分析


基于模版图片动态添加文字和图片,需要先调研一下有哪些技术方案,可能添加文字和图片的技术不同。


Graphics2D



利用 JDK 自带的 Graphics2D ,该类扩展 Graphics 类,以提供对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制。它是用于在 Java(tm) 平台上呈现二维形状、文本和图像的基础类。


Thumbnailator



使用第三方 Jar 包 Thumbnailator:使用第三方 Jar 包还是比较简单的,在 Thumbnailator 中已有相应的API了,只需阅读官方的文档即可实现。


GraphicsMagick与Im4Java


ImageMagick 是一个免费的创建、编辑、合成图片的开源软件。它可以读取、转换、写入多种格式的图片。图片切割、颜色替换、各种效果的应用,图片的旋转、组合,文本,直线,多边形,椭圆,曲线,附加到图片伸展旋转。


ImageMagick 是个图片处理工具,可以安装在绝大多数的平台上使用,Linux、Mac、Windows 都没有问题。GraphicsMagick 是在ImageMagick 基础上的另一个项目,大大提高了图片处理的性能,在 Linux 平台上,可以使用命令行的形式处理图片。关于 ImageMagick 在不同环境的安装教程,推荐阅读这篇文章


开源社区针对 ImageMagick 开发了两款 Java API,分别是 JMagick 和 Im4Java,两者的区别如下:


  • JMagick 是一个开源API,利用 JNI(Java Native Interface)技术实现了对 ImageMagick API 的 Java 访问接口,因此也将比纯 Java 实现的图片操作函数在速度上要快。JMagick 只实现了 ImageMagicAPI 的一部分功能,它的发行遵循LGPL协议。
  • Im4java 是 ImageMagick 的另一个 Java 开源接口。与 JMagick 不同之处在于 Im4java 只是生成与ImageMagick相对应的命令行,然后将生成的命令行传至选中的 ImageCommand(使用java.lang.ProcessBuilder.start()实现)来执行相应的操作。它支持大部分ImageMagick 命令,可以针对不同组的图片多次复用同一个命令行。


Im4java 支持 GraphicsMagick,GraphicsMagick 是 ImageMagick 的分支。相对 ImageMagick ,GraphicsMagick 更稳定,消耗资源更少。最重要的是不依赖 dll 环境,且性能更好,所以我们选择使用 Im4java,想要使用该 API,那么本机上就需要安装 GraphicsMagick。


本人尝试在 Mac 上安装 GraphicsMagick,推荐阅读 Mac 安装 GraphicsMagick ,这里补充一点个人安装时的经验。


Mac 可以使用 brew 命令:


brew install libpng
brew install libjpeg
#通过 brew 安装 GraphicsMagick(libpng 等依赖包会一并下载)
brew install graphicsmagick
复制代码


查看 GraphicsMagick 的版本以及安装路径:


% gm -version
GraphicsMagick 1.3.38 2022-03-26 Q16 http://www.GraphicsMagick.org/
......
Configured using the command:
  ./configure  '--prefix=/usr/local/Cellar/graphicsmagick/1.3.38_1' '--disable-dependency-tracking' '--disable-openmp' '--disable-static' '--enable-shared' '--with-modules' '--with-quantum-depth=16' '--without-lzma' '--without-x' '--without-gslib' '--with-gs-font-dir=/usr/local/share/ghostscript/fonts' '--without-wmf' 'CC=clang' 'CXX=clang++' 'PKG_CONFIG_PATH=/usr/local/opt/libpng/lib/pkgconfig:/usr/local/opt/freetype/lib/pkgconfig:/usr/local/opt/jpeg-turbo/lib/pkgconfig:/usr/local/opt/jasper/lib/pkgconfig:/usr/local/opt/libtiff/lib/pkgconfig:/usr/local/opt/little-cms2/lib/pkgconfig:/usr/local/opt/webp/lib/pkgconfig' 'PKG_CONFIG_LIBDIR=/usr/lib/pkgconfig:/usr/local/Homebrew/Library/Homebrew/os/mac/pkgconfig/11'
.....
复制代码


测试效果


% gm identify /Users/xxxx/Downloads/certificate_blank.jpg
/Users/xxx/Downloads/certificate_blank.jpg JPEG 453x640+0+0 DirectClass 8-bit 64.1Ki 0.000u 0m:0.000003s
复制代码


技术方案

Graphics2D


文字水印

public class Graphics2DUtil {
  private static final String FONT_FAMILY = "楷体";
  private static final String CERTIFICATE_BASE_PATH = "/src/main/resources/static/certificate-blank.png";
  private static final String WATERMARK_IMAGE_PATH = "/src/main/resources/static/icon.png";
  
  public static void graphics2DDrawTest(String srcImgPath, String outPath) {
    try {
      BufferedImage targetImg = ImageIO.read(new File(srcImgPath));
      int imgWidth = targetImg.getWidth();
      int imgHeight = targetImg.getHeight();
      BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight,
          BufferedImage.TYPE_INT_BGR);
      Graphics2D g = bufferedImage.createGraphics();
      g.drawImage(targetImg, 0, 0, imgWidth, imgHeight, null);
      g.setColor(Color.BLACK);
      // 第一行文本字体大小为120,居中显示
      Font userNameFont = new Font(FONT_FAMILY, Font.PLAIN, 120);
      g.setFont(userNameFont);
      String userName = "hresh";
      int[] userNameSize = getContentSize(userNameFont, userName);
      int userNameLeftMargin = (imgWidth - userNameSize[0]) / 2;
      int userNameTopMargin = 400 + userNameSize[1];
      g.drawString(userName, userNameLeftMargin, userNameTopMargin);
      g.dispose();
      FileOutputStream outImgStream = new FileOutputStream(outPath);
      ImageIO.write(bufferedImage, "png", outImgStream);
      g.dispose();
    } catch (IOException e) {
      e.getStackTrace();
    }
  }
  /**
   * 获取文本的长度,字体大小不同,长度也不同
   *
   * @param font
   * @param content
   * @return
   */
  public static int[] getContentSize(Font font, String content) {
    int[] contentSize = new int[2];
    FontRenderContext frc = new FontRenderContext(new AffineTransform(), true, true);
    Rectangle rec = font.getStringBounds(content, frc).getBounds();
    contentSize[0] = (int) rec.getWidth();
    contentSize[1] = (int) rec.getHeight();
    return contentSize;
  }
  public static void main(String[] args) throws IOException {
    String projectPath = System.getProperty("user.dir");
    String srcImgPath = projectPath + CERTIFICATE_BASE_PATH;
    String outPath = projectPath + "/src/main/resources/static/out/image_by_graphics2D.png";
    graphics2DDrawTest(srcImgPath, outPath);
  }
}
复制代码


执行效果如下:


1.jpg


上述代码中的 getContentSize()方法,根据 Font 和文本内容获取文本的宽度和高度,进一步可以知道文本中每个字符的宽高,如果文本需要换行,离不开字符的宽高数据。除了上述获取文本宽高的实现方式,还有一种实现方式,不过不推荐使用。


FontMetrics fm = sun.font.FontDesignMetrics.getMetrics(font);
int width = fm.stringWidth(content);
int height = fm.getHeight();
复制代码


因为 sun.font.FontDesignMetrics 在未来的版本可能会被删除掉,本人目前还是使用 JDK8。


图片水印


public static void graphics2DDrawTest(String srcImgPath, String waterImgPath, String outPath) {
    FileOutputStream outputStream = null;
    try {
      BufferedImage targetImg = ImageIO.read(new File(srcImgPath));
      int imgWidth = targetImg.getWidth();
      int imgHeight = targetImg.getHeight();
      BufferedImage bufferedImage = new BufferedImage(imgWidth, imgHeight,
          BufferedImage.TYPE_INT_BGR);
      Graphics2D g = bufferedImage.createGraphics();
      g.drawImage(targetImg, 0, 0, imgWidth, imgHeight, null);
      g.setColor(Color.BLACK);
      BufferedImage icon = ImageIO.read(new File(waterImgPath));
      g.drawImage(icon, 350, 600, icon.getWidth(),
          icon.getHeight(), null);
      FileOutputStream outImgStream = new FileOutputStream(outPath);
      ImageIO.write(bufferedImage, "png", outImgStream);
      g.dispose();
    } catch (IOException e) {
      e.getStackTrace();
    } finally {
      try {
        if (outputStream != null) {
          outputStream.flush();
          outputStream.close();
        }
      } catch (Exception e) {
        e.getStackTrace();
      }
    }
  }
复制代码


执行效果如下:


2.jpg


Thumbnailator


图片水印

Thumbnailator 不支持文字水印,只能测试一下图片水印的效果。


public class ThumbnailsUtil {
  private static final String CERTIFICATE_BASE_PATH = "/src/main/resources/static/certificate-blank.png";
  private static final String WATERMARK_IMAGE_PATH = "/src/main/resources/static/icon.png";
  public static void addImgWaterMark(String srcImagePath, String waterImgPath,
      String outPath) throws IOException {
    // 原始图片信息
    BufferedImage targetImg = ImageIO.read(new File(srcImagePath));
    // 水印图片
    BufferedImage watermarkImage = ImageIO.read(new File(waterImgPath));
    int height = targetImg.getHeight();
    int width = targetImg.getWidth();
    System.out.println("width:" + width + "; height:" + height);
    // 可以自定义坐标位置
    int x = 600;
    int y = 600;
    Coordinate coordinate = new Coordinate(x, y);
    Thumbnails.of(targetImg).size(width, height)
//        .watermark(Positions.CENTER, watermarkImage, 1f)  // 0.5f表示透明度,最大值为1
        .watermark(coordinate, watermarkImage, 1f)  // 0.5f表示透明度,最大值为1
        .outputQuality(1)   // 图片质量,最大值为1
        .toFile(new File(outPath));
  }
  public static void main(String[] args) throws IOException {
    String projectPath = System.getProperty("user.dir");
    String srcImgPath = projectPath + CERTIFICATE_BASE_PATH;
    String waterImgPath = projectPath + WATERMARK_IMAGE_PATH;
    String outPath =
        projectPath + "/src/main/resources/static/out/img_water_image_by_thumbnails.png";
    addImgWaterMark(srcImgPath, waterImgPath, outPath);
  }
}
复制代码


执行效果如下:


3.jpg


GraphicsMagick与Im4Java


文字水印


GraphicsMagick 生成文字水印的命令如下:


gm convert -font ${fontType} -fill ${color} -pointsize ${fontSize} -draw "text ${dx},${dy} '${textContent}'" ${sourceImgPath} ${distImgPath}
复制代码


参数含义如下:

  • fontType:字体类型;
  • color:字体颜色;
  • fontSize:字体大小;
  • dx:水印x轴位置
  • dy:水印y轴位置
  • textContent:水印内容
  • sourceImgPath:源文件路径
  • distImgPath:目标文件路径


我们先尝试使用命令来生成文字水印,看看能否成功。


gm convert -font /System/Library/Fonts/Supplemental/Songti.ttc -fill red -pointsize 50 -draw "text 400,500 '你好'" certificate-blank.png test.png
复制代码


结果如下,可以正常输出中文水印。


4.jpg


接下来我们试试在代码中使用 ImageMagick 来生成文字水印:


public class Im4JavaUtil {
  private static final int[] ICON_LEFT_MARGINS = new int[]{552, 467, 395};
  private static final String FONT_FAMILY = "楷体";
  private static final String CERTIFICATE_BASE_PATH = "/src/main/resources/static/certificate-blank.png";
  private static final String WATERMARK_IMAGE_PATH = "/src/main/resources/static/icon.png";
  // 是否使用 GraphicsMagick
  private static final boolean IS_USE_GRAPHICS_MAGICK = true;
  // 本机上graphicsmagick的安装位置
  private static final String GRAPHICS_MAGICK_PATH = "/usr/local/Cellar/graphicsmagick/1.3.38_1/bin";
  /**
   * 命令类型
   */
  private enum CommandType {
    convert("转换处理"), identify("图片信息"), textWaterMark("文字水印"), imageWaterMark("图片水印");
    private String name;
    CommandType(String name) {
      this.name = name;
    }
  }
  private static ImageCommand getImageCommand(CommandType command) {
    ImageCommand cmd = null;
    switch (command) {
      case convert:
      case textWaterMark:
        cmd = new ConvertCmd(IS_USE_GRAPHICS_MAGICK);
        break;
      case identify:
        cmd = new IdentifyCmd(IS_USE_GRAPHICS_MAGICK);
        break;
      case imageWaterMark:
        cmd = new CompositeCmd(IS_USE_GRAPHICS_MAGICK);
        break;
    }
    cmd.setSearchPath(GRAPHICS_MAGICK_PATH);
    return cmd;
  }
  public static void addTextWatermark(String srcImagePath, String destImagePath, String content)
      throws Exception {
    GMOperation op = new GMOperation();
    op.font("/System/Library/Fonts/Supplemental/Songti.ttc");
    // 文字方位-居中
    op.gravity("center");
    op.pointsize(120).fill("#BCBFC8").draw("text 0,0 '" + content + "'").quality(90.0);
    // 原图
    op.addImage();
    // 目标
    op.addImage();
    ImageCommand cmd = getImageCommand(CommandType.textWaterMark);
    cmd.run(op, srcImagePath, destImagePath);
  }
  public static void main(String[] args) throws Exception {
    String projectPath = System.getProperty("user.dir");
    String srcImgPath = projectPath + CERTIFICATE_BASE_PATH;
    String outPath = projectPath + "/src/main/resources/static/out/text_water_image_by_im4.png";
    String content = "中国";
    addTextWatermark(srcImgPath, outPath, content);
  }
}
复制代码


上述代码看起来比较简单,需要注意的是 op.draw()方法中的内容,尤其是单引号,一定不能漏掉,如果不加单引号,中文文字水印会乱码。


图片水印


gm composite -gravity ${gravity} -dissolve ${dissolve} -geometry +${dx}+${dy} ${waterImgPath} ${sourceImgPath} ${distImgPath}
复制代码


参数含义如下:

  • gravity:水印相对位置,
  • dissolve:水印透明度
  • dx:水印距离右边缘的距离
  • dy:水印距离下边缘的距离
  • waterImgPath:水印图片路径
  • sourceImgPath:源图片路径
  • distImgPath:目标图片路径


关于 gravity 属性,值范围如下:


5.jpg


测试一下


gm composite -gravity Center -dissolve 90 -geometry +50+$50 /Users/xxx/IdeaProjects/java_deep_learning/src/main/resources/static/icon.png /Users/xxx/IdeaProjects/java_deep_learning/src/main/resources/static/certificate-blank.png /Users/xxx/IdeaProjects/java_deep_learning/src/main/resources/static/out/img_water_image.png
复制代码


生成的图片如下所示:


6.jpg


我们最后还是用代码来试一下效果:


public static void addImgWatermark(String srcImagePath, String destImagePath, String waterImgPath)
  throws Exception {
  // 原始图片信息
  BufferedImage targetImg = ImageIO.read(new File(srcImagePath));
  // 水印图片
  BufferedImage watermarkImage = ImageIO.read(new File(waterImgPath));
  int w = targetImg.getWidth();
  int h = targetImg.getHeight();
  IMOperation op = new IMOperation();
  // 水印图片位置
  op.geometry(watermarkImage.getWidth(null), watermarkImage.getHeight(null),
              w - watermarkImage.getWidth(null) - 300, h - watermarkImage.getHeight(null) - 100);
  // 水印透明度
  op.dissolve(90);
  // 水印
  op.addImage(waterImgPath);
  // 原图
  op.addImage(srcImagePath);
  // 目标
  op.addImage(destImagePath);
  ImageCommand cmd = getImageCommand(CommandType.imageWaterMark);
  cmd.run(op);
}
复制代码


执行效果如下:


7.jpg


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
java 图片水印处理类
java 图片水印处理类
87 0
Java开发——Redis云管理平台 实现方案CacheCloud 扫盲
本文简述一个redis云平台解决方案,该方案是搜狐TV云平台管理的解决方案CacheCloud ,感兴趣的可以持续关注我的发布。
1835 0
Java 语言实现的 I/O 模型
1.概述 1.发展历史 JDK 1.4 之前,Java 所提供的网络编程API全部采用了I/O同步阻塞模型 JDK 1.4 引入了非阻塞I/O(NIO)类库,自此Java语言可以支持多路复用I/O模型 JDK 1.7 引入了异步I/O编程类库,被称为NIO2,也叫AIO 2.Java NIO 开源网络通信框架 Mina:Apache Grizzly: Netty:JBoss。
1033 0
实现Java集合迭代的高性能
版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 https://blog.csdn.net/chszs/article/details/81042018 实现Java集合迭代的高性能 2018.7.14 版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。
773 0
面试 5:手写 Java 的 pow() 实现
我们在处理一道编程面试题的时候,通常除了注意代码规范以外,千万要记得自己心中模拟一个单元测试。主要通过三方面来处理。 功能性测试 边界值测试 负面性测试 不管如何,一定要保证自己代码考虑的全面,而不要简单地猜想用户的输入一定是正确的,只是去实现功能。
1301 0
JAVA实现微信支付V3
喜欢的朋友可以关注下,粉丝也缺。 相信很多的码友在项目中都需要接入微信支付,虽说微信支付已成为一个普遍的现象,但是接入的过程中难免会遇到各种各样的坑,这一点支付宝的SDK就做的很好,已经完成的都知道了。
1564 0
面试:用 Java 实现一个 Singleton 模式
面试系列更新后,终于迎来了我们的第一期,我们也将贴近《剑指 Offer》的题目给大家带来 Java 的讲解,个人还是非常推荐《剑指 Offer》作为面试必刷的书籍的,这不,再一次把这本书分享给大家,PDF 版本在公众号后台回复「剑指Offer」即可获取。
1291 0
面试:Java 实现查找旋转数组的最小数字
在算法面试中,面试官总是喜欢围绕链表、排序、二叉树、二分查找来做文章,而大多数人都可以跟着专业的书籍来做到倒背如流。而面试官并不希望招收的是一位记忆功底很好,但不会活学活用的程序员。
1103 0
Dockerfile配置APM监控实现Java容器的性能监控
通过Dockerfile可以用来构建容器镜像,我们一般也是通过这种方式来构建一个Tomcat应用服务容器,如果要实现对容器中的Tomcat服务(或是其他Java应用)进行APM(应用性能管理)监控,就需要我们在容器中放置javaagent并做相关配置,而在已生成的容器中修改配置不符合容器管理的规范,所以我们建议在发布镜像时就将javaagent植入,这样在生成容器时就可以通过环境变量参数来决定是否开启监控。
1495 0
+关注
hresh
分享技术,记录人生
文章
问答
文章排行榜
最热
最新
相关电子书
更多
yqdh_58c13113597...1510470399.pdf
立即下载
yqdh_58c1310f3e1...1510467885.pdf
立即下载
59e71e6620d081509749879.pdf
立即下载