diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/img/GraphicsUtil.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/img/GraphicsUtil.java index 59ec9bf8b..39e69b85f 100644 --- a/hutool-swing/src/main/java/org/dromara/hutool/swing/img/GraphicsUtil.java +++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/img/GraphicsUtil.java @@ -83,7 +83,40 @@ public class GraphicsUtil { * @since 4.5.10 */ public static Graphics drawStringColourful(final Graphics g, final String str, final Font font, final int width, final int height) { - return drawString(g, str, font, null, width, height); + return drawStringColourful(g, str, font, width, height, null, 0); + } + + /** + * 绘制字符串,使用随机颜色,默认抗锯齿 + * + * @param g {@link Graphics}画笔 + * @param str 字符串 + * @param font 字体 + * @param width 字符串总宽度 + * @param height 字符串背景高度 + * @param compareColor 对比颜色,用于计算颜色与对比颜色的色差,色差小于minColorDistance则重新生成颜色 + * @param minColorDistance 随机生成的颜色与对比颜色的最小色差,小于此值则重新生成颜色 + * @return 画笔对象 + * @since 6.0.0 + */ + public static Graphics drawStringColourful(final Graphics g, final String str, final Font font, + final int width, final int height, final Color compareColor, final int minColorDistance) { + // 抗锯齿 + enableAntialias(g); + // 创建字体 + g.setFont(font); + + // 文字高度(必须在设置字体后调用) + final int midY = getCenterY(g, height); + + final int len = str.length(); + final int charWidth = width / len; + for (int i = 0; i < len; i++) { + // 产生随机的颜色值,让输出的每个字符的颜色值都将不同。 + g.setColor(ColorUtil.randomColor(compareColor, minColorDistance)); + g.drawString(String.valueOf(str.charAt(i)), i * charWidth, midY); + } + return g; } /** @@ -93,12 +126,11 @@ public class GraphicsUtil { * @param str 字符串 * @param font 字体 * @param color 字体颜色,{@code null} 表示使用随机颜色(每个字符单独随机) - * @param width 字符串背景的宽度 * @param height 字符串背景的高度 * @return 画笔对象 - * @since 4.5.10 + * @since 6.0.0 */ - public static Graphics drawString(final Graphics g, final String str, final Font font, final Color color, final int width, final int height) { + public static Graphics drawString(final Graphics g, final String str, final Font font, final Color color, final int height) { // 抗锯齿 enableAntialias(g); // 创建字体 @@ -109,16 +141,7 @@ public class GraphicsUtil { if (null != color) { g.setColor(color); } - - final int len = str.length(); - final int charWidth = width / len; - for (int i = 0; i < len; i++) { - if (null == color) { - // 产生随机的颜色值,让输出的每个字符的颜色值都将不同。 - g.setColor(ColorUtil.randomColor()); - } - g.drawString(String.valueOf(str.charAt(i)), i * charWidth, midY); - } + g.drawString(str, 0, midY); return g; } @@ -222,9 +245,9 @@ public class GraphicsUtil { private static void enableAntialias(final Graphics g) { if (g instanceof Graphics2D) { ((Graphics2D) g).setRenderingHints( - RenderingHintsBuilder.of() - .setAntialiasing(RenderingHintsBuilder.Antialias.ON) - .setTextAntialias(RenderingHintsBuilder.TextAntialias.ON).build() + RenderingHintsBuilder.of() + .setAntialiasing(RenderingHintsBuilder.Antialias.ON) + .setTextAntialias(RenderingHintsBuilder.TextAntialias.ON).build() ); } } diff --git a/hutool-swing/src/main/java/org/dromara/hutool/swing/img/color/ColorUtil.java b/hutool-swing/src/main/java/org/dromara/hutool/swing/img/color/ColorUtil.java index c05693a9a..a118fab21 100644 --- a/hutool-swing/src/main/java/org/dromara/hutool/swing/img/color/ColorUtil.java +++ b/hutool-swing/src/main/java/org/dromara/hutool/swing/img/color/ColorUtil.java @@ -40,28 +40,28 @@ public class ColorUtil { static { final Map colorMap = MapUtil - .builder("BLACK", Color.BLACK) - .put("WHITE", Color.WHITE) - .put("LIGHTGRAY", Color.LIGHT_GRAY) - .put("LIGHT_GRAY", Color.LIGHT_GRAY) - .put("GRAY", Color.GRAY) - .put("DARKGRAY", Color.DARK_GRAY) - .put("DARK_GRAY", Color.DARK_GRAY) - .put("RED", Color.RED) - .put("PINK", Color.PINK) - .put("ORANGE", Color.ORANGE) - .put("YELLOW", Color.YELLOW) - .put("GREEN", Color.GREEN) - .put("MAGENTA", Color.MAGENTA) - .put("CYAN", Color.CYAN) - .put("BLUE", Color.BLUE) - // 暗金色 - .put("DARKGOLD", hexToColor("#9e7e67")) - .put("DARK_GOLD", hexToColor("#9e7e67")) - // 亮金色 - .put("LIGHTGOLD", hexToColor("#ac9c85")) - .put("LIGHT_GOLD", hexToColor("#ac9c85")) - .build(); + .builder("BLACK", Color.BLACK) + .put("WHITE", Color.WHITE) + .put("LIGHTGRAY", Color.LIGHT_GRAY) + .put("LIGHT_GRAY", Color.LIGHT_GRAY) + .put("GRAY", Color.GRAY) + .put("DARKGRAY", Color.DARK_GRAY) + .put("DARK_GRAY", Color.DARK_GRAY) + .put("RED", Color.RED) + .put("PINK", Color.PINK) + .put("ORANGE", Color.ORANGE) + .put("YELLOW", Color.YELLOW) + .put("GREEN", Color.GREEN) + .put("MAGENTA", Color.MAGENTA) + .put("CYAN", Color.CYAN) + .put("BLUE", Color.BLUE) + // 暗金色 + .put("DARKGOLD", hexToColor("#9e7e67")) + .put("DARK_GOLD", hexToColor("#9e7e67")) + // 亮金色 + .put("LIGHTGOLD", hexToColor("#ac9c85")) + .put("LIGHT_GOLD", hexToColor("#ac9c85")) + .build(); COLOR_MAPPING = MapUtil.view(colorMap); } @@ -78,11 +78,11 @@ public class ColorUtil { */ public static String toCssRgb(final Color color) { return StrUtil.builder() - .append("rgb(") - .append(color.getRed()).append(",") - .append(color.getGreen()).append(",") - .append(color.getBlue()).append(")") - .toString(); + .append("rgb(") + .append(color.getRed()).append(",") + .append(color.getGreen()).append(",") + .append(color.getBlue()).append(")") + .toString(); } /** @@ -93,12 +93,12 @@ public class ColorUtil { */ public static String toCssRgba(final Color color) { return StrUtil.builder() - .append("rgba(") - .append(color.getRed()).append(",") - .append(color.getGreen()).append(",") - .append(color.getBlue()).append(",") - .append(color.getAlpha() / 255D).append(")") - .toString(); + .append("rgba(") + .append(color.getRed()).append(",") + .append(color.getGreen()).append(",") + .append(color.getBlue()).append(",") + .append(color.getAlpha() / 255D).append(")") + .toString(); } /** @@ -233,6 +233,8 @@ public class ColorUtil { return new Color(r, g, b); } + // region randomColor + /** * 生成随机颜色 * @@ -257,6 +259,42 @@ public class ColorUtil { return new Color(random.nextInt(RGB_COLOR_BOUND), random.nextInt(RGB_COLOR_BOUND), random.nextInt(RGB_COLOR_BOUND)); } + /** + * 生成随机颜色,与指定颜色有一定的区分度 + * + * @param compareColor 比较颜色,{@code null}表示无区分要求 + * @param minDistance 最小色差,按三维坐标计算的距离值。小于等于0表示无区分要求 + * @return 随机颜色 + * @since 5.8.30 + */ + public static Color randomColor(final Color compareColor, final int minDistance) { + Color color = randomColor(); + if(null == compareColor || minDistance <= 0){ + while (computeColorDistance(compareColor, color) < minDistance) { + color = randomColor(); + } + } + return color; + } + // endregion + + /** + * 计算两个颜色之间的色差,按三维坐标距离计算 + * + * @param color1 颜色1 + * @param color2 颜色2 + * @return 色差,按三维坐标距离值 + * @since 5.8.30 + */ + public static int computeColorDistance(final Color color1, final Color color2) { + if (null == color1 || null == color2) { + return 0; + } + return (int) Math.sqrt(Math.pow(color1.getRed() - color2.getRed(), 2) + + Math.pow(color1.getGreen() - color2.getGreen(), 2) + + Math.pow(color1.getBlue() - color2.getBlue(), 2)); + } + /** * AWT的{@link Color}颜色转换为ANSI颜色,由于取最接近颜色,故可能有色差 * diff --git a/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java b/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java index 1a4d7588e..957c6120b 100644 --- a/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java +++ b/hutool-swing/src/test/java/org/dromara/hutool/swing/captcha/CaptchaUtilTest.java @@ -12,9 +12,20 @@ package org.dromara.hutool.swing.captcha; +import org.dromara.hutool.core.util.ObjUtil; +import org.dromara.hutool.core.util.RandomUtil; +import org.dromara.hutool.swing.img.GraphicsUtil; +import org.dromara.hutool.swing.img.color.ColorUtil; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.util.concurrent.ThreadLocalRandom; + public class CaptchaUtilTest { @Test @@ -24,4 +35,70 @@ public class CaptchaUtilTest { CaptchaUtil.ofShearCaptcha(320, 240); } } + + @Test + @Disabled + public void drawStringColourfulTest() { + for(int i = 0; i < 10; i++) { + final AbstractCaptcha lineCaptcha = new TestLineCaptcha(200, 100, 5, 10); + lineCaptcha.write("d:/captcha/line"+i+".png"); + } + } + + static class TestLineCaptcha extends AbstractCaptcha{ + private static final long serialVersionUID = -558846929114465692L; + + public TestLineCaptcha(final int width, final int height, final int codeCount, final int interfereCount) { + super(width, height, codeCount, interfereCount); + } + + @Override + protected Image createImage(final String code) { + // 图像buffer + final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + final Graphics2D g = GraphicsUtil.createGraphics(image, ObjUtil.defaultIfNull(this.background, Color.WHITE)); + + // 干扰线 + drawInterfere(g); + + // 字符串 + drawString(g, code); + + return image; + } + + // ----------------------------------------------------------------------------------------------------- Private method start + /** + * 绘制字符串 + * + * @param g {@link Graphics}画笔 + * @param code 验证码 + */ + private void drawString(final Graphics2D g, final String code) { + // 指定透明度 + if (null != this.textAlpha) { + g.setComposite(this.textAlpha); + } + GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height,Color.WHITE,200); + } + + /** + * 绘制干扰线 + * + * @param g {@link Graphics2D}画笔 + */ + private void drawInterfere(final Graphics2D g) { + final ThreadLocalRandom random = RandomUtil.getRandom(); + // 干扰线 + for (int i = 0; i < this.interfereCount; i++) { + final int xs = random.nextInt(width); + final int ys = random.nextInt(height); + final int xe = xs + random.nextInt(width / 3); + final int ye = ys + random.nextInt(height / 3); + g.setColor(ColorUtil.randomColor(random)); + g.drawLine(xs, ys, xe, ye); + } + } + // ----------------------------------------------------------------------------------------------------- Private method start + } }