|
|
@@ -0,0 +1,197 @@
|
|
|
+package com.vber.common.web.core;
|
|
|
+
|
|
|
+import cn.hutool.captcha.AbstractCaptcha;
|
|
|
+import cn.hutool.captcha.generator.CodeGenerator;
|
|
|
+import cn.hutool.captcha.generator.RandomGenerator;
|
|
|
+import cn.hutool.core.img.GraphicsUtil;
|
|
|
+import cn.hutool.core.img.ImgUtil;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.RandomUtil;
|
|
|
+
|
|
|
+import java.awt.*;
|
|
|
+import java.awt.image.BufferedImage;
|
|
|
+import java.io.Serial;
|
|
|
+import java.util.concurrent.ThreadLocalRandom;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 带干扰线、波浪、圆的验证码
|
|
|
+ *
|
|
|
+ * @author Iwb
|
|
|
+ */
|
|
|
+public class WaveAndCircleCaptcha extends AbstractCaptcha {
|
|
|
+
|
|
|
+ @Serial
|
|
|
+ private static final long serialVersionUID = 1L;
|
|
|
+
|
|
|
+ // 构造方法(略,与之前一致)
|
|
|
+ public WaveAndCircleCaptcha(int width, int height) {
|
|
|
+ this(width, height, 4);
|
|
|
+ }
|
|
|
+
|
|
|
+ public WaveAndCircleCaptcha(int width, int height, int codeCount) {
|
|
|
+ this(width, height, codeCount, 6);
|
|
|
+ }
|
|
|
+
|
|
|
+ public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount) {
|
|
|
+ this(width, height, new RandomGenerator(codeCount), interfereCount);
|
|
|
+ }
|
|
|
+
|
|
|
+ public WaveAndCircleCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
|
|
|
+ super(width, height, generator, interfereCount);
|
|
|
+ }
|
|
|
+
|
|
|
+ public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount, float size) {
|
|
|
+ super(width, height, new RandomGenerator(codeCount), interfereCount, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Image createImage(String code) {
|
|
|
+ final BufferedImage image = new BufferedImage(
|
|
|
+ width,
|
|
|
+ height,
|
|
|
+ (null == this.background) ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_INT_RGB);
|
|
|
+ final Graphics2D g = ImgUtil.createGraphics(image, this.background);
|
|
|
+
|
|
|
+ try {
|
|
|
+ drawString(g, code);
|
|
|
+ // 扭曲
|
|
|
+ shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
|
|
|
+ drawInterfere(g);
|
|
|
+ } finally {
|
|
|
+ g.dispose();
|
|
|
+ }
|
|
|
+
|
|
|
+ return image;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawString(Graphics2D g, String code) {
|
|
|
+ // 设置抗锯齿(让字体渲染更清晰)
|
|
|
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
|
+ g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
|
|
+
|
|
|
+ if (this.textAlpha != null) {
|
|
|
+ g.setComposite(this.textAlpha);
|
|
|
+ }
|
|
|
+
|
|
|
+ GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void drawInterfere(Graphics2D g) {
|
|
|
+ ThreadLocalRandom random = RandomUtil.getRandom();
|
|
|
+ int circleCount = Math.max(0, this.interfereCount - 1);
|
|
|
+
|
|
|
+ // 圈圈
|
|
|
+ for (int i = 0; i < circleCount; i++) {
|
|
|
+ g.setColor(ImgUtil.randomColor(random));
|
|
|
+ int x = random.nextInt(width);
|
|
|
+ int y = random.nextInt(height);
|
|
|
+ int w = random.nextInt(height >> 1);
|
|
|
+ int h = random.nextInt(height >> 1);
|
|
|
+ g.drawOval(x, y, w, h);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 仅 1 条平滑波浪线
|
|
|
+ if (this.interfereCount >= 1) {
|
|
|
+ g.setColor(getRandomColor(120, 230, random));
|
|
|
+ drawSmoothWave(g, random);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void drawSmoothWave(Graphics2D g, ThreadLocalRandom random) {
|
|
|
+ int amplitude = random.nextInt(8) + 5; // 波动幅度
|
|
|
+ int wavelength = random.nextInt(40) + 30; // 波长
|
|
|
+ double phase = random.nextDouble() * Math.PI * 2;
|
|
|
+
|
|
|
+ // ✅ 关键:限制 baseY 在中间区域
|
|
|
+ int centerY = height / 2;
|
|
|
+ int verticalJitter = Math.max(5, height / 6); // 至少偏移5像素
|
|
|
+ int baseY = centerY - verticalJitter + random.nextInt(verticalJitter * 2);
|
|
|
+
|
|
|
+ g.setStroke(new BasicStroke(2.5f)); // 线宽
|
|
|
+
|
|
|
+ int[] xPoints = new int[width];
|
|
|
+ int[] yPoints = new int[width];
|
|
|
+ for (int x = 0; x < width; x++) {
|
|
|
+ int y = baseY + (int) (amplitude * Math.sin((double) x / wavelength * 2 * Math.PI + phase));
|
|
|
+ // 限制 y 不要超出图像边界(可选)
|
|
|
+ y = Math.max(amplitude, Math.min(y, height - amplitude));
|
|
|
+ xPoints[x] = x;
|
|
|
+ yPoints[x] = y;
|
|
|
+ }
|
|
|
+ g.drawPolyline(xPoints, yPoints, width);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Color getRandomColor(int min, int max, ThreadLocalRandom random) {
|
|
|
+ int range = max - min;
|
|
|
+ return new Color(
|
|
|
+ min + random.nextInt(range),
|
|
|
+ min + random.nextInt(range),
|
|
|
+ min + random.nextInt(range));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 扭曲
|
|
|
+ *
|
|
|
+ * @param g {@link Graphics}
|
|
|
+ * @param w1 w1
|
|
|
+ * @param h1 h1
|
|
|
+ * @param color 颜色
|
|
|
+ */
|
|
|
+ private void shear(Graphics g, int w1, int h1, Color color) {
|
|
|
+ shearX(g, w1, h1, color);
|
|
|
+ shearY(g, w1, h1, color);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * X坐标扭曲
|
|
|
+ *
|
|
|
+ * @param g {@link Graphics}
|
|
|
+ * @param w1 宽
|
|
|
+ * @param h1 高
|
|
|
+ * @param color 颜色
|
|
|
+ */
|
|
|
+ private void shearX(Graphics g, int w1, int h1, Color color) {
|
|
|
+
|
|
|
+ int period = RandomUtil.randomInt(this.width);
|
|
|
+
|
|
|
+ int frames = 1;
|
|
|
+ int phase = RandomUtil.randomInt(2);
|
|
|
+
|
|
|
+ for (int i = 0; i < h1; i++) {
|
|
|
+ double d = (double) (period >> 1)
|
|
|
+ * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
|
|
+ g.copyArea(0, i, w1, 1, (int) d, 0);
|
|
|
+ g.setColor(color);
|
|
|
+ g.drawLine((int) d, i, 0, i);
|
|
|
+ g.drawLine((int) d + w1, i, w1, i);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Y坐标扭曲
|
|
|
+ *
|
|
|
+ * @param g {@link Graphics}
|
|
|
+ * @param w1 宽
|
|
|
+ * @param h1 高
|
|
|
+ * @param color 颜色
|
|
|
+ */
|
|
|
+ private void shearY(Graphics g, int w1, int h1, Color color) {
|
|
|
+
|
|
|
+ int period = RandomUtil.randomInt(this.height >> 1);
|
|
|
+
|
|
|
+ int frames = 20;
|
|
|
+ int phase = 7;
|
|
|
+ for (int i = 0; i < w1; i++) {
|
|
|
+ double d = (double) (period >> 1)
|
|
|
+ * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
|
|
|
+ g.copyArea(i, 0, 1, h1, 0, (int) d);
|
|
|
+ g.setColor(color);
|
|
|
+ // 擦除原位置的痕迹
|
|
|
+ g.drawLine(i, (int) d, i, 0);
|
|
|
+ g.drawLine(i, (int) d + h1, i, h1);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|