亲测实用的图片滑块验证码
- 准备图片 存放于resources目录下image文件下
- 编写滑块验证工具类以及返回实体对象
package com.facemetric.tradingsystem.commons.captcha;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.Random;
import java.util.UUID;
public class SlidePuzzleUtil {
private final static Logger logger = LoggerFactory.getLogger(SlidePuzzleUtil.class);
private static String BASE64_CUTTURE = "data:image/png;base64,";
// 大图宽度(原图裁剪拼图后的背景图)***---一定要和前端显示框大小匹配***
private static final Integer bigWidth = 414;
// 大图高度 ***---一定要和前端显示框大小匹配***
private static final Integer bigHeight = 253;
// 小图宽度(滑块拼图)
private static int smallWidth = 60;
// 小图高度
private static int smallHeight = 60;
// 小圆半径,即拼图上的凹凸轮廓半径
private static final Integer smallCircle = 8;
// 小圆距离点
private static int smallCircleR1 = smallCircle / 2;
/**
* 生成滑块拼图验证码
*
* @param input
* @return 返回null, 表示生成滑块拼图验证码异常
*/
public static SliderPuzzleInfo createImage(InputStream input) {
SliderPuzzleInfo sliderPuzzleInfo = new SliderPuzzleInfo();
try {
// 1.获取原图对象
BufferedImage originalImage = ImageIO.read(input);
// 规范原图的大小
BufferedImage bigImage = resizeImage(originalImage, bigWidth, bigHeight, true);
// 2.随机生成离左上角的(X,Y)坐标,上限为 [bigWidth-smallWidth, bigHeight-smallHeight]。最好离大图左边远一点,上限不要紧挨着大图边界
Random random = new Random();
// X范围:[2*smallWidth, bigWidth - 2*smallWidth - smallCircle)
int randomX = random.nextInt(bigWidth - 4 * smallWidth - smallCircle) + 2 * smallWidth;
// Y范围:[smallCircle, bigHeight - smallHeight - smallCircle)
int randomY = random.nextInt(bigHeight - smallHeight - 2 * smallCircle) + smallCircle;
logger.info("原图大小:{} x {},大图大小:{} x {},随机生成的坐标:(X,Y)=({},{})", originalImage.getWidth(), originalImage.getHeight(), bigImage.getWidth(), bigImage.getHeight(),
randomX, randomY);
// 3.创建小图对象
BufferedImage smallImage = new BufferedImage(smallWidth, smallHeight, BufferedImage.TYPE_4BYTE_ABGR);
// 4.随机生成拼图轮廓数据
int[][] slideTemplateData = getSlideTemplateData(smallWidth, smallHeight, smallCircle, smallCircleR1);
// 5.从大图中裁剪拼图。抠原图,裁剪拼图
cutByTemplate(bigImage, smallImage, slideTemplateData, randomX, randomY);
sliderPuzzleInfo.setPosX(randomX);
sliderPuzzleInfo.setPosY(randomY);
//sliderPuzzleInfo.setBigWidth(bigWidth);
//sliderPuzzleInfo.setBigHeight(bigHeight);
//sliderPuzzleInfo.setBigImage(bigImage);
sliderPuzzleInfo.setBigImageBase64(getImageBASE64(bigImage));
//sliderPuzzleInfo.setSmallWidth(smallWidth);
//sliderPuzzleInfo.setSmallHeight(smallHeight);
//sliderPuzzleInfo.setSmallImage(smallImage);
sliderPuzzleInfo.setSmallImageBase64(getImageBASE64(smallImage));
} catch (Exception e) {
sliderPuzzleInfo = null;
logger.info("创建生成滑块拼图验证码异常,e=", e);
} finally {
return sliderPuzzleInfo;
}
}
/**
* 获取拼图图轮廓数据
*
* @param smallWidth
* @param smallHeight
* @param smallCircle
* @param r1
* @return 0和1,其中0表示没有颜色,1有颜色
*/
private static int[][] getSlideTemplateData(int smallWidth, int smallHeight, int smallCircle, int r1) {
// 拼图轮廓数据
int[][] data = new int[smallWidth][smallHeight];
//拼图去掉凹凸的白色距离
// 不写smallCircleR1时,凹凸为半圆
int xBlank = smallWidth - smallCircle - smallCircleR1;
int yBlank = smallHeight - smallCircle - smallCircleR1;
// 圆的位置
int rxa = xBlank / 2;
int ryb = smallHeight - smallCircle;
double rPow = Math.pow(smallCircle, 2);
/**
* 计算需要的拼图轮廓(方块和凹凸),用二维数组来表示,二维数组有两张值,0和1,其中0表示没有颜色,1有颜色
* 圆的标准方程 (x-a)²+(y-b)²=r²,标识圆心(a,b),半径为r的圆
*/
for (int i = 0; i < smallWidth; i++) {
for (int j = 0; j < smallHeight; j++) {
// 圆在拼图下方内
double topR = Math.pow(i - rxa, 2) + Math.pow(j - 2, 2);
// 圆在拼图下方外
double downR = Math.pow(i - rxa, 2) + Math.pow(j - ryb, 2);
// 圆在拼图左侧内 || (i <= xBlank && leftR <= rPow)
//double leftR = Math.pow(i - 2, 2) + Math.pow(j - rxa, 2);
// 圆在拼图右侧外
double rightR = Math.pow(i - ryb, 2) + Math.pow(j - rxa, 2);
if ((j <= yBlank && topR <= rPow) || (j >= yBlank && downR >= rPow)
|| (i >= xBlank && rightR >= rPow)) {
data[i][j] = 0;
} else {
data[i][j] = 1;
}
}
}
return data;
}
/**
* 裁剪拼图
*
* @param bigImage - 原图规范大小之后的大图
* @param smallImage - 小图
* @param slideTemplateData - 拼图轮廓数据
* @param x - 坐标x
* @param y - 坐标y
*/
private static void cutByTemplate(BufferedImage bigImage, BufferedImage smallImage, int[][] slideTemplateData, int x, int y) {
int[][] martrix = new int[3][3];
int[] values = new int[9];
//拼图去掉凹凸的白色距离 // 不写smallCircleR1时,凹凸为半圆
int xBlank = smallWidth - smallCircle - smallCircleR1;
int yBlank = smallHeight - smallCircle - smallCircleR1;
// 创建shape区域,即原图抠图区域模糊和抠出小图
/**
* 遍历小图轮廓数据,创建shape区域。即原图抠图处模糊和抠出小图
*/
for (int i = 0; i < smallImage.getWidth(); i++) {
for (int j = 0; j < smallImage.getHeight(); j++) {
// 获取大图中对应位置变色
//logger.info("随机生成的坐标:(X,Y)=({},{}),(i,j=({},{}),获取原图大小:{} x {}", x, y, i, j, x + i, y + j);
int rgb_ori = bigImage.getRGB(x + i, y + j);
//0和1,其中0表示没有颜色,1有颜色
int rgb = slideTemplateData[i][j];
if (rgb == 1) {
// 设置小图中对应位置变色
smallImage.setRGB(i, j, rgb_ori);
// 大图抠图区域高斯模糊
readPixel(bigImage, x + i, y + j, values);
fillMatrix(martrix, values);
bigImage.setRGB(x + i, y + j, avgMatrix(martrix));
//边框颜色
Color white = new Color(230, 230, 230);
Color black = new Color(20, 20, 20);
//左侧边界,加重高亮阴暗
if (j < yBlank) {
bigImage.setRGB(x, y + j, Color.LIGHT_GRAY.getRGB());
smallImage.setRGB(0, j, Color.GRAY.getRGB());
}
} else {
// 这里把背景设为透明
smallImage.setRGB(i, j, rgb_ori & 0x00ffffff);
}
}
}
}
/**
* 图片转BASE64
*
* @param image
* @return
* @throws IOException
*/
public static String getImageBASE64(BufferedImage image) throws IOException {
byte[] imagedata = null;
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ImageIO.write(image, "png", bao);
imagedata = bao.toByteArray();
String BASE64IMAGE = Base64.getEncoder().encodeToString(imagedata);
BASE64IMAGE = BASE64IMAGE.replaceAll("\n", "").replaceAll("\r", "");
return BASE64_CUTTURE + BASE64IMAGE;
}
/**
* 改变图片大小
*
* @param image 原图
* @param width 目标宽度
* @param height 目标高度
* @return 目标图
*/
public static BufferedImage resizeImage(final Image image, int width, int height, boolean type) {
BufferedImage bufferedImage;
if (type) {
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
} else {
bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
final Graphics2D graphics2D = bufferedImage.createGraphics();
graphics2D.setComposite(AlphaComposite.Src);
// below three lines are for RenderingHints for better image quality at cost of
// higher processing time
graphics2D.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics2D.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics2D.setStroke(new BasicStroke(5, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
graphics2D.drawImage(image, 0, 0, width, height, null);
graphics2D.dispose();
return bufferedImage;
}
private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
int xStart = x - 1;
int yStart = y - 1;
int current = 0;
for (int i = xStart; i < 3 + xStart; i++) {
for (int j = yStart; j < 3 + yStart; j++) {
int tx = i;
if (tx < 0) {
tx = -tx;
} else if (tx >= img.getWidth()) {
tx = x;
}
int ty = j;
if (ty < 0) {
ty = -ty;
} else if (ty >= img.getHeight()) {
ty = y;
}
pixels[current++] = img.getRGB(tx, ty);
}
}
}
private static void fillMatrix(int[][] matrix, int[] values) {
int filled = 0;
for (int i = 0; i < matrix.length; i++) {
int[] x = matrix[i];
for (int j = 0; j < x.length; j++) {
x[j] = values[filled++];
}
}
}
private static int avgMatrix(int[][] matrix) {
int r = 0;
int g = 0;
int b = 0;
for (int i = 0; i < matrix.length; i++) {
int[] x = matrix[i];
for (int j = 0; j < x.length; j++) {
if (j == 1) {
continue;
}
Color c = new Color(x[j]);
r += c.getRed();
g += c.getGreen();
b += c.getBlue();
}
}
return new Color(r / 8, g / 8, b / 8).getRGB();
}
}
package com.facemetric.tradingsystem.commons.captcha;
import java.awt.image.BufferedImage;
/**
1. 返回实体对象
*/
public class SliderPuzzleInfo {
/**
* 大图宽度
*/
private Integer bigWidth;
/**
* 大图高度
*/
private Integer bigHeight;
/**
* 大图转BASE64字符串
*/
private String bigImageBase64;
/**
* 大图
*/
private BufferedImage bigImage;
/**
* 随机坐标Y
*/
private Integer posY;
/**
* 随机坐标X
*/
private Integer posX;
/**
* 小图宽度
*/
private Integer smallWidth;
/**
* 小图高度
*/
private Integer smallHeight;
/**
* 小图转BASE64字符串
*/
private String smallImageBase64;
/**
* 小图
*/
private BufferedImage smallImage;
/**
* 标识
*/
private String captUuid;
public String getCaptUuid() {
return captUuid;
}
public void setCaptUuid(String captUuid) {
this.captUuid = captUuid;
}
public Integer getBigWidth() {
return bigWidth;
}
public void setBigWidth(Integer bigWidth) {
this.bigWidth = bigWidth;
}
public Integer getBigHeight() {
return bigHeight;
}
public void setBigHeight(Integer bigHeight) {
this.bigHeight = bigHeight;
}
public String getBigImageBase64() {
return bigImageBase64;
}
public void setBigImageBase64(String bigImageBase64) {
this.bigImageBase64 = bigImageBase64;
}
public BufferedImage getBigImage() {
return bigImage;
}
public void setBigImage(BufferedImage bigImage) {
this.bigImage = bigImage;
}
public Integer getPosY() {
return posY;
}
public void setPosY(Integer posY) {
this.posY = posY;
}
public Integer getPosX() {
return posX;
}
public void setPosX(Integer posX) {
this.posX = posX;
}
public Integer getSmallWidth() {
return smallWidth;
}
public void setSmallWidth(Integer smallWidth) {
this.smallWidth = smallWidth;
}
public Integer getSmallHeight() {
return smallHeight;
}
public void setSmallHeight(Integer smallHeight) {
this.smallHeight = smallHeight;
}
public String getSmallImageBase64() {
return smallImageBase64;
}
public void setSmallImageBase64(String smallImageBase64) {
this.smallImageBase64 = smallImageBase64;
}
public BufferedImage getSmallImage() {
return smallImage;
}
public void setSmallImage(BufferedImage smallImage) {
this.smallImage = smallImage;
}
public SliderPuzzleInfo(Integer bigWidth, Integer bigHeight, String bigImageBase64, BufferedImage bigImage, Integer posY, Integer posX, Integer smallWidth, Integer smallHeight, String smallImageBase64, BufferedImage smallImage) {
this.bigWidth = bigWidth;
this.bigHeight = bigHeight;
this.bigImageBase64 = bigImageBase64;
this.bigImage = bigImage;
this.posY = posY;
this.posX = posX;
this.smallWidth = smallWidth;
this.smallHeight = smallHeight;
this.smallImageBase64 = smallImageBase64;
this.smallImage = smallImage;
}
public SliderPuzzleInfo() {
}
}
- controlller层代码调用
/**
* 生成滑块拼图验证码
*/
@RequestMapping(value = "/rest/captchaCode", method = RequestMethod.GET)
public SliderPuzzleInfo getImageCode(){
Random random = new Random();
int num = random.nextInt(4);
String[] images = new String[]{"bgimages/2.jpg", "bgimages/4.jpg", "bgimages/6.jpg", "bgimages/7.jpg"};
//匹配代码打包找不到图片资源 转化为 流的形式
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(images[num]);
SliderPuzzleInfo sliderPuzzleInfo = SlidePuzzleUtil.createImage(resourceAsStream);
if (sliderPuzzleInfo == null) {
// 要是生成失败
throw new NotErrorCause("生成滑块验证码失败,验证码生成为空");
}
String captUuid = UUID.randomUUID().toString().replace("-", "");
sliderPuzzleInfo.setCaptUuid(captUuid);
//将坐标存缓存中 方便取值
this.cacheService.set(captUuid, sliderPuzzleInfo.getPosX());
sliderPuzzleInfo.setPosX(null);
return sliderPuzzleInfo;
}
.
4 滑块验证代码
/**
* 得到图片验证的坐标,进行有效性校对
*
* @param captUuid
* @param ImageX
* @return
*/
@RequestMapping(
value = {"/rest/verifyCode"},
method = {RequestMethod.GET}
)
public Boolean verifyImage(@RequestParam("captUuid") String captUuid, @RequestParam("ImageX") String ImageX) {
Integer xPosition = this.cacheService.get(captUuid);
if (null == xPosition) {
return false;
}
//计算验证图片坐标的误差值
int absX = Math.abs(Integer.parseInt(ImageX) - xPosition);
if (absX < 6) {//也可以将坐标转化为 百分比形式验证(容错值 float tolerant = 0.02f)
this.cacheService.delete(captUuid);
return true;
}
return false;
}
最终效果
注:参考大神:https://blog.csdn.net/qq_42402854/article/details/125112511