123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- package captcha
- import (
- "github.com/golang/freetype"
- "github.com/golang/freetype/truetype"
- "gogs.qqck.cn/s/gotools/files"
- "gogs.qqck.cn/s/gotools/rand"
- "image"
- "image/color"
- "image/draw"
- "math"
- )
- type Captcha struct {
- frontColors []color.Color
- bkgColors []color.Color
- disturlvl DisturLevel
- fonts []*truetype.Font
- size image.Point
- }
- type StrType int
- const (
- NUM StrType = iota // 数字
- LOWER // 小写字母
- UPPER // 大写字母
- ALL // 全部
- CLEAR // 去除部分易混淆的字符
- )
- type DisturLevel int
- const (
- NORMAL DisturLevel = 4
- MEDIUM DisturLevel = 8
- HIGH DisturLevel = 16
- )
- func New() *Captcha {
- c := &Captcha{
- disturlvl: NORMAL,
- size: image.Point{X: 82, Y: 32},
- }
- c.frontColors = []color.Color{color.Black}
- c.bkgColors = []color.Color{color.White}
- return c
- }
- // AddFont 添加一个字体
- func (c *Captcha) AddFont(path string) {
- fontdata := files.Get(path)
- if len(fontdata) == 0 {
- return
- }
- c.AddFontBytes(fontdata)
- }
- // AddFontBytes allows to load font from slice of bytes, for example, load the font packed by https://github.com/jteeuwen/go-bindata
- func (c *Captcha) AddFontBytes(contents []byte) error {
- font, err := freetype.ParseFont(contents)
- if err != nil {
- return err
- }
- c.fonts = append(c.fonts, font)
- return nil
- }
- // SetFont 设置字体 可以设置多个
- func (c *Captcha) SetFont(paths ...string) {
- for _, v := range paths {
- c.AddFont(v)
- }
- }
- func (c *Captcha) SetDisturbance(d DisturLevel) {
- if d > 0 {
- c.disturlvl = d
- }
- }
- func (c *Captcha) SetFrontColor(colors ...color.Color) {
- if len(colors) > 0 {
- c.frontColors = c.frontColors[:0]
- for _, v := range colors {
- c.frontColors = append(c.frontColors, v)
- }
- }
- }
- func (c *Captcha) SetBkgColor(colors ...color.Color) {
- if len(colors) > 0 {
- c.bkgColors = c.bkgColors[:0]
- for _, v := range colors {
- c.bkgColors = append(c.bkgColors, v)
- }
- }
- }
- func (c *Captcha) SetSize(w, h int) {
- if w < 48 {
- w = 48
- }
- if h < 20 {
- h = 20
- }
- c.size = image.Point{X: w, Y: h}
- }
- func (c *Captcha) randFont() *truetype.Font {
- return c.fonts[rand.Intn(len(c.fonts))]
- }
- // 绘制背景
- func (c *Captcha) drawBkg(img *Image) {
- // 填充主背景色
- bgcolorindex := rand.Intn(len(c.bkgColors))
- bkg := image.NewUniform(c.bkgColors[bgcolorindex])
- img.FillBkg(bkg)
- }
- // 绘制噪点
- func (c *Captcha) drawNoises(img *Image) {
- // 待绘制图片的尺寸
- size := img.Bounds().Size()
- dlen := int(c.disturlvl)
- // 绘制干扰斑点
- for i := 0; i < dlen; i++ {
- x := rand.Intn(size.X)
- y := rand.Intn(size.Y)
- r := rand.Intn(size.Y/20) + 1
- colorindex := rand.Intn(len(c.frontColors))
- img.DrawCircle(x, y, r, i%4 != 0, c.frontColors[colorindex])
- }
- // 绘制干扰线
- for i := 0; i < dlen; i++ {
- x := rand.Intn(size.X)
- y := rand.Intn(size.Y)
- o := int(math.Pow(-1, float64(i)))
- w := rand.Intn(size.Y) * o
- h := rand.Intn(size.Y/10) * o
- colorindex := rand.Intn(len(c.frontColors))
- img.DrawLine(x, y, x+w, y+h, c.frontColors[colorindex])
- colorindex++
- }
- }
- // 绘制文字
- func (c *Captcha) drawString(img *Image, str string) {
- if c.fonts == nil {
- panic("没有设置任何字体")
- }
- tmp := NewImage(c.size.X, c.size.Y)
- // 文字大小为图片高度的 0.6
- fsize := int(float64(c.size.Y) * 0.6)
- // 用于生成随机角度
- // 文字之间的距离
- // 左右各留文字的1/4大小为内部边距
- padding := fsize / 4
- gap := (c.size.X - padding*2) / (len(str))
- // 逐个绘制文字到图片上
- for i, char := range str {
- // 创建单个文字图片
- // 以文字为尺寸创建正方形的图形
- str := NewImage(fsize, fsize)
- // str.FillBkg(image.NewUniform(color.Black))
- // 随机取一个前景色
- colorindex := rand.Intn(len(c.frontColors))
- // 随机取一个字体
- font := c.randFont()
- str.DrawString(font, c.frontColors[colorindex], string(char), float64(fsize))
- // 转换角度后的文字图形
- rs := str.Rotate(float64(rand.Intn(40) - 20))
- // 计算文字位置
- s := rs.Bounds().Size()
- left := i*gap + padding
- top := (c.size.Y - s.Y) / 2
- // 绘制到图片上
- draw.Draw(tmp, image.Rect(left, top, left+s.X, top+s.Y), rs, image.ZP, draw.Over)
- }
- if c.size.Y >= 48 {
- // 高度大于48添加波纹 小于48波纹影响用户识别
- tmp.distortTo(float64(fsize)/10, 200.0)
- }
- draw.Draw(img, tmp.Bounds(), tmp, image.ZP, draw.Over)
- }
- // Create 生成一个验证码图片
- func (c *Captcha) Create(num int, t StrType) (*Image, string) {
- if num <= 0 {
- num = 4
- }
- dst := NewImage(c.size.X, c.size.Y)
- // tmp := NewImage(c.size.X, c.size.Y)
- c.drawBkg(dst)
- c.drawNoises(dst)
- str := string(c.randStr(num, int(t)))
- c.drawString(dst, str)
- // c.drawString(tmp, str)
- return dst, str
- }
- func (c *Captcha) CreateCustom(str string) *Image {
- if len(str) == 0 {
- str = "unkown"
- }
- dst := NewImage(c.size.X, c.size.Y)
- c.drawBkg(dst)
- c.drawNoises(dst)
- c.drawString(dst, str)
- return dst
- }
- var fontKinds = [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}
- var letters = []byte("34578acdefghjkmnpqstwxyABCDEFGHJKMNPQRSVWXY")
- // 生成随机字符串
- // size 个数 kind 模式
- func (c *Captcha) randStr(size int, kind int) []byte {
- ikind, result := kind, make([]byte, size)
- isAll := kind > 2 || kind < 0
- for i := 0; i < size; i++ {
- if isAll {
- ikind = rand.Intn(3)
- }
- scope, base := fontKinds[ikind][0], fontKinds[ikind][1]
- result[i] = uint8(base + rand.Intn(scope))
- // 不易混淆字符模式:重新生成字符
- if kind == 4 {
- result[i] = letters[rand.Intn(len(letters))]
- }
- }
- return result
- }
|