java生成验证码图片-高清图片验证码生成|干扰码|字符随机旋转

技术栈:java

场景描述:前端登录时请求后端生成的验证码图片,同时提供验证码正确性验证、有效期验证等

话不多说线上验证码生成后的效果:

(注:后端生成的验证码图片为png,背景透明的,上述例子的网页背景为故意设置的其他颜色用于验证码的效果展示)

以下是代码说明:

controller进行接口映射,调用工具类的生成验证码函数

工具类代码如下:

提供了三个方法:

  1. 生成水印数字验证码,将数值和生成时间存放至当次会话session中,将水印图片写入响应的输出流中
  2. 获取当次会话请求中的数字验证码
  3. 对用户输入的数字验证码进行验证
package com.demo.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.InvalidSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.util.Assert;
import com.demo.captcha.AbstractCaptcha;
import com.demo.captcha.PngCaptcha;
import com.demo.enums.TimeMillis;
import com.demo.exception.ExpiredException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.io.IOException;

/**
 * 验证码工具类
 * @author 何昌杰
 */
@Slf4j
public class CaptchaUtil {

    /**
     * 生成水印数字验证码,将数值和生成时间存放至当次会话session中,将水印图片写入响应的输出流中
     * @param request 请求
     * @param response 响应
     * @return true:成功; false: 失败
     */
    public static boolean set(HttpServletRequest request, HttpServletResponse response){
        AbstractCaptcha abstractCaptcha = new PngCaptcha(74,30,4,new Font("楷体", Font.BOLD, 32));
        HttpSession session = request.getSession();
        try {
            abstractCaptcha.out(response.getOutputStream());
            session.setAttribute("captcha", abstractCaptcha.text());
            session.setAttribute("captchaStamp", System.currentTimeMillis());
            return true;
        } catch (IOException e) {
            log.error(e.getMessage());
            return false;
        }
    }

    /**
     * 获取当次会话请求中的数字验证码
     * 注意:
     *     1、数字验证码有效期为1分钟,超过1分钟则失效,失效则抛出异常
     *     2、当前会话中无数字验证码则返回null
     * @return 数字验证码
     */
    public static String get(){
        try {
            Subject subject = SecurityUtils.getSubject();
            Session session = subject.getSession();
            String captcha = (String) session.getAttribute("captcha");
            long captchaStamp = (long) session.getAttribute("captchaStamp");
            long currentTimeMillis = System.currentTimeMillis();
            if((currentTimeMillis - captchaStamp) > TimeMillis.MINUTE){
                // 超过有效期
                throw new ExpiredException("验证码已过期,请刷新后重试");
            } else {
                return captcha;
            }
        } catch (InvalidSessionException | ExpiredException e) {
            log.error(e.getMessage());
            return null;
        }
    }

    /**
     * 对用户输入的数字验证码进行验证
     * 注意:
     *     1、数字验证码有效期为1分钟,超过1分钟则失效,失效则抛出异常
     *     2、当前会话中无数字验证码则返回false
     *     3、用户输入的数字验证码匹配失败则返回false
     *
     * @param captchaSource 用户输入的数字验证码
     * @return true:验证通过;false:验证失败
     */
    public static boolean check(String captchaSource){
        Assert.notNull(captchaSource, "请输入验证码后重试");
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        String captcha = (String) session.getAttribute("captcha");
        long captchaStamp = (long) session.getAttribute("captchaStamp");
        long currentTimeMillis = System.currentTimeMillis();
        if((currentTimeMillis - captchaStamp) > TimeMillis.MINUTE){
            // 超过有效期
            throw new ExpiredException("验证码已过期,请刷新后重试");
        } else {
            return captchaSource.equalsIgnoreCase(captcha);
        }
    }
}

其他所需的类及结构如下:

java生成验证码图片-高清图片验证码生成|干扰码|字符随机旋转_第1张图片

(注:MainTest.java为测试类,这里不贴出,下述类中使用的Randoms类不在此目录中,源代码见文末)

AbstractCaptcha.java

package com.demo.captcha;

import lombok.Getter;
import lombok.Setter;

import java.awt.*;
import java.io.OutputStream;

import static com.demo.utils.Randoms.alpha;
import static com.demo.utils.Randoms.num;

/**
 * 

验证码抽象类,暂时不支持中文

* @author 何昌杰 on 2019-10-12 19:32:00 */ @Getter @Setter public abstract class AbstractCaptcha { /** * 字体 */ Font font = new Font("Verdana", Font.ITALIC|Font.BOLD, 28); /** * 验证码随机字符长度 */ int len = 5; /** * 验证码显示宽度 */ protected int width = 150; /** * 验证码显示高度 */ int height = 40; /** * 随机字符串 */ private String chars = null; /** * 生成随机字符数组 * @return 字符数组 */ char[] alphas() { char[] cs = new char[len]; for(int i = 0;i maxRgb) { fc = 255; } if (bc > maxRgb) { bc = 255; } return new Color(fc + num(bc - fc), fc + num(bc - fc), fc + num(bc - fc)); } /** * 验证码输出,抽象方法,由子类实现 * @param os 输出流 */ public abstract void out(OutputStream os); /** * 获取随机字符串 * @return string */ public String text() { return chars; } }

Encoder.java

package com.demo.captcha;

import java.io.IOException;
import java.io.OutputStream;

/**
 * @author: wuhongjun
 * @version:1.0
 */
public class Encoder
{
    private static final int EOF = -1;

    private int imgW, imgH;
    private byte[] pixAry;
    private int initCodeSize;
    private int remaining;
    private int curPixel;

    // GIFCOMPR.C       - GIF Image compression routines
    //
    // Lempel-Ziv compression based on 'compress'.  GIF modifications by
    // David Rowley ([email protected])

    // General DEFINEs

    static final int BITS = 12;

    static final int HSIZE = 5003; // 80% occupancy

    // GIF Image compression - modified 'compress'
    //
    // Based on: compress.c - File compression ala IEEE Computer, June 1984.
    //
    // By Authors:  Spencer W. Thomas      (decvax!harpo!utah-cs!utah-gr!thomas)
    //              Jim McKie              (decvax!mcvax!jim)
    //              Steve Davies           (decvax!vax135!petsd!peora!srd)
    //              Ken Turkowski          (decvax!decwrl!turtlevax!ken)
    //              James A. Woods         (decvax!ihnp4!ames!jaw)
    //              Joe Orost              (decvax!vax135!petsd!joe)

    int n_bits; // number of bits/code
    int maxbits = BITS; // user settable max # bits/code
    int maxcode; // maximum code, given n_bits
    int maxmaxcode = 1 << BITS; // should NEVER generate this code

    int[] htab = new int[HSIZE];
    int[] codetab = new int[HSIZE];

    int hsize = HSIZE; // for dynamic table sizing

    int free_ent = 0; // first unused entry

    // block compression parameters -- after all codes are used up,
    // and compression rate changes, start over.
    boolean clear_flg = false;

    // Algorithm:  use open addressing double hashing (no chaining) on the
    // prefix code / next character combination.  We do a variant of Knuth's
    // algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
    // secondary probe.  Here, the modular division first probe is gives way
    // to a faster exclusive-or manipulation.  Also do block compression with
    // an adaptive reset, whereby the code table is cleared when the compression
    // ratio decreases, but after the table fills.  The variable-length output
    // codes are re-sized at this point, and a special CLEAR code is generated
    // for the decompressor.  Late addition:  construct the table according to
    // file size for noticeable speed improvement on small files.  Please direct
    // questions about this implementation to ames!jaw.

    int g_init_bits;

    int ClearCode;
    int EOFCode;

    // output
    //
    // Output the given code.
    // Inputs:
    //      code:   A n_bits-bit integer.  If == -1, then EOF.  This assumes
    //              that n_bits =< wordsize - 1.
    // Outputs:
    //      Outputs code to the file.
    // Assumptions:
    //      Chars are 8 bits long.
    // Algorithm:
    //      Maintain a BITS character long buffer (so that 8 codes will
    // fit in it exactly).  Use the VAX insv instruction to insert each
    // code in turn.  When the buffer fills up empty it and start over.

    int cur_accum = 0;
    int cur_bits = 0;

    int masks[] =
            {
                    0x0000,
                    0x0001,
                    0x0003,
                    0x0007,
                    0x000F,
                    0x001F,
                    0x003F,
                    0x007F,
                    0x00FF,
                    0x01FF,
                    0x03FF,
                    0x07FF,
                    0x0FFF,
                    0x1FFF,
                    0x3FFF,
                    0x7FFF,
                    0xFFFF };

    // Number of characters so far in this 'packet'
    int a_count;

    // Define the storage for the packet accumulator
    byte[] accum = new byte[256];

    //----------------------------------------------------------------------------
    Encoder(int width, int height, byte[] pixels, int color_depth) {
        imgW = width;
        imgH = height;
        pixAry = pixels;
        initCodeSize = Math.max(2, color_depth);
    }

    // Add a character to the end of the current packet, and if it is 254
    // characters, flush the packet to disk.
    void char_out(byte c, OutputStream outs) throws IOException {
        accum[a_count++] = c;
        if (a_count >= 254) {
            flushChar(outs);
        }
    }

    // Clear out the hash table

    // table clear for block compress
    void cl_block(OutputStream outs) throws IOException {
        cl_hash(hsize);
        free_ent = ClearCode + 2;
        clear_flg = true;

        output(ClearCode, outs);
    }

    // reset code table
    void cl_hash(int hsize) {
        for (int i = 0; i < hsize; ++i) {
            htab[i] = -1;
        }
    }

    void compress(int init_bits, OutputStream outs) throws IOException {
        int fcode;
        int i /* = 0 */;
        int c;
        int ent;
        int disp;
        int hsize_reg;
        int hshift;

        // Set up the globals:  g_init_bits - initial number of bits
        g_init_bits = init_bits;

        // Set up the necessary values
        clear_flg = false;
        n_bits = g_init_bits;
        maxcode = MAXCODE(n_bits);

        ClearCode = 1 << (init_bits - 1);
        EOFCode = ClearCode + 1;
        free_ent = ClearCode + 2;

        a_count = 0; // clear packet

        ent = nextPixel();

        hshift = 0;
        for (fcode = hsize; fcode < 65536; fcode *= 2) {
            ++hshift;
        }
        hshift = 8 - hshift; // set hash code range bound

        hsize_reg = hsize;
        cl_hash(hsize_reg); // clear hash table

        output(ClearCode, outs);

        outer_loop : while ((c = nextPixel()) != EOF) {
            fcode = (c << maxbits) + ent;
            i = (c << hshift) ^ ent; // xor hashing

            if (htab[i] == fcode) {
                ent = codetab[i];
                continue;
            } else if (htab[i] >= 0) // non-empty slot
            {
                disp = hsize_reg - i; // secondary hash (after G. Knott)
                if (i == 0) {
                    disp = 1;
                }
                do {
                    if ((i -= disp) < 0) {
                        i += hsize_reg;
                    }

                    if (htab[i] == fcode) {
                        ent = codetab[i];
                        continue outer_loop;
                    }
                } while (htab[i] >= 0);
            }
            output(ent, outs);
            ent = c;
            if (free_ent < maxmaxcode) {
                codetab[i] = free_ent++; // code -> hashtable
                htab[i] = fcode;
            } else {
                cl_block(outs);
            }
        }
        // Put out the final code.
        output(ent, outs);
        output(EOFCode, outs);
    }

    //----------------------------------------------------------------------------
    void encode(OutputStream os) throws IOException {
        os.write(initCodeSize); // write "initial code size" byte

        remaining = imgW * imgH; // reset navigation variables
        curPixel = 0;

        compress(initCodeSize + 1, os); // compress and write the pixel data

        os.write(0); // write block terminator
    }

    // Flush the packet to disk, and reset the accumulator
    void flushChar(OutputStream outs) throws IOException {
        if (a_count > 0) {
            outs.write(a_count);
            outs.write(accum, 0, a_count);
            a_count = 0;
        }
    }

    final int MAXCODE(int n_bits) {
        return (1 << n_bits) - 1;
    }

    //----------------------------------------------------------------------------
    // Return the next pixel from the image
    //----------------------------------------------------------------------------
    private int nextPixel() {
        if (remaining == 0) {
            return EOF;
        }

        --remaining;

        byte pix = pixAry[curPixel++];

        return pix & 0xff;
    }

    void output(int code, OutputStream outs) throws IOException {
        cur_accum &= masks[cur_bits];

        if (cur_bits > 0) {
            cur_accum |= (code << cur_bits);
        } else {
            cur_accum = code;
        }

        cur_bits += n_bits;

        while (cur_bits >= 8) {
            char_out((byte) (cur_accum & 0xff), outs);
            cur_accum >>= 8;
            cur_bits -= 8;
        }

        // If the next entry is going to be too big for the code size,
        // then increase it, if possible.
        if (free_ent > maxcode || clear_flg) {
            if (clear_flg) {
                maxcode = MAXCODE(n_bits = g_init_bits);
                clear_flg = false;
            } else {
                ++n_bits;
                if (n_bits == maxbits) {
                    maxcode = maxmaxcode;
                } else {
                    maxcode = MAXCODE(n_bits);
                }
            }
        }

        if (code == EOFCode) {
            // At EOF, write the rest of the buffer.
            while (cur_bits > 0) {
                char_out((byte) (cur_accum & 0xff), outs);
                cur_accum >>= 8;
                cur_bits -= 8;
            }

            flushChar(outs);
        }
    }
}

GifCaptcha.java

package com.demo.captcha;

import com.demo.utils.Streams;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import static com.demo.utils.Randoms.neg;

/**
 * 

Gif验证码类

* * @author 何昌杰 on 2019-10-12 19:32:00 */ public class GifCaptcha extends AbstractCaptcha { public GifCaptcha() {} private GifCaptcha(int width, int height) { this.width = width; this.height = height; } public GifCaptcha(int width, int height, int len) { this(width, height); this.len = len; } public GifCaptcha(int width, int height, int len, Font font) { this(width, height, len); this.font = font; } @Override public void out(OutputStream os) { try { GifEncoder gifEncoder = new GifEncoder(); //生成字符 gifEncoder.start(os); gifEncoder.setQuality(10); gifEncoder.setDelay(100); gifEncoder.setRepeat(0); BufferedImage frame; char[] rands = alphas(); for (int i = 0; i < len; i++) { frame = graphicsImage(rands, i); gifEncoder.addFrame(frame); frame.flush(); } gifEncoder.finish(); } finally { Streams.close(os); } } /** * 画随机码图 * * @param strs 字符数组 * @param flag 透明度使用 * @return BufferedImage */ private BufferedImage graphicsImage(char[] strs, int flag) { BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); //或得图形上下文 Graphics2D g2d = (Graphics2D) image.getGraphics(); //利用指定颜色填充背景 g2d.setColor(new Color(187,187,187)); g2d.fillRect(0, 0, width, height); AlphaComposite ac3; g2d.setFont(font); int y = height - 2, w = width/len; float x, x1, y1; g2d.setColor(new Color(46, 46, 46)); /* 画字符串 */ for(int i = 0; i < len; i++) { ac3 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, getAlpha(flag, i)); g2d.setComposite(ac3); x = w * i + 2; double angle = neg(30) * Math.PI / 180; x1 = (float) (x * Math.cos(angle) + y * Math.sin(angle)); y1 = (float) (y * Math.cos(angle) - x * Math.sin(angle)); g2d.rotate(angle); g2d.drawString(strs[i] + "", x1, y1); g2d.rotate(-angle); } g2d.dispose(); return image; } /** * 获取透明度,从0到1,自动计算步长 * @return float 透明度 */ private float getAlpha(int i, int j) { int num = i + j; float r = (float) 1 / len, s = (len + 1) * r; return num > len ? (num * r - s) : num * r; } }


GifEncoder.java

package com.demo.captcha;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.*;

/**
 * Class AnimatedGifEncoder - Encodes a GIF file consisting of one or
 * more frames.
 * 
 * Example:
 *    AnimatedGifEncoder e = new AnimatedGifEncoder();
 *    e.start(outputFileName);
 *    e.setDelay(1000);   // 1 frame per sec
 *    e.addFrame(image1);
 *    e.addFrame(image2);
 *    e.finish();
 * 
* No copyright asserted on the source code of this class. May be used * for any purpose, however, refer to the Unisys LZW patent for restrictions * on use of the associated Encoder class. Please forward any corrections * to questions at fmsware.com. * * @author wuhongjun * @version 1.03 November 2003 * */ public class GifEncoder { /** * image size */ protected int width; protected int height; /** * transparent color if given */ protected Color transparent = null; /** * transparent index in color table */ protected int transIndex; /** * no repeat */ protected int repeat = -1; /** * frame delay (hundredths) */ protected int delay = 0; /** * ready to output frames */ protected boolean started = false; protected OutputStream out; /** * current frame */ protected BufferedImage image; /** * BGR byte array from frame */ protected byte[] pixels; /** * converted frame indexed to palette */ protected byte[] indexedPixels; /** * number of bit planes */ protected int colorDepth; /** * RGB palette */ protected byte[] colorTab; /** * active palette entries */ protected boolean[] usedEntry = new boolean[256]; /** * color table size (bits-1) */ protected int palSize = 7; /** * disposal code (-1 = use default) */ protected int dispose = -1; /** * close stream when finished */ protected boolean closeStream = false; protected boolean firstFrame = true; /** * if false, get size from first frame */ protected boolean sizeSet = false; /** * default sample interval for quantizer */ protected int sample = 10; /** * Sets the delay time between each frame, or changes it * for subsequent frames (applies to last frame added). * * @param ms int delay time in milliseconds */ public void setDelay(int ms) { delay = Math.round(ms / 10.0f); } /** * Sets the GIF frame disposal code for the last added frame * and any subsequent frames. Default is 0 if no transparent * color has been set, otherwise 2. * @param code int disposal code. */ public void setDispose(int code) { if (code >= 0) { dispose = code; } } /** * Sets the number of times the set of GIF frames * should be played. Default is 1; 0 means play * indefinitely. Must be invoked before the first * image is added. * * @param iter int number of iterations. * @return */ public void setRepeat(int iter) { if (iter >= 0) { repeat = iter; } } /** * Sets the transparent color for the last added frame * and any subsequent frames. * Since all colors are subject to modification * in the quantization process, the color in the final * palette for each frame closest to the given color * becomes the transparent color for that frame. * May be set to null to indicate no transparent color. * * @param c Color to be treated as transparent on display. */ public void setTransparent(Color c) { transparent = c; } /** * Adds next GIF frame. The frame is not written immediately, but is * actually deferred until the next frame is received so that timing * data can be inserted. Invoking finish() flushes all * frames. If setSize was not invoked, the size of the * first image is used for all subsequent frames. * * @param im BufferedImage containing frame to write. * @return true if successful. */ public boolean addFrame(BufferedImage im) { if ((im == null) || !started) { return false; } boolean ok = true; try { if (!sizeSet) { // use first frame's size setSize(im.getWidth(), im.getHeight()); } image = im; getImagePixels(); // convert to correct format if necessary analyzePixels(); // build color table & map pixels if (firstFrame) { writeLSD(); // logical screen descriptior writePalette(); // global color table if (repeat >= 0) { // use NS app extension to indicate reps writeNetscapeExt(); } } writeGraphicCtrlExt(); // write graphic control extension writeImageDesc(); // image descriptor if (!firstFrame) { writePalette(); // local color table } writePixels(); // encode and write pixel data firstFrame = false; } catch (IOException e) { ok = false; } return ok; } /** * added by alvaro */ public boolean outFlush() { boolean ok = true; try { out.flush(); return ok; } catch (IOException e) { ok = false; } return ok; } public byte[] getFrameByteArray() { return ((ByteArrayOutputStream) out).toByteArray(); } /** * Flushes any pending data and closes output file. * If writing to an OutputStream, the stream is not * closed. */ public boolean finish() { if (!started) { return false; } boolean ok = true; started = false; try { // gif trailer out.write(0x3b); out.flush(); if (closeStream) { out.close(); } } catch (IOException e) { ok = false; } return ok; } public void reset() { // reset for subsequent use transIndex = 0; out = null; image = null; pixels = null; indexedPixels = null; colorTab = null; closeStream = false; firstFrame = true; } /** * Sets frame rate in frames per second. Equivalent to * setDelay(1000/fps). * * @param fps float frame rate (frames per second) */ public void setFrameRate(float fps) { if (fps != 0f) { delay = Math.round(100f / fps); } } /** * Sets quality of color quantization (conversion of images * to the maximum 256 colors allowed by the GIF specification). * Lower values (minimum = 1) produce better colors, but slow * processing significantly. 10 is the default, and produces * good color mapping at reasonable speeds. Values greater * than 20 do not yield significant improvements in speed. * * @param quality int greater than 0. */ public void setQuality(int quality) { if (quality < 1) { quality = 1; } sample = quality; } /** * Sets the GIF frame size. The default size is the * size of the first frame added if this method is * not invoked. * * @param w int frame width. * @param h int frame width. */ public void setSize(int w, int h) { if (started && !firstFrame) { return; } width = w; height = h; if (width < 1) { width = 320; } if (height < 1) { height = 240; } sizeSet = true; } /** * Initiates GIF file creation on the given stream. The stream * is not closed automatically. * * @param os OutputStream on which GIF images are written. * @return false if initial write failed. */ public boolean start(OutputStream os) { if (os == null) { return false; } boolean ok = true; closeStream = false; out = os; try { /** * header */ writeString("GIF89a"); } catch (IOException e) { ok = false; } return started = ok; } /** * Initiates writing of a GIF file with the specified name. * * @param file String containing output file name. * @return false if open or initial write failed. */ public boolean start(String file) { boolean ok = true; try { out = new BufferedOutputStream(new FileOutputStream(file)); ok = start(out); closeStream = true; } catch (IOException e) { ok = false; } return started = ok; } /** * Analyzes image colors and creates color map. */ protected void analyzePixels() { int len = pixels.length; int nPix = len / 3; indexedPixels = new byte[nPix]; Quant nq = new Quant(pixels, len, sample); // initialize quantizer // create reduced palette colorTab = nq.process(); // convert map from BGR to RGB for (int i = 0; i < colorTab.length; i += 3) { byte temp = colorTab[i]; colorTab[i] = colorTab[i + 2]; colorTab[i + 2] = temp; usedEntry[i / 3] = false; } // map image pixels to new palette int k = 0; for (int i = 0; i < nPix; i++) { int index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff); usedEntry[index] = true; indexedPixels[i] = (byte) index; } pixels = null; colorDepth = 8; palSize = 7; // get closest match to transparent color if specified if (transparent != null) { transIndex = findClosest(transparent); } } /** * Returns index of palette color closest to c * */ protected int findClosest(Color c) { if (colorTab == null) { return -1; } int r = c.getRed(); int g = c.getGreen(); int b = c.getBlue(); int minpos = 0; int dmin = 256 * 256 * 256; int len = colorTab.length; for (int i = 0; i < len;) { int dr = r - (colorTab[i++] & 0xff); int dg = g - (colorTab[i++] & 0xff); int db = b - (colorTab[i] & 0xff); int d = dr * dr + dg * dg + db * db; int index = i / 3; if (usedEntry[index] && (d < dmin)) { dmin = d; minpos = index; } i++; } return minpos; } /** * Extracts image pixels into byte array "pixels" */ protected void getImagePixels() { int w = image.getWidth(); int h = image.getHeight(); int type = image.getType(); if ((w != width) || (h != height) || (type != BufferedImage.TYPE_3BYTE_BGR)) { // create new image with right size/format BufferedImage temp = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR); Graphics2D g = temp.createGraphics(); g.drawImage(image, 0, 0, null); image = temp; } pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData(); } /** * Writes Graphic Control Extension */ protected void writeGraphicCtrlExt() throws IOException { // extension introducer out.write(0x21); // GCE label out.write(0xf9); // data block size out.write(4); int transp, disp; if (transparent == null) { transp = 0; // dispose = no action disp = 0; } else { transp = 1; // force clear if using transparent color disp = 2; } if (dispose >= 0) { // user override disp = dispose & 7; } disp <<= 2; // packed fields out.write(0 | // 1:3 reserved disp | // 4:6 disposal 0 | // 7 user input - 0 = none transp); // 8 transparency flag writeShort(delay); // delay x 1/100 sec out.write(transIndex); // transparent color index out.write(0); // block terminator } /** * Writes Image Descriptor */ protected void writeImageDesc() throws IOException { out.write(0x2c); // image separator writeShort(0); // image position x,y = 0,0 writeShort(0); writeShort(width); // image size writeShort(height); // packed fields if (firstFrame) { // no LCT - GCT is used for first (or only) frame out.write(0); } else { // specify normal LCT out.write(0x80 | // 1 local color table 1=yes 0 | // 2 interlace - 0=no 0 | // 3 sorted - 0=no 0 | // 4-5 reserved palSize); // 6-8 size of color table } } /** * Writes Logical Screen Descriptor */ protected void writeLSD() throws IOException { // logical screen size writeShort(width); writeShort(height); // packed fields out.write((0x80 | // 1 : global color table flag = 1 (gct used) 0x70 | // 2-4 : color resolution = 7 0x00 | // 5 : gct sort flag = 0 palSize)); // 6-8 : gct size out.write(0); // background color index out.write(0); // pixel aspect ratio - assume 1:1 } /** * Writes Netscape application extension to define * repeat count. */ protected void writeNetscapeExt() throws IOException { out.write(0x21); // extension introducer out.write(0xff); // app extension label out.write(11); // block size writeString("NETSCAPE" + "2.0"); // app id + auth code out.write(3); // sub-block size out.write(1); // loop sub-block id writeShort(repeat); // loop count (extra iterations, 0=repeat forever) out.write(0); // block terminator } /** * Writes color table */ protected void writePalette() throws IOException { out.write(colorTab, 0, colorTab.length); int n = (3 * 256) - colorTab.length; for (int i = 0; i < n; i++) { out.write(0); } } /** * Encodes and writes pixel data */ protected void writePixels() throws IOException { Encoder encoder = new Encoder(width, height, indexedPixels, colorDepth); encoder.encode(out); } /** * Write 16-bit value to output stream, LSB first */ protected void writeShort(int value) throws IOException { out.write(value & 0xff); out.write((value >> 8) & 0xff); } /** * Writes string to output stream */ protected void writeString(String s) throws IOException { for (int i = 0; i < s.length(); i++) { out.write((byte) s.charAt(i)); } } }

PngCaptcha.java

package com.demo.captcha;
import com.demo.utils.Streams;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import static com.demo.utils.Randoms.num;
import static com.demo.utils.Randoms.neg;

/**
 * 

png格式验证码

* @author 何昌杰 on 2019/10/12 */ public class PngCaptcha extends AbstractCaptcha { public PngCaptcha(){} private PngCaptcha(int width, int height) { this.width = width; this.height = height; } public PngCaptcha(int width, int height, int len){ this(width,height); this.len = len; } public PngCaptcha(int width, int height, int len, Font font){ this(width,height,len); this.font = font; } /** * 生成验证码 */ @Override public void out(OutputStream out){ graphicsImage(alphas(), out); } /** * 画随机码图 * @param strs 文本 * @param out 输出流 */ private boolean graphicsImage(char[] strs, OutputStream out){ boolean ok = false; try{ BufferedImage bi = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB); Graphics2D g = (Graphics2D)bi.getGraphics(); //设置抗锯齿 g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); Color color ; int len = strs.length; g.setColor(new Color(187,187,187,0)); g.fillRect(0,0,width,height); // 随机画干扰的蛋蛋 for(int i=0;i < 15;i++){ color = color(150, 250); g.setColor(color); // 绘制干扰圈 g.drawOval(num(width), num(height), 5+num(10), 5+num(10)); } g.setFont(font); int y = height - 4, w = width/len; float x, x1, y1; /* 画字符串 */ g.setColor(new Color(46, 46, 46)); for(int i = 0; i < len; i++) { x = w * i + 2; double angle = neg(30) * Math.PI / 180; x1 = (float) (x * Math.cos(angle) + y * Math.sin(angle)); y1 = (float) (y * Math.cos(angle) - x * Math.sin(angle)); g.rotate(angle); g.drawString(strs[i] + "", x1, y1); g.rotate(-angle); } ImageIO.write(bi, "png", out); out.flush(); ok = true; }catch (IOException ignored){} finally{ Streams.close(out); } return ok; } }


Quant.java

package com.demo.captcha;

/**
 * 

* * @author: wuhongjun * @version:1.0 */ public class Quant { protected static final int netsize = 256; /* number of colours used */ /* four primes near 500 - assume no image has a length so large */ /* that it is divisible by all four primes */ protected static final int prime1 = 499; protected static final int prime2 = 491; protected static final int prime3 = 487; protected static final int prime4 = 503; protected static final int minpicturebytes = (3 * prime4); /* minimum size for input image */ /* Program Skeleton ---------------- [select samplefac in range 1..30] [read image from input file] pic = (unsigned char*) malloc(3*width*height); initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output image header, using writecolourmap(f)] inxbuild(); write output image using inxsearch(b,g,r) */ /* Network Definitions ------------------- */ protected static final int maxnetpos = (netsize - 1); protected static final int netbiasshift = 4; /* bias for colour values */ protected static final int ncycles = 100; /* no. of learning cycles */ /* defs for freq and bias */ protected static final int intbiasshift = 16; /* bias for fractions */ protected static final int intbias = (((int) 1) << intbiasshift); protected static final int gammashift = 10; /* gamma = 1024 */ protected static final int gamma = (((int) 1) << gammashift); protected static final int betashift = 10; protected static final int beta = (intbias >> betashift); /* beta = 1/1024 */ protected static final int betagamma = (intbias << (gammashift - betashift)); /* defs for decreasing radius factor */ protected static final int initrad = (netsize >> 3); /* for 256 cols, radius starts */ protected static final int radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ protected static final int radiusbias = (((int) 1) << radiusbiasshift); protected static final int initradius = (initrad * radiusbias); /* and decreases by a */ protected static final int radiusdec = 30; /* factor of 1/30 each cycle */ /* defs for decreasing alpha factor */ protected static final int alphabiasshift = 10; /* alpha starts at 1.0 */ protected static final int initalpha = (((int) 1) << alphabiasshift); protected int alphadec; /* biased by 10 bits */ /* radbias and alpharadbias used for radpower calculation */ protected static final int radbiasshift = 8; protected static final int radbias = (((int) 1) << radbiasshift); protected static final int alpharadbshift = (alphabiasshift + radbiasshift); protected static final int alpharadbias = (((int) 1) << alpharadbshift); /* Types and Global Variables -------------------------- */ protected byte[] thepicture; /* the input image itself */ protected int lengthcount; /* lengthcount = H*W*3 */ protected int samplefac; /* sampling factor 1..30 */ // typedef int pixel[4]; /* BGRc */ protected int[][] network; /* the network itself - [netsize][4] */ protected int[] netindex = new int[256]; /* for network lookup - really 256 */ protected int[] bias = new int[netsize]; /* bias and freq arrays for learning */ protected int[] freq = new int[netsize]; protected int[] radpower = new int[initrad]; /* radpower for precomputation */ /* Initialise network in range (0,0,0) to (255,255,255) and set parameters ----------------------------------------------------------------------- */ public Quant(byte[] thepic, int len, int sample) { int i; int[] p; thepicture = thepic; lengthcount = len; samplefac = sample; network = new int[netsize][]; for (i = 0; i < netsize; i++) { network[i] = new int[4]; p = network[i]; p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; freq[i] = intbias / netsize; /* 1/netsize */ bias[i] = 0; } } public byte[] colorMap() { byte[] map = new byte[3 * netsize]; int[] index = new int[netsize]; for (int i = 0; i < netsize; i++) index[network[i][3]] = i; int k = 0; for (int i = 0; i < netsize; i++) { int j = index[i]; map[k++] = (byte) (network[j][0]); map[k++] = (byte) (network[j][1]); map[k++] = (byte) (network[j][2]); } return map; } /* Insertion sort of network and building of netindex[0..255] (to do after unbias) ------------------------------------------------------------------------------- */ public void inxbuild() { int i, j, smallpos, smallval; int[] p; int[] q; int previouscol, startpos; previouscol = 0; startpos = 0; for (i = 0; i < netsize; i++) { p = network[i]; smallpos = i; smallval = p[1]; /* index on g */ /* find smallest in i..netsize-1 */ for (j = i + 1; j < netsize; j++) { q = network[j]; if (q[1] < smallval) { /* index on g */ smallpos = j; smallval = q[1]; /* index on g */ } } q = network[smallpos]; /* swap p (i) and q (smallpos) entries */ if (i != smallpos) { j = q[0]; q[0] = p[0]; p[0] = j; j = q[1]; q[1] = p[1]; p[1] = j; j = q[2]; q[2] = p[2]; p[2] = j; j = q[3]; q[3] = p[3]; p[3] = j; } /* smallval entry is now in position i */ if (smallval != previouscol) { netindex[previouscol] = (startpos + i) >> 1; for (j = previouscol + 1; j < smallval; j++) netindex[j] = i; previouscol = smallval; startpos = i; } } netindex[previouscol] = (startpos + maxnetpos) >> 1; for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */ } /* Main Learning Loop ------------------ */ public void learn() { int i, j, b, g, r; int radius, rad, alpha, step, delta, samplepixels; byte[] p; int pix, lim; if (lengthcount < minpicturebytes) samplefac = 1; alphadec = 30 + ((samplefac - 1) / 3); p = thepicture; pix = 0; lim = lengthcount; samplepixels = lengthcount / (3 * samplefac); delta = samplepixels / ncycles; alpha = initalpha; radius = initradius; rad = radius >> radiusbiasshift; if (rad <= 1) rad = 0; for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); //fprintf(stderr,"beginning 1D learning: initial radius=%d\n", rad); if (lengthcount < minpicturebytes) step = 3; else if ((lengthcount % prime1) != 0) step = 3 * prime1; else { if ((lengthcount % prime2) != 0) step = 3 * prime2; else { if ((lengthcount % prime3) != 0) step = 3 * prime3; else step = 3 * prime4; } } i = 0; while (i < samplepixels) { b = (p[pix + 0] & 0xff) << netbiasshift; g = (p[pix + 1] & 0xff) << netbiasshift; r = (p[pix + 2] & 0xff) << netbiasshift; j = contest(b, g, r); altersingle(alpha, j, b, g, r); if (rad != 0) alterneigh(rad, j, b, g, r); /* alter neighbours */ pix += step; if (pix >= lim) pix -= lengthcount; i++; if (delta == 0) delta = 1; if (i % delta == 0) { alpha -= alpha / alphadec; radius -= radius / radiusdec; rad = radius >> radiusbiasshift; if (rad <= 1) rad = 0; for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); } } //fprintf(stderr,"finished 1D learning: final alpha=%f !\n",((float)alpha)/initalpha); } /* Search for BGR values 0..255 (after net is unbiased) and return colour index ---------------------------------------------------------------------------- */ public int map(int b, int g, int r) { int i, j, dist, a, bestd; int[] p; int best; bestd = 1000; /* biggest possible dist is 256*3 */ best = -1; i = netindex[g]; /* index on g */ j = i - 1; /* start at netindex[g] and work outwards */ while ((i < netsize) || (j >= 0)) { if (i < netsize) { p = network[i]; dist = p[1] - g; /* inx key */ if (dist >= bestd) i = netsize; /* stop iter */ else { i++; if (dist < 0) dist = -dist; a = p[0] - b; if (a < 0) a = -a; dist += a; if (dist < bestd) { a = p[2] - r; if (a < 0) a = -a; dist += a; if (dist < bestd) { bestd = dist; best = p[3]; } } } } if (j >= 0) { p = network[j]; dist = g - p[1]; /* inx key - reverse dif */ if (dist >= bestd) j = -1; /* stop iter */ else { j--; if (dist < 0) dist = -dist; a = p[0] - b; if (a < 0) a = -a; dist += a; if (dist < bestd) { a = p[2] - r; if (a < 0) a = -a; dist += a; if (dist < bestd) { bestd = dist; best = p[3]; } } } } } return (best); } public byte[] process() { learn(); unbiasnet(); inxbuild(); return colorMap(); } /* Unbias network to give byte values 0..255 and record position i to prepare for sort ----------------------------------------------------------------------------------- */ public void unbiasnet() { int i, j; for (i = 0; i < netsize; i++) { network[i][0] >>= netbiasshift; network[i][1] >>= netbiasshift; network[i][2] >>= netbiasshift; network[i][3] = i; /* record colour no */ } } /* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] --------------------------------------------------------------------------------- */ protected void alterneigh(int rad, int i, int b, int g, int r) { int j, k, lo, hi, a, m; int[] p; lo = i - rad; if (lo < -1) lo = -1; hi = i + rad; if (hi > netsize) hi = netsize; j = i + 1; k = i - 1; m = 1; while ((j < hi) || (k > lo)) { a = radpower[m++]; if (j < hi) { p = network[j++]; try { p[0] -= (a * (p[0] - b)) / alpharadbias; p[1] -= (a * (p[1] - g)) / alpharadbias; p[2] -= (a * (p[2] - r)) / alpharadbias; } catch (Exception e) { } // prevents 1.3 miscompilation } if (k > lo) { p = network[k--]; try { p[0] -= (a * (p[0] - b)) / alpharadbias; p[1] -= (a * (p[1] - g)) / alpharadbias; p[2] -= (a * (p[2] - r)) / alpharadbias; } catch (Exception e) { } } } } /* Move neuron i towards biased (b,g,r) by factor alpha ---------------------------------------------------- */ protected void altersingle(int alpha, int i, int b, int g, int r) { /* alter hit neuron */ int[] n = network[i]; n[0] -= (alpha * (n[0] - b)) / initalpha; n[1] -= (alpha * (n[1] - g)) / initalpha; n[2] -= (alpha * (n[2] - r)) / initalpha; } /* Search for biased BGR values ---------------------------- */ protected int contest(int b, int g, int r) { /* finds closest neuron (min dist) and updates freq */ /* finds best neuron (min dist-bias) and returns position */ /* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ /* bias[i] = gamma*((1/netsize)-freq[i]) */ int i, dist, a, biasdist, betafreq; int bestpos, bestbiaspos, bestd, bestbiasd; int[] n; bestd = ~(((int) 1) << 31); bestbiasd = bestd; bestpos = -1; bestbiaspos = bestpos; for (i = 0; i < netsize; i++) { n = network[i]; dist = n[0] - b; if (dist < 0) dist = -dist; a = n[1] - g; if (a < 0) a = -a; dist += a; a = n[2] - r; if (a < 0) a = -a; dist += a; if (dist < bestd) { bestd = dist; bestpos = i; } biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); if (biasdist < bestbiasd) { bestbiasd = biasdist; bestbiaspos = i; } betafreq = (freq[i] >> betashift); freq[i] -= betafreq; bias[i] += (betafreq << gammashift); } freq[bestpos] += beta; bias[bestpos] -= betagamma; return (bestbiaspos); } }

Randoms.java

package com.demo.utils;

import java.util.Random;

/**
 * 随机树工具类
 * @author He Changjie on 2019/10/12
 */
public class Randoms
{
    private static final Random RANDOM = new Random();
    /**
     * 定义验证码字符.去除了O和I等容易混淆的字母
     */
    public static final char[] ALPHA = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '2', '3', '4', '5', '6', '7', '8', '9'};

    /**
     * 产生两个数之间的随机数
     * @param min 小数
     * @param max 比min大的数
     * @return int 随机数字
     */
    public static int num(int min, int max)
    {
        return min + RANDOM.nextInt(max - min);
    }

    /**
     * 生成6位随机数字验证码
     * @return 数值
     */
    public static int verificationCode(){
        return num(100000, 999999);
    }

    /**
     * 产生0--num的随机数,不包括num
     * @param num 数字
     * @return int 随机数字
     */
    public static int num(int num)
    {
        return RANDOM.nextInt(num);
    }

    /**
     * 产生-num到num的随机数
     * @param num 数字
     * @return int 随机数字
     */
    public static int neg(int num)
    {
        return RANDOM.nextInt() % (num + 1);
    }

    public static char alpha()
    {
        return ALPHA[num(0, ALPHA.length)];
    }

}

 

你可能感兴趣的:(java)