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)// + ); + } }