├── code.gif ├── logo.png ├── code2.gif ├── examples ├── comic.ttf └── main.go ├── .gitignore ├── README.md ├── bilinear.go ├── draw.go ├── gif_captcha.go └── logo.svg /code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxbit2011/gifCaptcha/HEAD/code.gif -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxbit2011/gifCaptcha/HEAD/logo.png -------------------------------------------------------------------------------- /code2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxbit2011/gifCaptcha/HEAD/code2.gif -------------------------------------------------------------------------------- /examples/comic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxbit2011/gifCaptcha/HEAD/examples/comic.ttf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | *.log 23 | 24 | .idea/ 25 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/zxbit2011/gifCaptcha" 5 | "image/color" 6 | "image/gif" 7 | "net/http" 8 | ) 9 | 10 | var captcha = gifCaptcha.New() 11 | 12 | func main() { 13 | //设置颜色 14 | captcha.SetFrontColor(color.Black, color.RGBA{255, 0, 0, 255}, color.RGBA{0, 0, 255, 255}, color.RGBA{0, 153, 0, 255}) 15 | http.HandleFunc("/img", func(w http.ResponseWriter, r *http.Request) { 16 | gifData, code := captcha.RangCaptchaType(gifCaptcha.UPPER) 17 | // gifData, code := captcha.RangCaptcha() 18 | println(code) 19 | gif.EncodeAll(w, gifData) 20 | }) 21 | http.ListenAndServe(":8080", nil) 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](logo.svg) 2 | # gifCaptcha 3 | > gif 图形验证码 4 | # 丰富自定义设置 5 | * 图片大小 6 | * 多颜色 7 | * 文字模式 8 | * 文字数量 9 | * 干扰强度 10 | # 快速使用 11 | ```` 12 | > go get github.com/zxbit2011/gifCaptcha 13 | ```` 14 | # 示例代码 15 | ```` 16 | package main 17 | 18 | import ( 19 | "github.com/zxbit2011/gifCaptcha" 20 | "image/color" 21 | "image/gif" 22 | "net/http" 23 | ) 24 | 25 | var captcha = gifCaptcha.New() 26 | 27 | func main() { 28 | //设置颜色 29 | captcha.SetFrontColor(color.Black, color.RGBA{255, 0, 0, 255}, color.RGBA{0, 0, 255, 255}, color.RGBA{0, 153, 0, 255}) 30 | http.HandleFunc("/img", func(w http.ResponseWriter, r *http.Request) { 31 | gifData, code := captcha.RangCaptcha() 32 | println(code) 33 | gif.EncodeAll(w, gifData) 34 | }) 35 | http.ListenAndServe(":8080", nil) 36 | } 37 | ```` 38 | * 运行示例 39 | > cd examples && go run main.go 40 | * 浏览器预览 41 | http://127.0.0.1:8080/img 42 | # 注意 43 | `需提供字体文件examples/comic.ttf 默认路径放到根目录` 44 | # 示例效果 45 | ## 黑白 46 | ![code](code.gif) 47 | ## 彩色 48 | ![code2](code2.gif) 49 | 50 | # 感谢 51 | https://github.com/afocus/captcha 52 | -------------------------------------------------------------------------------- /bilinear.go: -------------------------------------------------------------------------------- 1 | package gifCaptcha 2 | 3 | // Bilinear Interpolation 双线性插值 4 | // 引用自 code.google.com/p/graphics-go/interp 5 | // 主要处理旋转验证码后消除锯齿 6 | 7 | import ( 8 | "image" 9 | "image/color" 10 | "math" 11 | ) 12 | 13 | var bili = Bilinear{} 14 | 15 | type Bilinear struct{} 16 | 17 | func (Bilinear) RGBA(src *image.RGBA, x, y float64) color.RGBA { 18 | p := findLinearSrc(src.Bounds(), x, y) 19 | 20 | // Array offsets for the surrounding pixels. 21 | off00 := offRGBA(src, p.low.X, p.low.Y) 22 | off01 := offRGBA(src, p.high.X, p.low.Y) 23 | off10 := offRGBA(src, p.low.X, p.high.Y) 24 | off11 := offRGBA(src, p.high.X, p.high.Y) 25 | 26 | var fr, fg, fb, fa float64 27 | 28 | fr += float64(src.Pix[off00+0]) * p.frac00 29 | fg += float64(src.Pix[off00+1]) * p.frac00 30 | fb += float64(src.Pix[off00+2]) * p.frac00 31 | fa += float64(src.Pix[off00+3]) * p.frac00 32 | 33 | fr += float64(src.Pix[off01+0]) * p.frac01 34 | fg += float64(src.Pix[off01+1]) * p.frac01 35 | fb += float64(src.Pix[off01+2]) * p.frac01 36 | fa += float64(src.Pix[off01+3]) * p.frac01 37 | 38 | fr += float64(src.Pix[off10+0]) * p.frac10 39 | fg += float64(src.Pix[off10+1]) * p.frac10 40 | fb += float64(src.Pix[off10+2]) * p.frac10 41 | fa += float64(src.Pix[off10+3]) * p.frac10 42 | 43 | fr += float64(src.Pix[off11+0]) * p.frac11 44 | fg += float64(src.Pix[off11+1]) * p.frac11 45 | fb += float64(src.Pix[off11+2]) * p.frac11 46 | fa += float64(src.Pix[off11+3]) * p.frac11 47 | 48 | var c color.RGBA 49 | c.R = uint8(fr + 0.5) 50 | c.G = uint8(fg + 0.5) 51 | c.B = uint8(fb + 0.5) 52 | c.A = uint8(fa + 0.5) 53 | return c 54 | } 55 | 56 | type BilinearSrc struct { 57 | // Top-left and bottom-right interpolation sources 58 | low, high image.Point 59 | // Fraction of each pixel to take. The 0 suffix indicates 60 | // top/left, and the 1 suffix indicates bottom/right. 61 | frac00, frac01, frac10, frac11 float64 62 | } 63 | 64 | func findLinearSrc(b image.Rectangle, sx, sy float64) BilinearSrc { 65 | maxX := float64(b.Max.X) 66 | maxY := float64(b.Max.Y) 67 | minX := float64(b.Min.X) 68 | minY := float64(b.Min.Y) 69 | lowX := math.Floor(sx - 0.5) 70 | lowY := math.Floor(sy - 0.5) 71 | if lowX < minX { 72 | lowX = minX 73 | } 74 | if lowY < minY { 75 | lowY = minY 76 | } 77 | 78 | highX := math.Ceil(sx - 0.5) 79 | highY := math.Ceil(sy - 0.5) 80 | if highX >= maxX { 81 | highX = maxX - 1 82 | } 83 | if highY >= maxY { 84 | highY = maxY - 1 85 | } 86 | 87 | // In the variables below, the 0 suffix indicates top/left, and the 88 | // 1 suffix indicates bottom/right. 89 | 90 | // Center of each surrounding pixel. 91 | x00 := lowX + 0.5 92 | y00 := lowY + 0.5 93 | x01 := highX + 0.5 94 | y01 := lowY + 0.5 95 | x10 := lowX + 0.5 96 | y10 := highY + 0.5 97 | x11 := highX + 0.5 98 | y11 := highY + 0.5 99 | 100 | p := BilinearSrc{ 101 | low: image.Pt(int(lowX), int(lowY)), 102 | high: image.Pt(int(highX), int(highY)), 103 | } 104 | 105 | // Literally, edge cases. If we are close enough to the edge of 106 | // the image, curtail the interpolation sources. 107 | if lowX == highX && lowY == highY { 108 | p.frac00 = 1.0 109 | } else if sy-minY <= 0.5 && sx-minX <= 0.5 { 110 | p.frac00 = 1.0 111 | } else if maxY-sy <= 0.5 && maxX-sx <= 0.5 { 112 | p.frac11 = 1.0 113 | } else if sy-minY <= 0.5 || lowY == highY { 114 | p.frac00 = x01 - sx 115 | p.frac01 = sx - x00 116 | } else if sx-minX <= 0.5 || lowX == highX { 117 | p.frac00 = y10 - sy 118 | p.frac10 = sy - y00 119 | } else if maxY-sy <= 0.5 { 120 | p.frac10 = x11 - sx 121 | p.frac11 = sx - x10 122 | } else if maxX-sx <= 0.5 { 123 | p.frac01 = y11 - sy 124 | p.frac11 = sy - y01 125 | } else { 126 | p.frac00 = (x01 - sx) * (y10 - sy) 127 | p.frac01 = (sx - x00) * (y11 - sy) 128 | p.frac10 = (x11 - sx) * (sy - y00) 129 | p.frac11 = (sx - x10) * (sy - y01) 130 | } 131 | 132 | return p 133 | } 134 | 135 | func offRGBA(src *image.RGBA, x, y int) int { 136 | return (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4 137 | } 138 | -------------------------------------------------------------------------------- /draw.go: -------------------------------------------------------------------------------- 1 | package gifCaptcha 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | 9 | "github.com/golang/freetype" 10 | "github.com/golang/freetype/truetype" 11 | ) 12 | 13 | // Image 图片 14 | type Image struct { 15 | *image.RGBA 16 | } 17 | 18 | // NewImage 创建一个新的图片 19 | func NewImage(w, h int) *Image { 20 | img := &Image{image.NewRGBA(image.Rect(0, 0, w, h))} 21 | return img 22 | } 23 | 24 | func sign(x int) int { 25 | if x > 0 { 26 | return 1 27 | } 28 | return -1 29 | } 30 | 31 | // DrawLine 画直线 32 | // Bresenham算法(https://zh.wikipedia.org/zh-cn/布雷森漢姆直線演算法) 33 | // x1,y1 起点 x2,y2终点 34 | func (img *Image) DrawLine(x1, y1, x2, y2 int, c color.Color) { 35 | dx, dy, flag := int(math.Abs(float64(x2-x1))), 36 | int(math.Abs(float64(y2-y1))), 37 | false 38 | if dy > dx { 39 | flag = true 40 | x1, y1 = y1, x1 41 | x2, y2 = y2, x2 42 | dx, dy = dy, dx 43 | } 44 | ix, iy := sign(x2-x1), sign(y2-y1) 45 | n2dy := dy * 2 46 | n2dydx := (dy - dx) * 2 47 | d := n2dy - dx 48 | for x1 != x2 { 49 | if d < 0 { 50 | d += n2dy 51 | } else { 52 | y1 += iy 53 | d += n2dydx 54 | } 55 | if flag { 56 | img.Set(y1, x1, c) 57 | } else { 58 | img.Set(x1, y1, c) 59 | } 60 | x1 += ix 61 | } 62 | } 63 | 64 | func (img *Image) drawCircle8(xc, yc, x, y int, c color.Color) { 65 | img.Set(xc+x, yc+y, c) 66 | img.Set(xc-x, yc+y, c) 67 | img.Set(xc+x, yc-y, c) 68 | img.Set(xc-x, yc-y, c) 69 | img.Set(xc+y, yc+x, c) 70 | img.Set(xc-y, yc+x, c) 71 | img.Set(xc+y, yc-x, c) 72 | img.Set(xc-y, yc-x, c) 73 | } 74 | 75 | // DrawCircle 画圆 76 | // xc,yc 圆心坐标 r 半径 fill是否填充颜色 77 | func (img *Image) DrawCircle(xc, yc, r int, fill bool, c color.Color) { 78 | size := img.Bounds().Size() 79 | // 如果圆在图片可见区域外,直接退出 80 | if xc+r < 0 || xc-r >= size.X || yc+r < 0 || yc-r >= size.Y { 81 | return 82 | } 83 | x, y, d := 0, r, 3-2*r 84 | for x <= y { 85 | if fill { 86 | for yi := x; yi <= y; yi++ { 87 | img.drawCircle8(xc, yc, x, yi, c) 88 | } 89 | } else { 90 | img.drawCircle8(xc, yc, x, y, c) 91 | } 92 | if d < 0 { 93 | d = d + 4*x + 6 94 | } else { 95 | d = d + 4*(x-y) + 10 96 | y-- 97 | } 98 | x++ 99 | } 100 | } 101 | 102 | // DrawString 写字 103 | func (img *Image) DrawString(font *truetype.Font, c color.Color, str string, fontsize float64) { 104 | ctx := freetype.NewContext() 105 | // default 72dpi 106 | ctx.SetDst(img) 107 | ctx.SetClip(img.Bounds()) 108 | ctx.SetSrc(image.NewUniform(c)) 109 | ctx.SetFontSize(fontsize) 110 | if font != nil { 111 | ctx.SetFont(font) 112 | } 113 | // 写入文字的位置 114 | pt := freetype.Pt(0, int(-fontsize/6)+ctx.PointToFixed(fontsize).Ceil()) 115 | ctx.DrawString(str, pt) 116 | } 117 | 118 | // Rotate 旋转 119 | func (img *Image) Rotate(angle float64) image.Image { 120 | return new(rotate).Rotate(angle, img.RGBA).transformRGBA() 121 | } 122 | 123 | // 填充背景 124 | func (img *Image) FillBkg(c image.Image) { 125 | draw.Draw(img, img.Bounds(), c, image.ZP, draw.Over) 126 | } 127 | 128 | // 水波纹, amplude=振幅, period=周期 129 | // copy from https://github.com/dchest/captcha/blob/master/image.go 130 | func (img *Image) distortTo(amplude float64, period float64) { 131 | w := img.Bounds().Max.X 132 | h := img.Bounds().Max.Y 133 | 134 | oldm := img.RGBA 135 | 136 | dx := 1.4 * math.Pi / period 137 | for x := 0; x < w; x++ { 138 | for y := 0; y < h; y++ { 139 | xo := amplude * math.Sin(float64(y)*dx) 140 | yo := amplude * math.Cos(float64(x)*dx) 141 | rgba := oldm.RGBAAt(x+int(xo), y+int(yo)) 142 | if rgba.A > 0 { 143 | oldm.SetRGBA(x, y, rgba) 144 | } 145 | } 146 | } 147 | } 148 | 149 | func inBounds(b image.Rectangle, x, y float64) bool { 150 | if x < float64(b.Min.X) || x >= float64(b.Max.X) { 151 | return false 152 | } 153 | if y < float64(b.Min.Y) || y >= float64(b.Max.Y) { 154 | return false 155 | } 156 | return true 157 | } 158 | 159 | type rotate struct { 160 | dx float64 161 | dy float64 162 | sin float64 163 | cos float64 164 | neww float64 165 | newh float64 166 | src *image.RGBA 167 | } 168 | 169 | func radian(angle float64) float64 { 170 | return angle * math.Pi / 180.0 171 | } 172 | 173 | func (r *rotate) Rotate(angle float64, src *image.RGBA) *rotate { 174 | r.src = src 175 | srsize := src.Bounds().Size() 176 | width, height := srsize.X, srsize.Y 177 | 178 | // 源图四个角的坐标(以图像中心为坐标系原点) 179 | // 左下角,右下角,左上角,右上角 180 | srcwp, srchp := float64(width)*0.5, float64(height)*0.5 181 | srcx1, srcy1 := -srcwp, srchp 182 | srcx2, srcy2 := srcwp, srchp 183 | srcx3, srcy3 := -srcwp, -srchp 184 | srcx4, srcy4 := srcwp, -srchp 185 | 186 | r.sin, r.cos = math.Sincos(radian(angle)) 187 | // 旋转后的四角坐标 188 | desx1, desy1 := r.cos*srcx1+r.sin*srcy1, -r.sin*srcx1+r.cos*srcy1 189 | desx2, desy2 := r.cos*srcx2+r.sin*srcy2, -r.sin*srcx2+r.cos*srcy2 190 | desx3, desy3 := r.cos*srcx3+r.sin*srcy3, -r.sin*srcx3+r.cos*srcy3 191 | desx4, desy4 := r.cos*srcx4+r.sin*srcy4, -r.sin*srcx4+r.cos*srcy4 192 | 193 | // 新的高度很宽度 194 | r.neww = math.Max(math.Abs(desx4-desx1), math.Abs(desx3-desx2)) + 0.5 195 | r.newh = math.Max(math.Abs(desy4-desy1), math.Abs(desy3-desy2)) + 0.5 196 | r.dx = -0.5*r.neww*r.cos - 0.5*r.newh*r.sin + srcwp 197 | r.dy = 0.5*r.neww*r.sin - 0.5*r.newh*r.cos + srchp 198 | return r 199 | } 200 | 201 | func (r *rotate) pt(x, y int) (float64, float64) { 202 | return float64(-y)*r.sin + float64(x)*r.cos + r.dy, 203 | float64(y)*r.cos + float64(x)*r.sin + r.dx 204 | } 205 | 206 | func (r *rotate) transformRGBA() image.Image { 207 | 208 | srcb := r.src.Bounds() 209 | b := image.Rect(0, 0, int(r.neww), int(r.newh)) 210 | dst := image.NewRGBA(b) 211 | 212 | for y := b.Min.Y; y < b.Max.Y; y++ { 213 | for x := b.Min.X; x < b.Max.X; x++ { 214 | sx, sy := r.pt(x, y) 215 | if inBounds(srcb, sx, sy) { 216 | // 消除锯齿填色 217 | c := bili.RGBA(r.src, sx, sy) 218 | off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4 219 | dst.Pix[off+0] = c.R 220 | dst.Pix[off+1] = c.G 221 | dst.Pix[off+2] = c.B 222 | dst.Pix[off+3] = c.A 223 | } 224 | } 225 | } 226 | return dst 227 | } 228 | -------------------------------------------------------------------------------- /gif_captcha.go: -------------------------------------------------------------------------------- 1 | package gifCaptcha 2 | 3 | import ( 4 | "github.com/golang/freetype" 5 | "github.com/golang/freetype/truetype" 6 | "image" 7 | "image/color" 8 | "image/color/palette" 9 | "image/draw" 10 | "image/gif" 11 | "io/ioutil" 12 | "math" 13 | "math/rand" 14 | "time" 15 | ) 16 | 17 | // GifCaptcha gif 验证码 18 | type GifCaptcha struct { 19 | frontColors []color.Color //图片前景 20 | bkgColors []color.Color //图片背景 21 | disturbLevel DisturbLevel //干扰级别 22 | fonts []*truetype.Font //字体 23 | size image.Point //图片大小 24 | frame int //帧数 25 | delay int //连续的延迟时间,每帧一次,每秒钟的100分之一 26 | } 27 | 28 | // 验证码字符类型 29 | type StrType int 30 | 31 | const ( 32 | NUM StrType = iota // 数字 33 | LOWER // 小写字母 34 | UPPER // 大写字母 35 | ALL // 全部 36 | ) 37 | 38 | // DisturbLevel 干扰级别 39 | type DisturbLevel int 40 | 41 | const ( 42 | NORMAL DisturbLevel = 4 43 | MEDIUM DisturbLevel = 8 44 | HIGH DisturbLevel = 16 45 | ) 46 | 47 | func New() *GifCaptcha { 48 | c := &GifCaptcha{ 49 | disturbLevel: MEDIUM, 50 | size: image.Point{X: 128, Y: 48}, 51 | delay: 40, 52 | frame: 30, 53 | } 54 | c.frontColors = []color.Color{color.Black} 55 | c.bkgColors = []color.Color{color.White} 56 | c.AddFont("comic.ttf") 57 | return c 58 | } 59 | 60 | // AddFont 添加一个字体 61 | func (c *GifCaptcha) AddFont(path string) error { 62 | fontData, err := ioutil.ReadFile(path) 63 | if err != nil { 64 | return err 65 | } 66 | font, err := freetype.ParseFont(fontData) 67 | if err != nil { 68 | return err 69 | } 70 | if c.fonts == nil { 71 | c.fonts = []*truetype.Font{} 72 | } 73 | c.fonts = append(c.fonts, font) 74 | return nil 75 | } 76 | 77 | //AddFontFromBytes allows to load font from slice of bytes, for example, load the font packed by https://github.com/jteeuwen/go-bindata 78 | func (c *GifCaptcha) AddFontFromBytes(contents []byte) error { 79 | font, err := freetype.ParseFont(contents) 80 | if err != nil { 81 | return err 82 | } 83 | if c.fonts == nil { 84 | c.fonts = []*truetype.Font{} 85 | } 86 | c.fonts = append(c.fonts, font) 87 | return nil 88 | } 89 | 90 | // SetFont 设置字体 可以设置多个 91 | func (c *GifCaptcha) SetFont(paths ...string) error { 92 | for _, v := range paths { 93 | if err := c.AddFont(v); err != nil { 94 | return err 95 | } 96 | } 97 | return nil 98 | } 99 | 100 | func (c *GifCaptcha) SetDisturbance(d DisturbLevel) { 101 | if d > 0 { 102 | c.disturbLevel = d 103 | } 104 | } 105 | 106 | func (c *GifCaptcha) SetFrontColor(colors ...color.Color) { 107 | if len(colors) > 0 { 108 | c.frontColors = c.frontColors[:0] 109 | for _, v := range colors { 110 | c.frontColors = append(c.frontColors, v) 111 | } 112 | } 113 | } 114 | 115 | func (c *GifCaptcha) SetBkgColor(colors ...color.Color) { 116 | if len(colors) > 0 { 117 | c.bkgColors = c.bkgColors[:0] 118 | for _, v := range colors { 119 | c.bkgColors = append(c.bkgColors, v) 120 | } 121 | } 122 | } 123 | 124 | func (c *GifCaptcha) SetSize(w, h int) { 125 | if w < 48 { 126 | w = 48 127 | } 128 | if h < 20 { 129 | h = 20 130 | } 131 | c.size = image.Point{w, h} 132 | } 133 | 134 | func (c *GifCaptcha) randFont() *truetype.Font { 135 | if len(c.fonts) == 0 { 136 | return nil 137 | } 138 | return c.fonts[rand.Intn(len(c.fonts))] 139 | } 140 | 141 | // 绘制背景 142 | func (c *GifCaptcha) drawBkg(img *Image) { 143 | ra := rand.New(rand.NewSource(time.Now().UnixNano())) 144 | //填充主背景色 145 | bgColorIndex := ra.Intn(len(c.bkgColors)) 146 | bkg := image.NewUniform(c.bkgColors[bgColorIndex]) 147 | img.FillBkg(bkg) 148 | } 149 | 150 | // 绘制噪点 151 | func (c *GifCaptcha) drawNoises(img *Image) { 152 | ra := rand.New(rand.NewSource(time.Now().UnixNano())) 153 | 154 | // 待绘制图片的尺寸 155 | size := img.Bounds().Size() 156 | dlen := int(c.disturbLevel) 157 | // 绘制干扰斑点 158 | for i := 0; i < dlen; i++ { 159 | x := ra.Intn(size.X) 160 | y := ra.Intn(size.Y) 161 | r := ra.Intn(size.Y/20) + 1 162 | colorIndex := ra.Intn(len(c.frontColors)) 163 | img.DrawCircle(x, y, r, i%4 != 0, c.frontColors[colorIndex]) 164 | } 165 | 166 | // 绘制干扰线 167 | for i := 0; i < dlen; i++ { 168 | x := ra.Intn(size.X) 169 | y := ra.Intn(size.Y) 170 | o := int(math.Pow(-1, float64(i))) 171 | w := ra.Intn(size.Y) * o 172 | h := ra.Intn(size.Y/10) * o 173 | colorIndex := ra.Intn(len(c.frontColors)) 174 | img.DrawLine(x, y, x+w, y+h, c.frontColors[colorIndex]) 175 | } 176 | 177 | } 178 | 179 | // 绘制噪点 180 | func (c *GifCaptcha) drawNoisesArr(img *Image, dot, line [][]int, frontColor color.Color) { 181 | 182 | // 绘制干扰斑点 183 | for i := 0; i < len(dot); i++ { 184 | img.DrawCircle(dot[i][0], dot[i][1], dot[i][2], i%4 != 0, frontColor) 185 | } 186 | 187 | // 绘制干扰线 188 | for i := 0; i < len(line); i++ { 189 | img.DrawLine(line[i][0], line[i][1], line[i][2], line[i][3], frontColor) 190 | } 191 | 192 | } 193 | 194 | // 绘制噪点 195 | func (c *GifCaptcha) getNoises() (dot, line [][]int) { 196 | ra := rand.New(rand.NewSource(time.Now().UnixNano())) 197 | 198 | // 待绘制图片的尺寸 199 | size := c.size 200 | dlen := int(c.disturbLevel) 201 | // 绘制干扰斑点 202 | for i := 0; i < dlen; i++ { 203 | x := ra.Intn(size.X) 204 | y := ra.Intn(size.Y) 205 | r := ra.Intn(size.Y/20) + 1 206 | /*colorIndex := ra.Intn(len(c.frontColors)) 207 | img.DrawCircle(x, y, r, i%4 != 0, c.frontColors[colorIndex])*/ 208 | arr := []int{x, y, r} 209 | dot = append(dot, arr) 210 | } 211 | 212 | // 绘制干扰线 213 | for i := 0; i < dlen; i++ { 214 | x := ra.Intn(size.X) 215 | y := ra.Intn(size.Y) 216 | o := int(math.Pow(-1, float64(i))) 217 | w := ra.Intn(size.Y) * o 218 | h := ra.Intn(size.Y/10) * o 219 | /*colorIndex := ra.Intn(len(c.frontColors)) 220 | img.DrawLine(x, y, x+w, y+h, c.frontColors[colorIndex]) 221 | */ 222 | arr := []int{x, y, x + w, y + h} 223 | line = append(line, arr) 224 | } 225 | return 226 | } 227 | 228 | // 绘制文字 229 | func (c *GifCaptcha) drawString(str string, dot, line [][]int, frontColor color.Color) (tmp *Image) { 230 | tmp = NewImage(c.size.X, c.size.Y) 231 | 232 | // 文字大小为图片高度的 0.6 233 | fsize := int(float64(c.size.Y) * 0.6) 234 | // 用于生成随机角度 235 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 236 | 237 | // 文字之间的距离 238 | // 左右各留文字的1/4大小为内部边距 239 | padding := fsize / 4 240 | gap := (c.size.X - padding*2) / (len(str)) 241 | 242 | // 逐个绘制文字到图片上 243 | for i, char := range str { 244 | // 创建单个文字图片 245 | // 以文字为尺寸创建正方形的图形 246 | img := NewImage(fsize, fsize) 247 | // str.FillBkg(image.NewUniform(color.Black)) 248 | // 随机取一个前景色 249 | colorIndex := r.Intn(len(c.frontColors)) 250 | //随机取一个字体 251 | font := c.randFont() 252 | img.DrawString(font, c.frontColors[colorIndex], string(char), float64(fsize)) 253 | 254 | // 转换角度后的文字图形 255 | rs := img.Rotate(float64(r.Intn(40) - 20)) 256 | // 计算文字位置 257 | s := rs.Bounds().Size() 258 | left := i*gap + padding 259 | top := (c.size.Y - s.Y) / 2 260 | // 绘制到图片上 261 | draw.Draw(tmp, image.Rect(left, top, left+s.X, top+s.Y), rs, image.ZP, draw.Over) 262 | } 263 | if c.size.Y >= 48 { 264 | // 高度大于48添加波纹 小于48波纹影响用户识别 265 | tmp.distortTo(float64(fsize)/10, 200.0) 266 | } 267 | c.drawNoisesArr(tmp, dot, line, frontColor) 268 | return 269 | } 270 | 271 | // Create 生成一个验证码图片 272 | func (c *GifCaptcha) RangCaptcha() (gifData *gif.GIF, str string) { 273 | str = string(c.randStr(4, int(ALL))) 274 | gifData = c.createGif(str) 275 | return 276 | } 277 | 278 | // Create 生成一个验证码图片 279 | func (c *GifCaptcha) RangCaptchaType(strType StrType) (gifData *gif.GIF, str string) { 280 | str = string(c.randStr(4, int(strType))) 281 | gifData = c.createGif(str) 282 | return 283 | } 284 | 285 | // Create 生成一个验证码图片 286 | func (c *GifCaptcha) Create(num int, t StrType) (gifData *gif.GIF, str string) { 287 | if num <= 0 { 288 | num = 4 289 | } 290 | str = string(c.randStr(num, int(t))) 291 | gifData = c.createGif(str) 292 | return 293 | } 294 | 295 | func (c *GifCaptcha) CreateCustom(str string) *gif.GIF { 296 | if len(str) == 0 { 297 | str = "unkown" 298 | } 299 | return c.createGif(str) 300 | } 301 | 302 | func (c *GifCaptcha) createGif(str string) *gif.GIF { 303 | dot, line := c.getNoises() 304 | anim := gif.GIF{ 305 | LoopCount: 0, 306 | } 307 | // 用于生成随机角度 308 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 309 | frontColor := c.frontColors[r.Intn(len(c.frontColors))] 310 | for i := 0; i < c.frame; i++ { 311 | tmp := c.drawString(str, dot, line, frontColor) 312 | img := NewImage(c.size.X, c.size.Y) 313 | bkg := image.NewUniform(color.White) 314 | img.FillBkg(bkg) 315 | 316 | draw.Draw(img, tmp.Bounds(), tmp, image.ZP, draw.Over) 317 | 318 | p := image.NewPaletted(image.Rect(0, 0, c.size.X, c.size.Y), palette.Plan9) 319 | draw.Draw(p, p.Bounds(), img, image.ZP, draw.Src) //添加图片 320 | 321 | anim.Image = append(anim.Image, p) 322 | anim.Delay = append(anim.Delay, c.delay) 323 | } 324 | return &anim 325 | } 326 | 327 | var fontKinds = [][]int{{10, 48}, {26, 97}, {26, 65}} 328 | 329 | // 生成随机字符串 330 | // size 个数 kind 模式 331 | func (c *GifCaptcha) randStr(size int, kind int) []byte { 332 | ikind, result := kind, make([]byte, size) 333 | isAll := kind > 2 || kind < 0 334 | rand.Seed(time.Now().UnixNano()) 335 | for i := 0; i < size; i++ { 336 | if isAll { 337 | ikind = rand.Intn(3) 338 | } 339 | scope, base := fontKinds[ikind][0], fontKinds[ikind][1] 340 | result[i] = uint8(base + rand.Intn(scope)) 341 | } 342 | return result 343 | } 344 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------