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

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

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

问题记录


1、代码中使用 ImageMagick 字体加载异常


org.im4java.core.CommandException: /usr/local/Cellar/graphicsmagick/1.3.38_1/bin/gm convert: Unable to read font (/usr/local/share/ghostscript/fonts/n019003l.pfb) [No such file or directory].
复制代码


原因:使用 Homebrew 安装了 ImageMagick,缺少 ghostscript。


解决方案:


brew install ghostscript
复制代码


如果您使用的是 Ubuntu 或在带有 Ubuntu 的 docker 中删除并重新安装 ghostscript 包将解决您的问题。


apt remove ghostscript
apt install ghostscript
复制代码


技术实现


上面展示了三种技术的简单实现,可以直观的看到,Graphics2D 和 Im4Java 能够实现文字和图片水印,但 Im4Java 需要额外安装 GraphicsMagick,不管是 Windows 还是 Mac,又或者是 Linux 上,都可以事先安装 GraphicsMagick,那么我们姑且认为 Im4Java 也满足基本需要。


回头看看我们的需求:文字水印可能会有多行,且字体大小和样式可能存在不同,图片水印可能有多个,这些条件无疑增加了实现难度。


Graphics2D


public class Graphics2DUtil {
  private static final int[] ICON_LEFT_MARGINS = new int[]{400, 100};
  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 waterImgPath, String outPath,
                                        String[] iconNums) {
    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();
      // 第二行文本的字体不一样,居中显示
      g = (Graphics2D) bufferedImage.getGraphics();
      Font secondFont = new Font(FONT_FAMILY, Font.PLAIN, 72);
      g.setFont(secondFont);
      g.setColor(Color.BLUE);
      String content = "Hello World";
      int[] contentSize = getContentSize(secondFont, content);
      int contentLeftMargin = (imgWidth - contentSize[0]) / 2;
      int contentTopMargin = 600 + contentSize[1];
      g.drawString(content, contentLeftMargin, contentTopMargin);
      int imgLeftMargin = ICON_LEFT_MARGINS[iconNums.length - 1];
      int imgTopMargin = 1000;
      BufferedImage image = ImageIO.read(new File(waterImgPath));
      int[] imgSize = getImgSize(image);
      for (int i = 0; i < iconNums.length; i++) {
        if (i > 0) {
          imgLeftMargin = imgLeftMargin + imgSize[0] + 10;
        }
        BufferedImage icon = ImageIO.read(new File(waterImgPath));
        g.drawImage(icon, imgLeftMargin, imgTopMargin, icon.getWidth(),
                    icon.getHeight(), null);
      }
      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();
    }
  }
  /**
   * 获取文本的长度,字体大小不同,长度也不同
   *
   * @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;
  }
  /**
   * 获取图片的宽和高
   *
   * @param img
   * @return
   */
  public static int[] getImgSize(BufferedImage img) {
    int[] imgSize = new int[2];
    imgSize[0] = img.getWidth(null); // 得到源图宽
    imgSize[1] = img.getHeight(null); // 得到源图高
    return imgSize;
  }
  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/image_by_graphics2D.png";
    // 假设图片水印背景图不一样,1对应1.png,2对应2.png
    String[] iconNums = new String[]{"1", "2"};
    graphics2DDrawTest(srcImgPath, waterImgPath, outPath, iconNums);
  }
}
复制代码


执行效果如下:


8.jpg


从结果上来看,Graphics2D 可以满足我们的需求,如果文本内容过长,需要换行,又该如何做呢?


private static final int TEXT_AREA_WIDTH = 1150;
private static final int TEXT_AREA_LEFT_MARGIN = 100;
private static final int TEXT_AREA_RIGHT_MARGIN = 100;
private static void drawTextWithFontStyleLineFeed(Graphics2D g, String userName,
                                                      int imgWidth) {
  int userNameFontSize = 110;
  Font userNameFont = new Font(FONT_FAMILY, Font.PLAIN, userNameFontSize);
  int[] userNameSize = getContentSize(userNameFont, userName);
  if (userNameSize[0] > TEXT_AREA_WIDTH) {
    userNameFontSize = 80;
    userNameFont = new Font(FONT_FAMILY, Font.PLAIN, userNameFontSize);
  }
  if (Objects.equals(userNameFontSize, 80)) {
    g.setFont(userNameFont);
    userNameSize = getContentSize(userNameFont, userName);
    int userNameWidth = userNameSize[0];
    int wordWidth = userNameWidth / userName.length();
    int wordNum = TEXT_AREA_WIDTH / wordWidth;
    String userNameFirst = userName.substring(0, wordNum);
    String userNameSec = userName.substring(wordNum);
    int userNameFirstWidth = wordWidth * wordNum;
    int userNameFirstLeftMargin =
      (imgWidth - userNameFirstWidth - TEXT_AREA_LEFT_MARGIN - TEXT_AREA_RIGHT_MARGIN) / 2
      + TEXT_AREA_LEFT_MARGIN;
    int userNameFirstTopMargin = 700;
    g.drawString(userNameFirst, userNameFirstLeftMargin, userNameFirstTopMargin);
    int userNameSectWidth = wordWidth * (userName.length() - wordNum);
    int userNameSecLeftMargin =
      (imgWidth - userNameSectWidth - TEXT_AREA_LEFT_MARGIN - TEXT_AREA_RIGHT_MARGIN) / 2
      + TEXT_AREA_LEFT_MARGIN;
    int userNameSecTopMargin = 700 + userNameSize[1] + 70;
    g.drawString(userNameSec, userNameSecLeftMargin, userNameSecTopMargin);
  } else {
    g.setFont(userNameFont);
    userNameSize = getContentSize(userNameFont, userName);
    int userNameLeftMargin =
      (imgWidth - userNameSize[0] - TEXT_AREA_LEFT_MARGIN - TEXT_AREA_RIGHT_MARGIN) / 2
      + TEXT_AREA_LEFT_MARGIN;
    int userNameTopMargin = 363 * 3 + userNameSize[1] - 10;
    g.drawString(userName, userNameLeftMargin, userNameTopMargin);
    g.dispose();
  }
}
复制代码


用 drawUserNameWithFontStyleLineFeed()方法来替代 graphics2DDrawTest()方法中关于添加文字水印的代码,执行效果如下所示:


9.jpg


至此,关于 Graphics2D 的技术实现细节基本搞定了,也非常符合我们的要求。

Im4Java


首先是文字水印字体大小不同的问题,我们尝试修改代码来解决该问题。


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.gravity("center");
  op.pointsize(80).fill("#BCBFC8").draw("text 0,150 '" + content + "'").quality(90.0);
  // 原图
  op.addImage();
  // 目标
  op.addImage();
  ImageCommand cmd = getImageCommand(CommandType.textWaterMark);
  cmd.run(op, srcImagePath, destImagePath);
}
复制代码


执行效果如下:


10.jpg


接着是文本换行问题,实现起来还算简单。


private static final String FONT_FAMILY_PATH = "/src/main/resources/static/SourceHanSerif-Light.ttc";
public static void addTextWatermark(String srcImagePath, String destImagePath, String content)
      throws Exception {
    GMOperation op = new GMOperation();
    String projectPath = System.getProperty("user.dir");
    op.font(projectPath + FONT_FAMILY_PATH);
    String text = "这是一个专注于IT技术学习交流的个人技术博客网站";
    BufferedImage targetImg = ImageIO.read(new File(srcImagePath));
    int imgWidth = targetImg.getWidth();
    drawTextWithFontStyleLineFeed(op, text, imgWidth);
    // 原图
    op.addImage();
    // 目标
    op.addImage();
    ImageCommand cmd = getImageCommand(CommandType.textWaterMark);
    cmd.run(op, srcImagePath, destImagePath);
  }
  private static final int TEXT_AREA_WIDTH = 1150;
  private static final int TEXT_AREA_LEFT_MARGIN = 100;
  private static final int TEXT_AREA_RIGHT_MARGIN = 100;
  private static void drawTextWithFontStyleLineFeed(GMOperation op, String text, int imgWidth) {
    float textFontSize = 110f;
    int[] textSize = getContentSize(text, textFontSize);
    if (textSize[0] > TEXT_AREA_WIDTH) {
      textFontSize = 80f;
      textSize = getContentSize(text, textFontSize);
    }
    if (Objects.equals(textFontSize, 80f)) {
      int textWidth = textSize[0];
      int wordWidth = textWidth / text.length();
      int wordNum = TEXT_AREA_WIDTH / wordWidth;
      String textFirst = text.substring(0, wordNum);
      String textSec = text.substring(wordNum);
      int textFirstWidth = wordWidth * wordNum;
      int textFirstLeftMargin =
          (imgWidth - textFirstWidth - TEXT_AREA_LEFT_MARGIN - TEXT_AREA_RIGHT_MARGIN) / 2
              + TEXT_AREA_LEFT_MARGIN;
      int textFirstTopMargin = 50;
      op.gravity("west");
      op.pointsize(80).fill("#000000")
          .draw("text " + textFirstLeftMargin + "," + textFirstTopMargin + " '" + textFirst + "'")
          .quality(90.0);
      int textSectWidth = wordWidth * (text.length() - wordNum);
      int textSecLeftMargin =
          (imgWidth - textSectWidth - TEXT_AREA_LEFT_MARGIN - TEXT_AREA_RIGHT_MARGIN) / 2
              + TEXT_AREA_LEFT_MARGIN;
      int textSecTopMargin = 50 + textSize[1] + 10;
      op.gravity("west");
      op.pointsize(80).fill("#000000")
          .draw("text " + textSecLeftMargin + "," + textSecTopMargin + " '" + textSec + "'")
          .quality(90.0);
    }
  }
  private static Font getFont(float fontSize) {
    try {
      InputStream resourceAsStream = new ClassPathResource("static/SourceHanSerif-Light.ttc").getInputStream();
      Font font = Font.createFont(Font.TRUETYPE_FONT, resourceAsStream);
      return font.deriveFont(fontSize);
    } catch (Exception e) {
      log.error(e.getMessage());
    }
    return new Font(FONT_FAMILY, Font.PLAIN, 120);
  }
  public static int[] getContentSize(String content, float fontSize) {
    Font font = getFont(fontSize);
    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;
  }
复制代码


执行效果如下:


11.jpg


还有多个图片水印的问题,勉强可以实现,但想要实现动态化比较繁琐。


public static void addImgWatermark2(String srcImagePath, String destImagePath,
                                    String waterImgPath)
  throws Exception {
  String projectPath = System.getProperty("user.dir");
  // 原始图片信息
  BufferedImage targetImg = ImageIO.read(new File(srcImagePath));
  // 水印图片
  BufferedImage watermarkImage = ImageIO.read(new File(waterImgPath));
  int w = targetImg.getWidth();
  int h = targetImg.getHeight();
  int watermarkImageWidth = watermarkImage.getWidth(null);
  int watermarkImageHeight = watermarkImage.getHeight(null);
  IMOperation op2 = new IMOperation();
  // 水印图片位置
  op2.geometry(watermarkImageWidth, watermarkImageHeight,
               w - watermarkImageWidth - 600, h - watermarkImageHeight - 300);
  // 水印透明度
  op2.dissolve(90);
  // 水印
  op2.addImage(waterImgPath);
  // 原图
  op2.addImage(srcImagePath);
  // 目标
  String outPath = projectPath + "/src/main/resources/static/out/img_water_image22_by_im4.png";
  op2.addImage(outPath);
  ImageCommand cmd = getImageCommand(CommandType.imageWaterMark);
  cmd.run(op2);
  IMOperation op = new IMOperation();
  // 水印图片位置
  op.geometry(watermarkImageWidth, watermarkImageHeight,
              w - watermarkImageWidth, h - watermarkImageHeight - 300);
  // 水印透明度
  op.dissolve(90);
  // 水印
  op.addImage(waterImgPath);
  // 原图
  op.addImage(outPath);
  // 目标
  op.addImage(destImagePath);
  cmd.run(op);
}
复制代码


执行效果如下:


12.jpg


最后同时生成文字水印和图片水印


public static void addImgWatermark(String srcImagePath, String destImagePath, String waterImgPath)
  throws Exception {
  IMOperation op = new IMOperation();
  String projectPath = System.getProperty("user.dir");
  op.font(projectPath + FONT_FAMILY_PATH);
  op.gravity("center");
  String content = "中国";
  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);
  // 原始图片信息
  BufferedImage targetImg = ImageIO.read(new File(destImagePath));
  // 水印图片
  BufferedImage watermarkImage = ImageIO.read(new File(waterImgPath));
  int w = targetImg.getWidth();
  int h = targetImg.getHeight();
  int watermarkImageWidth = watermarkImage.getWidth(null);
  int watermarkImageHeight = watermarkImage.getHeight(null);
  IMOperation op2 = new IMOperation();
  // 水印图片位置
  op2.geometry(watermarkImageWidth, watermarkImageHeight,
               w - watermarkImageWidth - 300, h - watermarkImageHeight - 100);
  // 水印透明度
  op2.dissolve(90);
  // 水印
  op2.addImage(waterImgPath);
  // 原图
  op2.addImage(destImagePath);
  // 目标
  String outPath = projectPath + "/src/main/resources/static/out/img_water_image22_by_im4.png";
  op2.addImage(outPath);
  ImageCommand cmd2 = getImageCommand(CommandType.imageWaterMark);
  cmd2.run(op2);
}
复制代码


执行效果如下:


13.jpg


勉强能实现功能,但代码看起来太费劲了。


小结


GraphicsMagick 与 Im4Java 结合使用功能非常不错,除了添加文字水印和图片水印外,还可以旋转图片,压缩、裁剪等等操作,感兴趣的朋友可以阅读这篇文章


关于图片处理也可以考虑 Thumbnailator,同样具备图片压缩、裁剪等功能。

Graphics2D 针对图片也可以实现压缩、裁剪等功能,推荐阅读这篇文章


综上所述,Graphics2D 可以在一张背景图上同时添加文字和图片水印,以及文字可以按需求设置字体大小和样式,还可以同时添加多个图片水印。我们最终选择 Graphics2D 作为实现文字水印和图片水印的技术方案。


扩展


上面敲定了技术方案,但在实际应用中,还会遇到一些意想不到的问题,这里简单总结一些我经历过的问题,希望对大家有所帮助。


Docker发布镜像报错


比如说使用 Jenkins 发布镜像出现如下错误信息:


NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager
复制代码


问题原因:这种一般是出现在 docker 部署,且使用了精简版的 linux 基础镜像, 精简到把字体都阉割掉了,可以查看项目中的 Dockerfile 文件,比如说:


FROM openjdk:8u265-jdk-slim
复制代码


使用的是 openjdk 的 docker 'slim images',则该镜像就不包含包 'fontconfig' 和 'libfreetype6'。


如果你的项目有字体相关操作,比如导出 excel,就会报上述异常。


解决办法:

1、换个东西全一点的镜像;

2、在构建镜像时安装字体,dockerfile增加命令:


RUN apt-get update; apt-get install -y fontconfig libfreetype6
复制代码


国际化问题


我们想要在图片上添加文字水印,就要考虑乱码的原因,不止英文,还有中日韩,以及欧洲的各种语言。


回头看看我们写的代码,它支持中日韩,当然也支持英文。


private static final String FONT_FAMILY = "楷体";
Font userNameFont = new Font(FONT_FAMILY, Font.PLAIN, userNameFontSize);
复制代码


示例如下:


14.jpg


我们既然选择使用 Graphics2D 来渲染文本,就要了解 Java 应用程序有哪些方式来选择字体。

  • 使用逻辑字体名称:Java 2 平台定义了每个实现必须支持的五个逻辑字体名称:Serif、SansSerif、Monospaced、Dialog 和 DialogInput。这些逻辑字体名称以与实现相关的方式映射到物理字体。通常,一个逻辑字体名称映射到几种物理字体,以涵盖大范围的字符。
  • 使用物理字体名称:Java 2 平台提供的 API 允许应用程序确定哪些字体可用于给定的运行时以及这些字体可以处理哪些字符,并使用它们的真实名称(例如,“Times Roman”或“赫尔维蒂卡”)。应用程序可以让用户选择字体或以编程方式确定要使用的字体。
  • 使用 Lucida 字体:Sun 的 Java 2 运行时环境包含这个物理字体系列,它也被许可用于 Java 2 平台的其他实现。这些字体是物理字体,但不依赖于主机操作系统。
  • 使用捆绑的物理字体:应用程序可以捆绑 TrueType 字体并使用该 Font.createFont 方法实例化它们。


针对四种方式,根据个人理解列举相关示例代码:


1、使用逻辑字体名称


Font userNameFont = new Font("Serif", Font.PLAIN, 120);
// Font对象
java.awt.Font[family=Serif,name=Serif,style=plain,size=120]
复制代码


2、使用物理字体名称


Font userNameFont = new Font("楷体", Font.PLAIN, 120);
// Font对象
java.awt.Font[family=Dialog,name=楷体,style=plain,size=120]
复制代码


3、使用 Lucida 字体


查看本机 JDK 安装目录,在 /jre/lib/fonts 目录下有 Lucida 字体。


15.jpg


4、使用捆绑的物理字体


private static Font getFont(float fontSize) {
  try {
    InputStream resourceAsStream = new ClassPathResource("static/SourceHanSerif-Light.ttc")
      .getInputStream();
    Font font = Font.createFont(Font.TRUETYPE_FONT, resourceAsStream);
    return font.deriveFont(fontSize);
  } catch (Exception e) {
    log.error(e.getMessage());
  }
  return new Font(FONT_FAMILY, Font.PLAIN, 120);
}
复制代码


上述四种方法各有优势,区别如下所示:


  • 使用逻辑字体名称:
    • 优点:保证这些字体名称可以在任何地方使用,并且它们至少可以使用主机操作系统本地化的语言(通常是更大范围的语言)进行文本渲染。
    • 缺点:用于呈现文本的物理字体因不同的实现、主机操作系统和语言环境而异,因此应用程序无法在任何地方实现相同的外观。此外,映射机制有时会限制可以呈现的字符范围。后者曾经是 1.5 之前的 J2RE 版本的一个大问题:例如,日文文本只能在日文本地化的主机操作系统上呈现,即使安装了日文字体,也不能在其他本地化系统上呈现。对于使用 2D 字体渲染的应用程序,此问题在 J2RE 1.5.0 版中更为罕见,因为映射机制现在通常可以识别并使用所有支持的书写系统的字体(如果已安装)。
  • 使用物理字体名称:
    • 优点:这种方法让应用程序可以充分利用所有可用字体,以实现不同的文本外观和最大的语言覆盖率。
    • 缺点:这种方法很难编程。
  • 使用 Lucida 字体:
    • 优点:使用这些字体的应用程序可以在这些字体可用的地方实现相同的外观。此外,这些字体涵盖多种语言(尤其是欧洲和中东),因此您可以为支持的语言创建完全多语言的应用程序。
    • 缺点:这些字体可能并非在所有 Java 2 运行时环境中都可用。此外,它们目前不涵盖完整的 Unicode 字符集;特别是不支持中文、日文和韩文。
  • 使用捆绑的物理字体:
    • 优点:使用这些字体的应用程序可以在任何地方实现相同的外观,并且可以完全控制它们支持的语言。
    • 缺点:捆绑的字体可能会很大,特别是如果它们支持中文、日文和韩文。需要解决许可问题。


上面废话太多,咱简单总结一下:


使用逻辑字体名称,依赖于本机上安装的字体,比如说 Windows、Mac、Linux 三种不同的系统一旦安装的物理字体不一致,那么还可能存在乱码问题。使用物理字体名称本质上和逻辑字体面临一样的问题。Lucida 字体都说了不支持中日韩语言,所以不做考虑。而使用捆绑的物理字体使用起来比较费劲,针对多种语言,可能需要捆绑的字体各不相同。


实际应用时该如何选择,这个要根据实际需要进行分析,如果不需要考虑国际化问题,那就简单了。恰好自己遇到了国际化问题,本人的处理方法是结合使用物理字体名称和捆绑物理字体,使用物理字体名称来应对大多数语言,针对乱码的语言可以特殊处理,即将项目中字体资源加载到当前运行环境中。


下面举例演示如何处理中日韩三种语言乱码的情况,首先得找到一个应对中日韩乱码的字体,这里使用的是思源宋体——SourceHanSerif-Light.ttc,接着需要根据文本内容来判断对应什么语言,如果是中日韩语言,才需要使用思源宋体。


private boolean isContainChinese(String text) {
  Pattern p = Pattern
    .compile("[\u4E00-\u9FA5|!|,|。|(|)|《|》|“|”|?|:|;|【|】]");
  Matcher m = p.matcher(text);
  return m.find();
}
private boolean isJapanese(String text) {
  try {
    return text.getBytes("shift-jis").length >= (2 * text.length());
  } catch (UnsupportedEncodingException e) {
    return false;
  }
}
private boolean checkKoreaChar(String text) {
  char[] chars = text.toCharArray();
  for (char ch : chars) {
    if ((ch > 0x3130 && ch < 0x318F) || (ch >= 0xAC00 && ch <= 0xD7A3)) {
      return true;
    }
  }
  return false;
}
// 在resource目录下上传字体资源文件
private Font getFont(String text, float fontSize) {
  try {
    String fontPath = "";
    if (isJapanese(text) || isContainChinese(text) || checkKoreaChar(text)) {
      fontPath = "/static/fonts/SourceHanSerif-Light.ttc";
    }else{
      return new Font("CeraPro", Font.PLAIN, fontSize);
    }
    // 此种写法适用于SpringBoot框架
    InputStream resourceAsStream = this.getClass().getResourceAsStream(fontPath);
    // 通用写法如下:
    //InputStream resourceAsStream = new ClassPathResource("static/SourceHanSerif-Light.ttc")
    //      .getInputStream();
    Font font = Font.createFont(Font.TRUETYPE_FONT, resourceAsStream);
    return font.deriveFont(fontSize);
  } catch (Exception e) {
    log.error(e.getMessage());
  }
  return new Font("CeraPro", Font.PLAIN, fontSize);
}
复制代码


总结


工作上遇到技术问题时,最好先做好技术调研工作,尽量全面,不要找到一种就认为完事大吉了,我们还要考虑业务需求,将各种技术难点列举出来,看看该方案是否都能解决。


回头看看自己解决文字水印和图片水印的过程,当时自己只找到前两种技术方案,没有注意到 Im4Java,所以直接就选用了 Graphics2D 作为技术选型。恰好它能满足添加文字水印中遇到的字体样式问题,以及文本换行问题,所以一路还算顺利,没有返工操作。但这只是侥幸,如果当时我还注意到了 Im4Java,那么我会一开始去尝试 Im4Java。



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

相关文章
Java 图片压缩
先上传压缩后的效果图一张: 图片1是压缩前,2是压缩后,这是以最大压缩比压缩的,效果只有自己去看了。 package zhangao.main; import java.
1161 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_58c13166275...1510469898.pdf
立即下载
yqdh_58c1310f3e1...1510467885.pdf
立即下载