WaveAndCircleCaptcha.java 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. package org.dromara.auth.config;
  2. import cn.hutool.captcha.AbstractCaptcha;
  3. import cn.hutool.captcha.generator.CodeGenerator;
  4. import cn.hutool.captcha.generator.RandomGenerator;
  5. import cn.hutool.core.img.GraphicsUtil;
  6. import cn.hutool.core.img.ImgUtil;
  7. import cn.hutool.core.util.ObjectUtil;
  8. import cn.hutool.core.util.RandomUtil;
  9. import java.awt.*;
  10. import java.awt.image.BufferedImage;
  11. import java.io.Serial;
  12. import java.util.concurrent.ThreadLocalRandom;
  13. /**
  14. * 带干扰线、波浪、圆的验证码
  15. *
  16. * @author Lion Li
  17. */
  18. public class WaveAndCircleCaptcha extends AbstractCaptcha {
  19. @Serial
  20. private static final long serialVersionUID = 1L;
  21. // 构造方法(略,与之前一致)
  22. public WaveAndCircleCaptcha(int width, int height) {
  23. this(width, height, 4);
  24. }
  25. public WaveAndCircleCaptcha(int width, int height, int codeCount) {
  26. this(width, height, codeCount, 6);
  27. }
  28. public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount) {
  29. this(width, height, new RandomGenerator(codeCount), interfereCount);
  30. }
  31. public WaveAndCircleCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
  32. super(width, height, generator, interfereCount);
  33. }
  34. public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount, float size) {
  35. super(width, height, new RandomGenerator(codeCount), interfereCount, size);
  36. }
  37. @Override
  38. public Image createImage(String code) {
  39. final BufferedImage image = new BufferedImage(
  40. width,
  41. height,
  42. (null == this.background) ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_INT_RGB
  43. );
  44. final Graphics2D g = ImgUtil.createGraphics(image, this.background);
  45. try {
  46. drawString(g, code);
  47. // 扭曲
  48. shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
  49. drawInterfere(g);
  50. } finally {
  51. g.dispose();
  52. }
  53. return image;
  54. }
  55. private void drawString(Graphics2D g, String code) {
  56. // 设置抗锯齿(让字体渲染更清晰)
  57. g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
  58. g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
  59. if (this.textAlpha != null) {
  60. g.setComposite(this.textAlpha);
  61. }
  62. GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
  63. }
  64. protected void drawInterfere(Graphics2D g) {
  65. ThreadLocalRandom random = RandomUtil.getRandom();
  66. int circleCount = Math.max(0, this.interfereCount - 1);
  67. // 圈圈
  68. for (int i = 0; i < circleCount; i++) {
  69. g.setColor(ImgUtil.randomColor(random));
  70. int x = random.nextInt(width);
  71. int y = random.nextInt(height);
  72. int w = random.nextInt(height >> 1);
  73. int h = random.nextInt(height >> 1);
  74. g.drawOval(x, y, w, h);
  75. }
  76. // 仅 1 条平滑波浪线
  77. if (this.interfereCount >= 1) {
  78. g.setColor(getRandomColor(120, 230, random));
  79. drawSmoothWave(g, random);
  80. }
  81. }
  82. private void drawSmoothWave(Graphics2D g, ThreadLocalRandom random) {
  83. int amplitude = random.nextInt(8) + 5; // 波动幅度
  84. int wavelength = random.nextInt(40) + 30; // 波长
  85. double phase = random.nextDouble() * Math.PI * 2;
  86. // ✅ 关键:限制 baseY 在中间区域
  87. int centerY = height / 2;
  88. int verticalJitter = Math.max(5, height / 6); // 至少偏移5像素
  89. int baseY = centerY - verticalJitter + random.nextInt(verticalJitter * 2);
  90. g.setStroke(new BasicStroke(2.5f)); // 线宽
  91. int[] xPoints = new int[width];
  92. int[] yPoints = new int[width];
  93. for (int x = 0; x < width; x++) {
  94. int y = baseY + (int) (amplitude * Math.sin((double) x / wavelength * 2 * Math.PI + phase));
  95. // 限制 y 不要超出图像边界(可选)
  96. y = Math.max(amplitude, Math.min(y, height - amplitude));
  97. xPoints[x] = x;
  98. yPoints[x] = y;
  99. }
  100. g.drawPolyline(xPoints, yPoints, width);
  101. }
  102. private Color getRandomColor(int min, int max, ThreadLocalRandom random) {
  103. int range = max - min;
  104. return new Color(
  105. min + random.nextInt(range),
  106. min + random.nextInt(range),
  107. min + random.nextInt(range)
  108. );
  109. }
  110. /**
  111. * 扭曲
  112. *
  113. * @param g {@link Graphics}
  114. * @param w1 w1
  115. * @param h1 h1
  116. * @param color 颜色
  117. */
  118. private void shear(Graphics g, int w1, int h1, Color color) {
  119. shearX(g, w1, h1, color);
  120. shearY(g, w1, h1, color);
  121. }
  122. /**
  123. * X坐标扭曲
  124. *
  125. * @param g {@link Graphics}
  126. * @param w1 宽
  127. * @param h1 高
  128. * @param color 颜色
  129. */
  130. private void shearX(Graphics g, int w1, int h1, Color color) {
  131. int period = RandomUtil.randomInt(this.width);
  132. int frames = 1;
  133. int phase = RandomUtil.randomInt(2);
  134. for (int i = 0; i < h1; i++) {
  135. double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
  136. g.copyArea(0, i, w1, 1, (int) d, 0);
  137. g.setColor(color);
  138. g.drawLine((int) d, i, 0, i);
  139. g.drawLine((int) d + w1, i, w1, i);
  140. }
  141. }
  142. /**
  143. * Y坐标扭曲
  144. *
  145. * @param g {@link Graphics}
  146. * @param w1 宽
  147. * @param h1 高
  148. * @param color 颜色
  149. */
  150. private void shearY(Graphics g, int w1, int h1, Color color) {
  151. int period = RandomUtil.randomInt(this.height >> 1);
  152. int frames = 20;
  153. int phase = 7;
  154. for (int i = 0; i < w1; i++) {
  155. double d = (double) (period >> 1) * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
  156. g.copyArea(i, 0, 1, h1, 0, (int) d);
  157. g.setColor(color);
  158. // 擦除原位置的痕迹
  159. g.drawLine(i, (int) d, i, 0);
  160. g.drawLine(i, (int) d + h1, i, h1);
  161. }
  162. }
  163. }