生成图形验证码工具类
import com.alibaba.fastjson.JSONObject;
import com.common.util.FileUtils;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
/**
* 图形验证码生成工具类
*/
@Slf4j
@UtilityClass
public class VerifyImageUtil {
private static int ORI_WIDTH = 280; //源文件宽度
private static int ORI_HEIGHT = 171; //源文件高度
private static int WIDTH; //模板图宽度
private static int HEIGHT; //模板图高度
private static int BOLD = 5;
/**
* 源文件宽度
*/
private static int ORIGINAL_WIDTH = 240;
/**
* 源文件高度
*/
private static int ORIGINAL_HEIGHT = 160;
private static final String IMG_FILE_TYPE = "png";
public static Map getImgVerify(HttpServletRequest request) throws Exception {
log.info("进入getImgVerify方法");
long startTime = System.currentTimeMillis();
long startTime1 = System.currentTimeMillis();
HashMap<String, Object> resultMap = new HashMap<>();
Random random = new Random();
//模板图片
InputStream stream = VerifyImageUtil.class.getClassLoader().getResourceAsStream("templates/template.png");
File templateFile = new File("template.png");
FileUtils.copyInputStreamToFile(stream, templateFile);
log.info("生成图形验证码 获取templates,耗时:{} ===== end =====", System.currentTimeMillis() - startTime1);
startTime1 = System.currentTimeMillis();
int targetNo = random.nextInt(10) + 1;
stream = VerifyImageUtil.class.getClassLoader().getResourceAsStream("targets/" + targetNo + ".png");
File targetFile = new File(targetNo + ".png");
FileUtils.copyInputStreamToFile(stream, targetFile);
log.info("生成图形验证码 获取targets:{}.jpg,耗时:{} ===== end =====", targetNo, System.currentTimeMillis() - startTime1);
startTime1 = System.currentTimeMillis();
Map<String, Object> pictureMap = pictureTemplateCut(templateFile, targetFile);
/**
* 将用户手机号以及滑块坐标保存入缓存
*/
HashMap sessionMap = new HashMap();
sessionMap.put("X", pictureMap.get("X"));
sessionMap.put("Y", pictureMap.get("Y"));
log.info("X:" + sessionMap.get("X"));
log.info("Y:" + sessionMap.get("Y"));
request.getSession().setAttribute("imgXyMap", sessionMap);
log.info("生成图形验证码 获取pictureMap,耗时:{} ===== end =====", System.currentTimeMillis() - startTime1);
log.info("生成图形验证码 从request获取imgXyMap:{}", JSONObject.toJSONString(request.getSession().getAttribute("imgXyMap")));
resultMap.put("newImage", pictureMap.get("newImage"));
resultMap.put("oriCopyImage", pictureMap.get("oriCopyImage"));
resultMap.put("Y", pictureMap.get("Y"));
resultMap.put("oirWidth", pictureMap.get("oirWidth"));
resultMap.put("oriHeight", pictureMap.get("oriHeight"));
resultMap.put("newWidth", pictureMap.get("newWidth"));
resultMap.put("newHeight", pictureMap.get("newHeight"));
log.info("生成图形验证码 oirWidth:{},oriHeight:{},newWidth:{},newHeight:{}",
pictureMap.get("oirWidth"), pictureMap.get("oriHeight"), pictureMap.get("newWidth"), pictureMap.get("newHeight"));
log.info("生成图形验证码,总耗时:{} ===== end =====", System.currentTimeMillis() - startTime);
return resultMap;
}
/**
* 根据模版切图
* @param templateFile 模版文件
* @param targetFile 目标文件
* @param templateType 模版文件类型
* @param targetType 目标文件类型
* @param X X坐标
* @param Y Y坐标
* @return
* @throws Exception
*/
private static Map<String, byte[]> pictureTemplatesCut(File templateFile, File targetFile, String templateType, String targetType, int X, int Y) throws Exception {
Map<String, byte[]> pictureMap = new HashMap<>();
// 文件类型
String templateFiletype = templateType;
String oriFiletype = targetType;
if (StringUtils.isEmpty(templateFiletype) || StringUtils.isEmpty(oriFiletype)) {
throw new RuntimeException("文件类型为空");
}
// 源文件流
File Orifile = targetFile;
InputStream oriis = new FileInputStream(Orifile);
// 模板图
BufferedImage imageTemplate = ImageIO.read(templateFile);
WIDTH = imageTemplate.getWidth();
HEIGHT = imageTemplate.getHeight();
//generateCutoutCoordinates();
// 最终图像
BufferedImage newImage = new BufferedImage(WIDTH, HEIGHT, imageTemplate.getType());
Graphics2D graphics = newImage.createGraphics();
graphics.setBackground(Color.white);
int bold = 5;
// 获取感兴趣的目标区域
BufferedImage targetImageNoDeal = getTargetArea(X, Y, WIDTH, HEIGHT, oriis, oriFiletype);
// 根据模板图片抠图
newImage = DealCutPictureByTemplate(targetImageNoDeal, imageTemplate, newImage);
// 设置“抗锯齿”的属性
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
graphics.drawImage(newImage, 0, 0, null);
graphics.dispose();
ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
ImageIO.write(newImage, "png", os);//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
byte[] newImages = os.toByteArray();
pictureMap.put("newImage", newImages);
// 源图生成遮罩
BufferedImage oriImage = ImageIO.read(Orifile);
byte[] oriCopyImages = DealOriPictureByTemplate(oriImage, imageTemplate, X, Y);
pictureMap.put("oriCopyImage", oriCopyImages);
return pictureMap;
}
/**
* 抠图后原图生成
*
* @param oriImage
* @param templateImage
* @param x
* @param y
* @return
* @throws Exception
*/
private static byte[] DealOriPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x,
int y) throws Exception {
// 源文件备份图像矩阵 支持alpha通道的rgb图像
BufferedImage ori_copy_image = new BufferedImage(oriImage.getWidth(), oriImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
// 源文件图像矩阵
int[][] oriImageData = getData(oriImage);
// 模板图像矩阵
int[][] templateImageData = getData(templateImage);
//copy 源图做不透明处理
for (int i = 0; i < oriImageData.length; i++) {
for (int j = 0; j < oriImageData[0].length; j++) {
int rgb = oriImage.getRGB(i, j);
int r = (0xff & rgb);
int g = (0xff & (rgb >> 8));
int b = (0xff & (rgb >> 16));
//无透明处理
rgb = r + (g << 8) + (b << 16) + (255 << 24);
ori_copy_image.setRGB(i, j, rgb);
}
}
for (int i = 0; i < templateImageData.length; i++) {
for (int j = 0; j < templateImageData[0].length - 5; j++) {
int rgb = templateImage.getRGB(i, j);
//对源文件备份图像(x+i,y+j)坐标点进行透明处理
if (rgb != 16777215 && rgb <= 0) {
int rgb_ori = ori_copy_image.getRGB(x + i, y + j);
int r = (0xff & rgb_ori);
int g = (0xff & (rgb_ori >> 8));
int b = (0xff & (rgb_ori >> 16));
rgb_ori = r + (g << 8) + (b << 16) + (150 << 24);
//源代码
//ori_copy_image.setRGB(x + i, y + j, rgb_ori);
//修改后的值
ori_copy_image.setRGB(x + i, y + j, 65280);
} else {
//do nothing
}
}
}
ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
ImageIO.write(ori_copy_image, "png", os);//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
byte b[] = os.toByteArray();//从流中获取数据数组。
return b;
}
/**
* 根据模板图片抠图
*
* @param oriImage
* @param templateImage
* @return
*/
private static BufferedImage DealCutPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage,
BufferedImage targetImage) throws Exception {
// 源文件图像矩阵
int[][] oriImageData = getData(oriImage);
// 模板图像矩阵
int[][] templateImageData = getData(templateImage);
// 模板图像宽度
for (int i = 0; i < templateImageData.length; i++) {
// 模板图片高度
for (int j = 0; j < templateImageData[0].length; j++) {
//防止数组越界判断
if (i == (templateImageData.length - 1) || j == (templateImageData[0].length - 1)) {
continue;
}
// 如果模板图像当前像素点不是白色 copy源文件信息到目标图片中
int rgb = templateImageData[i][j];
if (rgb != 16777215 && rgb <= 0) {
targetImage.setRGB(i, j, oriImageData[i][j]);
}
}
}
return targetImage;
}
/**
* 获取目标区域
*
* @param x 随机切图坐标x轴位置
* @param y 随机切图坐标y轴位置
* @param targetWidth 切图后目标宽度
* @param targetHeight 切图后目标高度
* @param ois 源文件输入流
* @return
* @throws Exception
*/
private static BufferedImage getTargetArea(int x, int y, int targetWidth, int targetHeight, InputStream ois,
String filetype) throws Exception {
Iterator<ImageReader> imageReaderList = ImageIO.getImageReadersByFormatName(filetype);
ImageReader imageReader = imageReaderList.next();
// 获取图片流
ImageInputStream iis = ImageIO.createImageInputStream(ois);
// 输入源中的图像将只按顺序读取
imageReader.setInput(iis, true);
ImageReadParam param = imageReader.getDefaultReadParam();
Rectangle rec = new Rectangle(x, y, targetWidth, targetHeight);
param.setSourceRegion(rec);
BufferedImage targetImage = imageReader.read(0, param);
return targetImage;
}
/**
* 生成图像矩阵
*
* @param
* @return
* @throws Exception
*/
private static int[][] getData(BufferedImage bimg) throws Exception {
int[][] data = new int[bimg.getWidth()][bimg.getHeight()];
for (int i = 0; i < bimg.getWidth(); i++) {
for (int j = 0; j < bimg.getHeight(); j++) {
data[i][j] = bimg.getRGB(i, j);
}
}
return data;
}
/**
* @description 随机生成抠图坐标
* @author LL
*/
private static HashMap generateCutoutCoordinates() {
Random random = new Random();
int widthDifference = ORI_WIDTH - WIDTH;
int heightDifference = ORI_HEIGHT - HEIGHT;
int X, Y;
if (widthDifference <= 0) {
X = 5;
} else {
X = random.nextInt(ORI_WIDTH - WIDTH) + 5;
}
if (heightDifference <= 0) {
Y = 5;
} else {
Y = random.nextInt(ORI_HEIGHT - HEIGHT) + 5;
}
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setMaximumFractionDigits(2);
HashMap map = new HashMap();
map.put("X", X);
map.put("Y", Y);
return map;
}
public static Boolean checkImgVerify(String X, HashMap imgXyMap) {
//Y轴固定,只需校验X轴
if (StringUtils.isEmpty(X) || !StringUtils.isNumeric(X)) {
return false;
}
if (imgXyMap != null && !imgXyMap.isEmpty() && imgXyMap.get("X") != null) {
Integer reqX = Integer.parseInt(imgXyMap.get("X").toString());
Integer getX = Integer.parseInt(X);
Integer comX = reqX - getX;
//允许误差±3(根据自身业务逻辑调整)
if (-3 < comX && comX < 3) {
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* 根据模板切图
*
* @param templateFile
* @param targetFile
* @return
*/
private static Map<String, Object> pictureTemplateCut(File templateFile, File targetFile) throws Exception {
log.info("pictureTemplateCut --------------------- 开始");
Map<String, Object> pictureMap = new HashMap<>();
//模板图
BufferedImage templateImage = ImageIO.read(templateFile);
//模板图宽度
int templateWidth = templateImage.getWidth();
//模板图高度
int templateHeight = templateImage.getHeight();
//原图
BufferedImage originalImage = ImageIO.read(targetFile);
//原图宽度
int oriImageWidth = originalImage.getWidth();
//原图高度
int oriImageHeight = originalImage.getHeight();
/**
* 随机生成抠图坐标X,Y
* X轴距离右端targetWidth Y轴距离底部targetHeight以上
*/
Random random = new Random();
log.info("oriImageHeight:{},templateHeight:{}", oriImageHeight, templateHeight);
int widthRandom = random.nextInt(oriImageWidth - 2 * templateWidth) + templateWidth;
int heightRandom = random.nextInt(oriImageHeight - templateHeight);
log.info("原图大小X={},Y={},随机生成的坐标 X={},Y={}", oriImageWidth, oriImageHeight, widthRandom, heightRandom);
/**
* 新建一个和模板一样大小的图像
* TYPE_4BYTE_ABGR表示具有8位RGBA颜色分量的图像
* 取imageTemplate.getType()
*/
BufferedImage newImage = new BufferedImage(templateWidth, templateHeight, templateImage.getType());
//得到画笔对象
Graphics2D graphics = newImage.createGraphics();
//如果需要生成RGB格式,需要做如下配置,Transparency 设置透明
newImage = graphics.getDeviceConfiguration().createCompatibleImage(templateWidth, templateHeight, Transparency.TRANSLUCENT);
/**
* 新建的图像根据模板颜色赋值,源图生成遮罩
*/
cutByTemplate(originalImage, templateImage, newImage, widthRandom, heightRandom);
/**
* 设置“抗锯齿”的属性
*/
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setStroke(new BasicStroke(BOLD, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
graphics.drawImage(newImage, 0, 0, null);
graphics.dispose();
//新建流
ByteArrayOutputStream newImageOs = new ByteArrayOutputStream();
//利用ImageIO类提供的write方法,将png图片的数据模式写入流。
ImageIO.write(newImage, IMG_FILE_TYPE, newImageOs);
byte[] newImagebyte = newImageOs.toByteArray();
ByteArrayOutputStream oriImagesOs = new ByteArrayOutputStream();
ImageIO.write(originalImage, IMG_FILE_TYPE, oriImagesOs);
byte[] oriImageByte = oriImagesOs.toByteArray();
//原图片的抠图(小图)
pictureMap.put("newImage", Base64Utils.encode(newImagebyte));
//原图片(大图)
pictureMap.put("oriCopyImage", Base64Utils.encode(oriImageByte));
//抠图坐标 X
pictureMap.put("X", widthRandom);
//抠图坐标 Y
pictureMap.put("Y", heightRandom);
//原图(大图)宽度
pictureMap.put("oirWidth", ORIGINAL_WIDTH);
//原图(大图)高度
pictureMap.put("oriHeight", ORIGINAL_HEIGHT);
//抠图(小图)宽度
pictureMap.put("newWidth", templateWidth);
//抠图(小图)高度
pictureMap.put("newHeight", templateHeight);
return pictureMap;
}
/**
* @param originalImage 原图
* @param templateImage 模板图
* @param newImage 新扣除的小图
* @param x 随机扣取的X坐标
* @param y 随机扣取的Y坐标
*/
private static void cutByTemplate(BufferedImage originalImage, BufferedImage templateImage, BufferedImage newImage, int x, int y) {
//临时数组遍历用于高斯模糊存周边像素值
int[][] martrix = new int[3][3];
int[] values = new int[9];
int xLength = templateImage.getWidth();
int yLength = templateImage.getHeight();
// 模板图像宽度
for (int i = 0; i < xLength; i++) {
// 模板图片高度
for (int j = 0; j < yLength; j++) {
// 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
int rgb = templateImage.getRGB(i, j);
if (rgb < 0) {
newImage.setRGB(i, j, originalImage.getRGB(x + i, y + j));
//抠图区域高斯模糊
readPixel(originalImage, x + i, y + j, values);
fillMatrix(martrix, values);
// originalImage.setRGB(x + i, y + j, avgMatrix(martrix));
originalImage.setRGB(x + i, y + j, Color.white.getRGB());
}
//防止数组越界判断
if (i == (xLength - 1) || j == (yLength - 1)) {
continue;
}
int rightRgb = templateImage.getRGB(i + 1, j);
int downRgb = templateImage.getRGB(i, j + 1);
//描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0) || (rgb < 0 && downRgb >= 0)) {
newImage.setRGB(i, j, Color.lightGray.getRGB());
originalImage.setRGB(x + i, y + j, Color.lightGray.getRGB());
}
}
}
}
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();
}
}