思路:
package com.sxapp.message.utils;
import net.coobird.thumbnailator.Thumbnails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
* @author Dirk
* @Description 滑动验证码工具类
* @Date Create at 2019-06-26 11:20
*/
public class CaptchaUtil {
private static final Logger log = LoggerFactory.getLogger(CaptchaUtil.class);
/**
* @param originImage 原图
* @param templateImage 抠图模板
* @param x 原图上的抠图位置
* @param y 原图上的抠图位置
* @return 滑动小方块图和带阴影的原图
*/
public static Map<String, String> getCaptchaImages(BufferedImage originImage, BufferedImage templateImage,
int x, int y) {
// 滑动小方块图
BufferedImage blockImage = new BufferedImage(templateImage.getWidth(), templateImage.getHeight(),
templateImage.getType());
// 带alpha通道的原图的复制 --> 带阴影的原图
BufferedImage shadowImage = new BufferedImage(originImage.getWidth(), originImage.getHeight(),
BufferedImage.TYPE_4BYTE_ABGR);
// 原图像矩阵
int[][] originImageData = getData(originImage);
// 模板图像矩阵
int[][] templateImageData = getData(templateImage);
// shadowImage 设置不透明
for (int i = 0; i < originImageData.length; i++) {
for (int j = 0; j < originImageData[i].length; j++) {
int color = originImage.getRGB(i, j);
final int r = (color >> 16) & 0xff;
final int g = (color >> 8) & 0xff;
final int b = color & 0xff;
shadowImage.setRGB(i, j, colorToRGB(255, r, g, b));
}
}
// 抠图
for (int i = 0; i < templateImageData.length; i++) {
for (int j = 0; j < templateImageData[i].length; j++) {
int alpha = (templateImageData[i][j] >> 24) & 0xff;
if (alpha > 128) {
// 复制原图抠图区域到滑动小方块
int color = originImageData[x + i][y + j];
blockImage.setRGB(i, j, color);
// shadowImage 抠图区域加权法灰度化并设置半透明
final int r = (color >> 16) & 0xff;
final int g = (color >> 8) & 0xff;
final int b = color & 0xff;
int gray = (int) (0.3 * r + 0.59 * g + 0.11 * b);
shadowImage.setRGB(x + i, y + j, colorToRGB(150, gray, gray, gray));
} else if (alpha > 0) {
// 将templateImage的描边复制给blockImage
int color = templateImageData[i][j];
final int r = getRed(color);
final int g = getGreen(color);
final int b = getBlue(color);
blockImage.setRGB(i, j, colorToRGB(alpha, r, g, b));
}
}
}
// 图片处理:png转jpg,高斯模糊,图片压缩
shadowImage = imageCompression(blur(png2jpg(shadowImage)), 0.5, 1);
blockImage = imageCompression(blockImage, 0.5, 1);
//返回扣出的小方块图和带阴影的原图
Map<String, String> result = new HashMap<>(2);
result.put("blockImage", imageToBase64(blockImage, "png"));
result.put("shadowImage", imageToBase64(shadowImage, "jpg"));
return result;
}
/**
* @param bufferedImage 源图片
* @param size 压缩尺寸比例,取值0 ~ 1,1 表示原图大小
* @param quality 压缩质量比例,取值 0 ~ 1,1 表示原图质量
* @return
*/
private static BufferedImage imageCompression(BufferedImage bufferedImage, double size, double quality) {
try {
return Thumbnails.of(bufferedImage)
.scale(size)
.outputQuality(quality)
.asBufferedImage();
} catch (IOException e) {
log.error("图片压缩失败:【{}】", e.getMessage());
e.printStackTrace();
}
return bufferedImage;
}
private static BufferedImage png2jpg(BufferedImage png) {
BufferedImage jpg = new BufferedImage(png.getWidth(), png.getHeight(), BufferedImage.TYPE_INT_RGB);
jpg.createGraphics().drawImage(png, 0, 0, Color.BLACK, null);
return jpg;
}
/**
* 生成图像矩阵
*
* @param bufferedImage 图片
* @return 像素信息的二维数组
*/
private static int[][] getData(BufferedImage bufferedImage) {
int[][] data = new int[bufferedImage.getWidth()][bufferedImage.getHeight()];
for (int i = 0; i < bufferedImage.getWidth(); i++) {
for (int j = 0; j < bufferedImage.getHeight(); j++) {
data[i][j] = bufferedImage.getRGB(i, j);
}
}
return data;
}
private static int getAlpha(int color) {
return (color >> 24) & 0xff;
}
private static int getRed(int color) {
return (color >> 16) & 0xff;
}
private static int getGreen(int color) {
return (color >> 8) & 0xff;
}
private static int getBlue(int color) {
return color & 0xff;
}
/**
* 颜色分量转换为RGB值
*
* @param alpha
* @param red
* @param green
* @param blue
* @return
*/
private static int colorToRGB(int alpha, int red, int green, int blue) {
int newPixel = 0;
newPixel += alpha;
newPixel = newPixel << 8;
newPixel += red;
newPixel = newPixel << 8;
newPixel += green;
newPixel = newPixel << 8;
newPixel += blue;
return newPixel;
}
/**
* base64字符串和图片互转
*
* @param image 图片
* @param formatName 图片格式
* @return base64字符串
*/
private static String imageToBase64(BufferedImage image, String formatName) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(image, formatName, os);
} catch (IOException e) {
e.printStackTrace();
}
byte[] imageData = os.toByteArray();
String base64Image = "data:image/jpg;base64,";
base64Image = base64Image.concat(java.util.Base64.getEncoder().encodeToString(imageData));
base64Image = base64Image.replaceAll("\n", "").replaceAll("\r", "");
return base64Image;
}
/**
* base64字符串和图片互转
*
* @param base64String base64字符串
* @return 图片
*/
public static BufferedImage base64StringToImage(String base64String) {
try {
byte[] imageData = Base64.getDecoder().decode(base64String);
ByteArrayInputStream is = new ByteArrayInputStream(imageData);
return ImageIO.read(is);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 获得抠图位置
*
* @param origin 原图的长宽
* @param template 抠图模板图的长宽
* @return 随机抠图位置
*/
public static int getPosition(Integer origin, Integer template) {
int difference = origin - template;
if (difference <= 0) {
return 0;
}
int position = new Random().nextInt(difference);
if (position < origin / 2 && position + origin / 2 + template < origin) {
position += origin / 2;
}
return position;
}
/**
* 生成验证码
*
* @param length 验证码长度
* @return 验证码
*/
public static String generateCaptcha(Integer length) {
StringBuilder captcha = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
captcha.append(random.nextInt(10));
}
return captcha.toString();
}
/**
* 高斯模糊
*
* @param originImage 原图
* @return 修改后的图
*/
private static BufferedImage blur(BufferedImage originImage) {
BufferedImage newImage = new BufferedImage(originImage.getWidth(), originImage.getHeight(),
originImage.getType());
int[][] data = getData(originImage);
int width = originImage.getWidth();
int height = originImage.getHeight();
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
if (i == 0 || j == 0 || i == width - 1 || j == height - 1) {
newImage.setRGB(i, j, originImage.getRGB(i, j));
continue;
}
// 获得原图该点为中心的九宫矩阵
int[][] color = new int[][]{{data[i - 1][j - 1], data[i - 1][j], data[i - 1][j + 1]},
{data[i][j - 1], data[i][j], data[i][j + 1]},
{data[i + 1][j - 1], data[i + 1][j], data[i + 1][j + 1]}};
newImage.setRGB(i, j, gaussianBlur(color));
}
}
return newImage;
}
/**
* 通过该点的九宫矩阵像素计算得到经过高斯模糊后该像素的颜色
*
* @param color 二维3*3数组
* @return
*/
private static int gaussianBlur(int[][] color) {
float[][] template = new float[][]{{0.0947416f, 0.118318f, 0.0947416f}, {0.118318f, 0.147761f, 0.118318f},
{0.0947416f, 0.118318f, 0.0947416f}};
float alpha = 0f;
float r = 0f;
float g = 0f;
float b = 0f;
for (int i = 0; i < template.length; i++) {
for (int j = 0; j < template[i].length; j++) {
alpha += template[i][j] * getAlpha(color[i][j]);
r += template[i][j] * getRed(color[i][j]);
g += template[i][j] * getGreen(color[i][j]);
b += template[i][j] * getBlue(color[i][j]);
}
}
return colorToRGB(Math.round(alpha), Math.round(r), Math.round(g), Math.round(b));
}
}
Random random = new Random();
int targetNo = random.nextInt(Integer.parseInt(targetCount)) + 1;
int templateNo = random.nextInt(Integer.parseInt(templateCount)) + 1;
log.debug("targetNo: [{}] templateNo: [{}]", targetNo, templateNo);
BufferedImage oriImage;
BufferedImage templateImage;
Map<String, String> resultMap;
int x, y;
try {
// 读取原图和模板图 根据具体路径设置
oriImage = ImageIO.read(Objects.requireNonNull(getClass().getClassLoader()
.getResourceAsStream("static/targets/" + targetNo + ".jpg")));
templateImage = ImageIO.read(Objects.requireNonNull(getClass().getClassLoader()
.getResourceAsStream("static/templates/" + templateNo + ".png")));
// 生成抠图位置
x = CaptchaUtil.getPosition(oriImage.getWidth(), templateImage.getWidth());
y = CaptchaUtil.getPosition(oriImage.getHeight(), templateImage.getHeight());
// 获取滑动小块图像和带阴影原图
resultMap = CaptchaUtil.getCaptchaImages(oriImage, templateImage, x, y);
} catch (IOException e) {
e.printStackTrace();
}
resultMap.put("Y", String.valueOf((int) ((float) y / (float) oriImage.getHeight() * 100)));
return resultMap;
模板说明:
- 背景部分不透明度为0
- 黑色边框为不透明度小于等于50%
- 中间图案部分不透明度大于50%