生成图片滑动验证码图片

思路:

  1. 获得一张原图(originImage)和一张抠图模板(templateImage)
  2. 复制原图得到带alpha通道的原图的复制(shadowImage)
  3. 将原图的复制图先全部设置为不透明
  4. 将模板图(templateImage)所有不透明的像素的位置的滑块图(blockImage)替换成原图(originImage)的相应位置的像素
  5. 将原图的复制图(shadowImage)的抠图位置像素设置半透明
  6. 大功告成,返回原图的复制图(shadowImage)和滑块图(blockImage)

工具类

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%

你可能感兴趣的:(Java)