diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e40ce6b5..322aa28bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
* 【core 】 BeanValuePovider转换失败时,返回原数据,而非null
* 【core 】 支持BeanUtil.toBean(object, Map.class)转换(issue#I1I4HC@Gitee)
* 【core 】 MapUtil和CollUtil增加clear方法(issue#I1I4HC@Gitee)
+* 【core 】 增加FontUtil,可定义pressText是否从中间(issue#I1HSWU@Gitee)
### Bug修复
* 【core 】 修复SimpleCache死锁问题(issue#I1HOKB@Gitee)
diff --git a/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java
new file mode 100644
index 000000000..6c3f52ab0
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/img/FontUtil.java
@@ -0,0 +1,123 @@
+package cn.hutool.core.img;
+
+import cn.hutool.core.exceptions.UtilException;
+import cn.hutool.core.io.IORuntimeException;
+import sun.font.FontDesignMetrics;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.awt.FontMetrics;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * AWT中字体相关工具类
+ *
+ * @author looly
+ * @since 5.3.6
+ */
+public class FontUtil {
+
+ /**
+ * 创建默认字体
+ *
+ * @return 默认字体
+ */
+ public static Font createFont() {
+ return new Font(null);
+ }
+
+ /**
+ * 创建SansSerif字体
+ *
+ * @param size 字体大小
+ * @return 字体
+ */
+ public static Font createSansSerifFont(int size) {
+ return createFont(Font.SANS_SERIF, size);
+ }
+
+ /**
+ * 创建指定名称的字体
+ *
+ * @param name 字体名称
+ * @param size 字体大小
+ * @return 字体
+ */
+ public static Font createFont(String name, int size) {
+ return new Font(name, Font.PLAIN, size);
+ }
+
+ /**
+ * 根据文件创建字体
+ * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT}
+ *
+ * @param fontFile 字体文件
+ * @return {@link Font}
+ */
+ public static Font createFont(File fontFile) {
+ try {
+ return Font.createFont(Font.TRUETYPE_FONT, fontFile);
+ } catch (FontFormatException e) {
+ // True Type字体无效时使用Type1字体
+ try {
+ return Font.createFont(Font.TYPE1_FONT, fontFile);
+ } catch (Exception e1) {
+ throw new UtilException(e);
+ }
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+
+ /**
+ * 根据文件创建字体
+ * 首先尝试创建{@link Font#TRUETYPE_FONT}字体,此类字体无效则创建{@link Font#TYPE1_FONT}
+ *
+ * @param fontStream 字体流
+ * @return {@link Font}
+ */
+ public static Font createFont(InputStream fontStream) {
+ try {
+ return Font.createFont(Font.TRUETYPE_FONT, fontStream);
+ } catch (FontFormatException e) {
+ // True Type字体无效时使用Type1字体
+ try {
+ return Font.createFont(Font.TYPE1_FONT, fontStream);
+ } catch (Exception e1) {
+ throw new UtilException(e1);
+ }
+ } catch (IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+
+ /**
+ * 获得字体对应字符串的长宽信息
+ *
+ * @param font 字体
+ * @param str 字符串
+ * @return 长宽信息
+ */
+ public static Dimension getDimension(Font font, String str) {
+ final FontMetrics metrics = FontDesignMetrics.getMetrics(font);
+ return getDimension(FontDesignMetrics.getMetrics(font), str);
+ }
+
+ /**
+ * 获得字体对应字符串的长宽信息
+ *
+ * @param metrics {@link FontMetrics}
+ * @param str 字符串
+ * @return 长宽信息
+ */
+ public static Dimension getDimension(FontMetrics metrics, String str) {
+ final int width = metrics.stringWidth(str);
+ final int height = metrics.getAscent() - metrics.getLeading() - metrics.getDescent();
+
+ return new Dimension(width, height);
+ }
+
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java
index f1ce6a002..107c87e45 100644
--- a/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/img/GraphicsUtil.java
@@ -1,16 +1,23 @@
package cn.hutool.core.img;
+import cn.hutool.core.util.ObjectUtil;
+
+import java.awt.AlphaComposite;
import java.awt.Color;
+import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Point;
+import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
/**
* {@link Graphics}相关工具类
- *
+ *
* @author looly
* @since 4.5.2
*/
@@ -18,7 +25,7 @@ public class GraphicsUtil {
/**
* 创建{@link Graphics2D}
- *
+ *
* @param image {@link BufferedImage}
* @param color {@link Color}背景颜色以及当前画笔颜色,{@code null}表示不设置背景色
* @return {@link Graphics2D}
@@ -26,8 +33,8 @@ public class GraphicsUtil {
*/
public static Graphics2D createGraphics(BufferedImage image, Color color) {
final Graphics2D g = image.createGraphics();
-
- if(null != color) {
+
+ if (null != color) {
// 填充背景
g.setColor(color);
g.fillRect(0, 0, image.getWidth(), image.getHeight());
@@ -39,8 +46,8 @@ public class GraphicsUtil {
/**
* 获取文字居中高度的Y坐标(距离上边距距离)
* 此方法依赖FontMetrics,如果获取失败,默认为背景高度的1/3
- *
- * @param g {@link Graphics2D}画笔
+ *
+ * @param g {@link Graphics2D}画笔
* @param backgroundHeight 背景高度
* @return 最小高度,-1表示无法获取
* @since 4.5.17
@@ -64,11 +71,11 @@ public class GraphicsUtil {
/**
* 绘制字符串,使用随机颜色,默认抗锯齿
- *
- * @param g {@link Graphics}画笔
- * @param str 字符串
- * @param font 字体
- * @param width 字符串总宽度
+ *
+ * @param g {@link Graphics}画笔
+ * @param str 字符串
+ * @param font 字体
+ * @param width 字符串总宽度
* @param height 字符串背景高度
* @return 画笔对象
* @since 4.5.10
@@ -79,12 +86,12 @@ public class GraphicsUtil {
/**
* 绘制字符串,默认抗锯齿
- *
- * @param g {@link Graphics}画笔
- * @param str 字符串
- * @param font 字体
- * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机)
- * @param width 字符串背景的宽度
+ *
+ * @param g {@link Graphics}画笔
+ * @param str 字符串
+ * @param font 字体
+ * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机)
+ * @param width 字符串背景的宽度
* @param height 字符串背景的高度
* @return 画笔对象
* @since 4.5.10
@@ -98,7 +105,7 @@ public class GraphicsUtil {
g.setFont(font);
// 文字高度(必须在设置字体后调用)
- int midY = GraphicsUtil.getCenterY(g, height);
+ int midY = getCenterY(g, height);
if (null != color) {
g.setColor(color);
}
@@ -115,4 +122,97 @@ public class GraphicsUtil {
return g;
}
+ /**
+ * 绘制字符串,默认抗锯齿。
+ * 此方法定义一个矩形区域和坐标,文字基于这个区域中间偏移x,y绘制。
+ *
+ * @param g {@link Graphics}画笔
+ * @param str 字符串
+ * @param font 字体,字体大小决定了在背景中绘制的大小
+ * @param color 字体颜色,{@code null} 表示使用黑色
+ * @param rectangle 字符串绘制坐标和大小,此对象定义了绘制字符串的区域大小和偏移位置
+ * @return 画笔对象
+ * @since 4.5.10
+ */
+ public static Graphics drawString(Graphics g, String str, Font font, Color color, Rectangle rectangle) {
+ // 背景长宽
+ final int backgroundWidth = rectangle.width;
+ final int backgroundHeight = rectangle.height;
+
+ //获取字符串本身的长宽
+ Dimension dimension;
+ try {
+ dimension = FontUtil.getDimension(g.getFontMetrics(font), str);
+ } catch (Exception e) {
+ // 此处报告bug某些情况下会抛出IndexOutOfBoundsException,在此做容错处理
+ dimension = new Dimension(backgroundWidth / 3, backgroundHeight / 3);
+ }
+
+ rectangle.setSize(dimension.width, dimension.height);
+ final Point point = ImgUtil.getPointBaseCentre(rectangle, backgroundWidth, backgroundHeight);
+
+ return drawString(g, str, font, color, point);
+ }
+
+ /**
+ * 绘制字符串,默认抗锯齿
+ *
+ * @param g {@link Graphics}画笔
+ * @param str 字符串
+ * @param font 字体,字体大小决定了在背景中绘制的大小
+ * @param color 字体颜色,{@code null} 表示使用黑色
+ * @param point 绘制字符串的位置坐标
+ * @return 画笔对象
+ * @since 5.3.6
+ */
+ public static Graphics drawString(Graphics g, String str, Font font, Color color, Point point) {
+ // 抗锯齿
+ if (g instanceof Graphics2D) {
+ ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+
+ g.setFont(font);
+ g.setColor(ObjectUtil.defaultIfNull(color, Color.BLACK));
+ g.drawString(str, point.x, point.y);
+
+ return g;
+ }
+
+ /**
+ * 绘制图片
+ *
+ * @param g 画笔
+ * @param img 要绘制的图片
+ * @param point 绘制的位置,基于左上角
+ * @return 画笔对象
+ */
+ public static Graphics drawImg(Graphics g, Image img, Point point) {
+ return drawImg(g, img,
+ new Rectangle(point.x, point.y, img.getWidth(null), img.getHeight(null)));
+ }
+
+ /**
+ * 绘制图片
+ *
+ * @param g 画笔
+ * @param img 要绘制的图片
+ * @param rectangle 矩形对象,表示矩形区域的x,y,width,height,,基于左上角
+ * @return 画笔对象
+ */
+ public static Graphics drawImg(Graphics g, Image img, Rectangle rectangle) {
+ g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图
+ return g;
+ }
+
+ /**
+ * 设置画笔透明度
+ *
+ * @param g 画笔
+ * @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
+ * @return 画笔
+ */
+ public static Graphics2D setAlpha(Graphics2D g, float alpha){
+ g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
+ return g;
+ }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/img/Img.java b/hutool-core/src/main/java/cn/hutool/core/img/Img.java
index 0f2ae2911..74ae47517 100644
--- a/hutool-core/src/main/java/cn/hutool/core/img/Img.java
+++ b/hutool-core/src/main/java/cn/hutool/core/img/Img.java
@@ -15,9 +15,9 @@ import javax.imageio.stream.ImageOutputStream;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
-import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
@@ -145,13 +145,13 @@ public class Img implements Serializable {
/**
* 构造
*
- * @param srcImage 来源图片
+ * @param srcImage 来源图片
* @param targetImageType 目标图片类型
* @since 5.0.7
*/
public Img(BufferedImage srcImage, String targetImageType) {
this.srcImage = srcImage;
- if(null == targetImageType){
+ if (null == targetImageType) {
targetImageType = ImgUtil.IMAGE_TYPE_JPG;
}
this.targetImageType = targetImageType;
@@ -312,7 +312,7 @@ public class Img implements Serializable {
Graphics2D g = image.createGraphics();
// 设置背景
- if(null != fixedColor){
+ if (null != fixedColor) {
g.setBackground(fixedColor);
g.clearRect(0, 0, width, height);
}
@@ -450,20 +450,22 @@ public class Img implements Serializable {
if (null == font) {
// 默认字体
- font = new Font("Courier", Font.PLAIN, (int) (targetImage.getHeight() * 0.75));
+ font = FontUtil.createSansSerifFont((int) (targetImage.getHeight() * 0.75));
}
-
- // 抗锯齿
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g.setColor(color);
- g.setFont(font);
// 透明度
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
- // 在指定坐标绘制水印文字
- final FontMetrics metrics = g.getFontMetrics(font);
- final int textLength = metrics.stringWidth(pressText);
- final int textHeight = metrics.getAscent() - metrics.getLeading() - metrics.getDescent();
- g.drawString(pressText, Math.abs(targetImage.getWidth() - textLength) / 2 + x, Math.abs(targetImage.getHeight() + textHeight) / 2 + y);
+
+ // 绘制
+ if (positionBaseCentre) {
+ // 基于中心绘制
+ GraphicsUtil.drawString(g, pressText, font, color,
+ new Rectangle(x, y, targetImage.getWidth(), targetImage.getHeight()));
+ } else {
+ // 基于左上角绘制
+ GraphicsUtil.drawString(g, pressText, font, color,
+ new Point(x, y));
+ }
+
g.dispose();
this.targetImage = targetImage;
@@ -497,7 +499,6 @@ public class Img implements Serializable {
public Img pressImage(Image pressImg, Rectangle rectangle, float alpha) {
final Image targetImg = getValidSrcImg();
- fixRectangle(rectangle, targetImg.getWidth(null), targetImg.getHeight(null));
this.targetImage = draw(ImgUtil.toBufferedImage(targetImg), pressImg, rectangle, alpha);
return this;
}
@@ -619,12 +620,21 @@ public class Img implements Serializable {
* @param backgroundImg 背景图片
* @param img 要绘制的图片
* @param rectangle 矩形对象,表示矩形区域的x,y,width,height,x,y从背景图片中心计算
+ * @param alpha 透明度:alpha 必须是范围 [0.0, 1.0] 之内(包含边界值)的一个浮点数字
* @return 绘制后的背景
*/
- private static BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) {
+ private BufferedImage draw(BufferedImage backgroundImg, Image img, Rectangle rectangle, float alpha) {
final Graphics2D g = backgroundImg.createGraphics();
- g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));
- g.drawImage(img, rectangle.x, rectangle.y, rectangle.width, rectangle.height, null); // 绘制切割后的图
+ GraphicsUtil.setAlpha(g, alpha);
+
+ Point point;
+ if (positionBaseCentre) {
+ point = ImgUtil.getPointBaseCentre(rectangle, backgroundImg.getWidth(), backgroundImg.getHeight());
+ } else {
+ point = new Point(rectangle.x, rectangle.y);
+ }
+ GraphicsUtil.drawImg(g, img, point);
+
g.dispose();
return backgroundImg;
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java
index 74d0faaa1..ca5c47fbd 100644
--- a/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/img/ImgUtil.java
@@ -2,7 +2,6 @@ package cn.hutool.core.img;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
-import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
@@ -25,10 +24,10 @@ import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.Color;
import java.awt.Font;
-import java.awt.FontFormatException;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
+import java.awt.Point;
import java.awt.Rectangle;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
@@ -1377,18 +1376,7 @@ public class ImgUtil {
* @since 3.0.9
*/
public static Font createFont(File fontFile) {
- try {
- return Font.createFont(Font.TRUETYPE_FONT, fontFile);
- } catch (FontFormatException e) {
- // True Type字体无效时使用Type1字体
- try {
- return Font.createFont(Font.TYPE1_FONT, fontFile);
- } catch (Exception e1) {
- throw new UtilException(e);
- }
- } catch (IOException e) {
- throw new IORuntimeException(e);
- }
+ return FontUtil.createFont(fontFile);
}
/**
@@ -1400,18 +1388,7 @@ public class ImgUtil {
* @since 3.0.9
*/
public static Font createFont(InputStream fontStream) {
- try {
- return Font.createFont(Font.TRUETYPE_FONT, fontStream);
- } catch (FontFormatException e) {
- // True Type字体无效时使用Type1字体
- try {
- return Font.createFont(Font.TYPE1_FONT, fontStream);
- } catch (Exception e1) {
- throw new UtilException(e1);
- }
- } catch (IOException e) {
- throw new IORuntimeException(e);
- }
+ return FontUtil.createFont(fontStream);
}
/**
@@ -1917,4 +1894,20 @@ public class ImgUtil {
}
return new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255));
}
+
+ /**
+ * 获得修正后的矩形坐标位置,变为以背景中心为基准坐标(即x,y == 0,0时,处于背景正中)
+ *
+ * @param rectangle 矩形
+ * @param backgroundWidth 参考宽(背景宽)
+ * @param backgroundHeight 参考高(背景高)
+ * @return 修正后的{@link Point}
+ * @since 5.3.6
+ */
+ public static Point getPointBaseCentre(Rectangle rectangle, int backgroundWidth, int backgroundHeight) {
+ return new Point(
+ rectangle.x + (Math.abs(backgroundWidth - rectangle.width) / 2), //
+ rectangle.y + (Math.abs(backgroundHeight - rectangle.height) / 2)//
+ );
+ }
}