Java AWT生成滑动验证码

谁都会走 提交于 2019-12-01 22:16:59

最近工作需要在登录时使用滑动验证码做登录校验,在生成验证码图片的时候碰到了不小的麻烦 : (。

网上能查到的做法基本上都是使用一张已存在的实际图片作为滑块的图形模板,然后按照此模板做二重循环逐像素地从源图像抠出滑块以及新图。

这种方式优点就是可以控制每个像素,如改变局部的 Alpha 值、做局部的高斯模糊等。缺点也显而易见——太麻烦,且二重循环对内存也是不小的负担。

笔者的想法是:首先生成滑块 1)根据规定好的路径生成闭合区间;2)设置 Graphics2D 的渲染区间,限制仅渲染此闭合区间的内部setClip方法);3)使用原图来渲染当前 Image(drawImage方法)。最后在源图扣掉滑块的地方加上阴影。

研究了一下 Java 的 awt 图像处理,这里对这几天的成果做一个记录,本文仅涉及图像处理方面,去掉了其它业务逻辑。

生成闭合区间

Java awt中提供了Shape接口,来代表任意一种几何图形。考虑我们要实现如下滑块图形:

上图左边的滑块可以先简化为右侧,原理是一样的。右边的图像一共有 A、B、C、D、E、F 六个顶点。其中 CD 之间是一段圆弧。Graphics2D的坐标系从左上角开始,X轴往下、Y轴往右是正方向。这里先算出各顶点坐标:A(0,0)、B(0,50)、C(15,50)、D(35,50)、E(50,50)、F(50,0)。

注意到H1'和H2'两个点。CD 间的圆弧由三阶贝塞尔曲线表示,H1' 和 H2' 就是此贝塞尔曲线的控制点,坐标为:H1'(15, 50+40/3),H2'(35, 50+40/3)。

下面先来看看绘制的形状是否正确(为了美观,X轴和Y轴都向正方向平移了30像素):

import javax.swing.*;
import java.awt.*;
import java.awt.geom.GeneralPath;

public class CreateSlideShape extends JComponent {

    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        // 设置线条粗细
        g2d.setStroke(new BasicStroke(3));
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        GeneralPath path2D = new GeneralPath();
        path2D.moveTo(offsetX, offsetY);
        path2D.lineTo(offsetX, 50 + offsetY);
        path2D.lineTo(15 + offsetX, 50 + offsetY);
        float f = 13.333f;
        path2D.curveTo(15 + offsetX, 50.f + f + offsetY, 35 + offsetX, 50.f + f + offsetY, 35 + offsetX, 50 + offsetY);
        path2D.lineTo(50 + offsetX, 50 + offsetY);
        path2D.lineTo(50 + offsetX, +offsetY);
        path2D.closePath();

        g2d.draw(path2D);
    }

    public static void main(String args[]) {
        JFrame window = new JFrame();
        window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        window.setBounds(30, 30, 150, 150);
        window.getContentPane().add(new CreateSlideShape());
        window.setVisible(true);
    }

    private static final int offsetX = 30;
    private static final int offsetY = 30;
}

生成的图片如下:

滑块渲染 + 源图阴影

大体思路上边已经说过了,直接上代码。注意坐标系的变化:

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;

/**
 * 滑动验证码Demo
 *
 * @author Landas
 */
public class ImageClip {

    /**
     * 生成滑块,共分为两步:
     * 1. 根据形状来绘制边框
     * 2. 裁剪指定位置的源图到新的滑块图片
     *
     * @param sourceImage 源图片
     * @param sliderShape 要生成的滑块图形
     * @return 使用源图生成的滑块
     */
    private static BufferedImage clip(BufferedImage sourceImage, Shape sliderShape) {
        // 设置透明背景
        ColorModel cm = ColorModel.getRGBdefault();
        WritableRaster wr = cm.createCompatibleWritableRaster(slider_width, slider_height);
        BufferedImage out = new BufferedImage(cm, wr, cm.isAlphaPremultiplied(), null);
        // 设置抗锯齿
        Graphics2D g2d = out.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 绘制丑丑的边框当做阴影。实际使用时需要调整线宽以及颜色
        g2d.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        g2d.setColor(new Color(160, 160, 160));
        g2d.draw(sliderShape);

        // 调整当前 Graphics2D 的 context,使用 sliderShape 来限制渲染区域
        g2d.setClip(sliderShape);
        // 使用源图来渲染滑块,实际使用时注意坐标变换
        g2d.drawImage(sourceImage, 0, 0, slider_width, slider_height, x, y, x + slider_width, y + slider_height, null);
        // 绘制完毕,释放
        g2d.dispose();
        return out;
    }

    /**
     * 源图裁剪掉滑块的部分需要加上阴影。
     * 这里是简单粗暴的直接改变源图指定位置指定形状的 Alpha 值。实际使用中需要采用其他策略
     *
     * @param sourceImage 源图
     * @param sliderShape 滑块形状
     */
    private static void inverseClip(BufferedImage sourceImage, Shape sliderShape) {
        Graphics2D g = sourceImage.createGraphics();
        // 亮度简单设置为 50%
        float percentage = .5f;
        int brightness = (int) (256 - 256 * percentage);
        g.setColor(new Color(0, 0, 0, brightness));
        // 坐标重新定位
        g.translate(x, y);
        g.fill(sliderShape);
        g.dispose();
    }

    // 生成滑块形状,请根据项目需要调整
    private static Shape createShape() {
        GeneralPath path2D = new GeneralPath();

        path2D.moveTo(0, 0);
        path2D.lineTo(0, 50);
        path2D.lineTo(15, 50);
        path2D.curveTo(15, 63.333f, 35, 63.333f, 35, 50);
        path2D.lineTo(50, 50);
        path2D.lineTo(50, 0);
        path2D.closePath();

        return path2D;
    }

    public static void main(String[] args) throws IOException {
        // 读取源图
        BufferedImage background = ImageIO.read(new File("D:/source.jpg"));
        // 生成滑块的形状
        Shape sliderShape = createShape();
        // 生成滑块的图片并保存
        BufferedImage sliderImage = clip(background, sliderShape);
        File outputfile = new File("D:/slider.png");
        ImageIO.write(sliderImage, "png", outputfile);
        // 源图加上阴影并保存
        inverseClip(background, sliderShape);
        File outputfile2 = new File("D:/sourceCopy.png");
        ImageIO.write(background, "png", outputfile2);
    }

    // 指定裁剪滑块的位置,项目中需要随机生成
    private static final int x = 300;
    private static final int y = 130;

    // 滑块的高度和宽度,正方形边长 50,半圆半径 10,边框预留 2,一共是62。根据项目需要调整
    private static final int slider_width = 62;
    private static final int slider_height = 62;
}

有关绘图的代码就这么多,实际项目中其他细节已经略去。下面来看看效果,源图片是作者丑丑的鼠绘,^_^。

源图:

滑块:

抠掉滑块之后的背景图片:

Fin.~

下面列出参考文档:

  1. Graphics with AWT and Java 2D(很详细,强烈推荐)
  2. 三阶贝塞尔曲线拟合圆弧的一般公式
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!