This commit is contained in:
arraykeys
2019-08-08 17:13:34 +08:00
parent e8e5966a8c
commit f0bf2d5fec
2885 changed files with 1195993 additions and 12 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 Andrew Bosonchenko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+137
View File
@@ -0,0 +1,137 @@
package captcha
// Bilinear Interpolation 双线性插值
// 引用自 code.google.com/p/graphics-go/interp
// 主要处理旋转验证码后消除锯齿
import (
"image"
"image/color"
"math"
)
var bili = Bilinear{}
type Bilinear struct{}
func (Bilinear) RGBA(src *image.RGBA, x, y float64) color.RGBA {
p := findLinearSrc(src.Bounds(), x, y)
// Array offsets for the surrounding pixels.
off00 := offRGBA(src, p.low.X, p.low.Y)
off01 := offRGBA(src, p.high.X, p.low.Y)
off10 := offRGBA(src, p.low.X, p.high.Y)
off11 := offRGBA(src, p.high.X, p.high.Y)
var fr, fg, fb, fa float64
fr += float64(src.Pix[off00+0]) * p.frac00
fg += float64(src.Pix[off00+1]) * p.frac00
fb += float64(src.Pix[off00+2]) * p.frac00
fa += float64(src.Pix[off00+3]) * p.frac00
fr += float64(src.Pix[off01+0]) * p.frac01
fg += float64(src.Pix[off01+1]) * p.frac01
fb += float64(src.Pix[off01+2]) * p.frac01
fa += float64(src.Pix[off01+3]) * p.frac01
fr += float64(src.Pix[off10+0]) * p.frac10
fg += float64(src.Pix[off10+1]) * p.frac10
fb += float64(src.Pix[off10+2]) * p.frac10
fa += float64(src.Pix[off10+3]) * p.frac10
fr += float64(src.Pix[off11+0]) * p.frac11
fg += float64(src.Pix[off11+1]) * p.frac11
fb += float64(src.Pix[off11+2]) * p.frac11
fa += float64(src.Pix[off11+3]) * p.frac11
var c color.RGBA
c.R = uint8(fr + 0.5)
c.G = uint8(fg + 0.5)
c.B = uint8(fb + 0.5)
c.A = uint8(fa + 0.5)
return c
}
type BilinearSrc struct {
// Top-left and bottom-right interpolation sources
low, high image.Point
// Fraction of each pixel to take. The 0 suffix indicates
// top/left, and the 1 suffix indicates bottom/right.
frac00, frac01, frac10, frac11 float64
}
func findLinearSrc(b image.Rectangle, sx, sy float64) BilinearSrc {
maxX := float64(b.Max.X)
maxY := float64(b.Max.Y)
minX := float64(b.Min.X)
minY := float64(b.Min.Y)
lowX := math.Floor(sx - 0.5)
lowY := math.Floor(sy - 0.5)
if lowX < minX {
lowX = minX
}
if lowY < minY {
lowY = minY
}
highX := math.Ceil(sx - 0.5)
highY := math.Ceil(sy - 0.5)
if highX >= maxX {
highX = maxX - 1
}
if highY >= maxY {
highY = maxY - 1
}
// In the variables below, the 0 suffix indicates top/left, and the
// 1 suffix indicates bottom/right.
// Center of each surrounding pixel.
x00 := lowX + 0.5
y00 := lowY + 0.5
x01 := highX + 0.5
y01 := lowY + 0.5
x10 := lowX + 0.5
y10 := highY + 0.5
x11 := highX + 0.5
y11 := highY + 0.5
p := BilinearSrc{
low: image.Pt(int(lowX), int(lowY)),
high: image.Pt(int(highX), int(highY)),
}
// Literally, edge cases. If we are close enough to the edge of
// the image, curtail the interpolation sources.
if lowX == highX && lowY == highY {
p.frac00 = 1.0
} else if sy-minY <= 0.5 && sx-minX <= 0.5 {
p.frac00 = 1.0
} else if maxY-sy <= 0.5 && maxX-sx <= 0.5 {
p.frac11 = 1.0
} else if sy-minY <= 0.5 || lowY == highY {
p.frac00 = x01 - sx
p.frac01 = sx - x00
} else if sx-minX <= 0.5 || lowX == highX {
p.frac00 = y10 - sy
p.frac10 = sy - y00
} else if maxY-sy <= 0.5 {
p.frac10 = x11 - sx
p.frac11 = sx - x10
} else if maxX-sx <= 0.5 {
p.frac01 = y11 - sy
p.frac11 = sy - y01
} else {
p.frac00 = (x01 - sx) * (y10 - sy)
p.frac01 = (sx - x00) * (y11 - sy)
p.frac10 = (x11 - sx) * (sy - y00)
p.frac11 = (sx - x10) * (sy - y01)
}
return p
}
func offRGBA(src *image.RGBA, x, y int) int {
return (y-src.Rect.Min.Y)*src.Stride + (x-src.Rect.Min.X)*4
}
+260
View File
@@ -0,0 +1,260 @@
package captcha
import (
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"image"
"image/color"
"image/draw"
"io/ioutil"
"math"
"math/rand"
"time"
)
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 // 全部
)
type DisturLevel int
const (
NORMAL DisturLevel = 4
MEDIUM DisturLevel = 8
HIGH DisturLevel = 16
)
func New() *Captcha {
c := &Captcha{
disturlvl: NORMAL,
size: image.Point{82, 32},
}
c.frontColors = []color.Color{color.Black}
c.bkgColors = []color.Color{color.White}
return c
}
// AddFont 添加一个字体
func (c *Captcha) AddFont(path string) error {
fontdata, erro := ioutil.ReadFile(path)
if erro != nil {
return erro
}
font, erro := freetype.ParseFont(fontdata)
if erro != nil {
return erro
}
if c.fonts == nil {
c.fonts = []*truetype.Font{}
}
c.fonts = append(c.fonts, font)
return nil
}
//AddFontFromBytes allows to load font from slice of bytes, for example, load the font packed by https://github.com/jteeuwen/go-bindata
func (c *Captcha) AddFontFromBytes(contents []byte) error {
font, err := freetype.ParseFont(contents)
if err != nil {
return err
}
if c.fonts == nil {
c.fonts = []*truetype.Font{}
}
c.fonts = append(c.fonts, font)
return nil
}
// SetFont 设置字体 可以设置多个
func (c *Captcha) SetFont(paths ...string) error {
for _, v := range paths {
if erro := c.AddFont(v); erro != nil {
return erro
}
}
return nil
}
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{w, h}
}
func (c *Captcha) randFont() *truetype.Font {
return c.fonts[rand.Intn(len(c.fonts))]
}
// 绘制背景
func (c *Captcha) drawBkg(img *Image) {
ra := rand.New(rand.NewSource(time.Now().UnixNano()))
//填充主背景色
bgcolorindex := ra.Intn(len(c.bkgColors))
bkg := image.NewUniform(c.bkgColors[bgcolorindex])
img.FillBkg(bkg)
}
// 绘制噪点
func (c *Captcha) drawNoises(img *Image) {
ra := rand.New(rand.NewSource(time.Now().UnixNano()))
// 待绘制图片的尺寸
size := img.Bounds().Size()
dlen := int(c.disturlvl)
// 绘制干扰斑点
for i := 0; i < dlen; i++ {
x := ra.Intn(size.X)
y := ra.Intn(size.Y)
r := ra.Intn(size.Y/20) + 1
colorindex := ra.Intn(len(c.frontColors))
img.DrawCircle(x, y, r, i%4 != 0, c.frontColors[colorindex])
}
// 绘制干扰线
for i := 0; i < dlen; i++ {
x := ra.Intn(size.X)
y := ra.Intn(size.Y)
o := int(math.Pow(-1, float64(i)))
w := ra.Intn(size.Y) * o
h := ra.Intn(size.Y/10) * o
colorindex := ra.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)
// 用于生成随机角度
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// 文字之间的距离
// 左右各留文字的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 := r.Intn(len(c.frontColors))
//随机取一个字体
font := c.randFont()
str.DrawString(font, c.frontColors[colorindex], string(char), float64(fsize))
// 转换角度后的文字图形
rs := str.Rotate(float64(r.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}}
// 生成随机字符串
// size 个数 kind 模式
func (c *Captcha) randStr(size int, kind int) []byte {
ikind, result := kind, make([]byte, size)
isAll := kind > 2 || kind < 0
rand.Seed(time.Now().UnixNano())
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))
}
return result
}
+225
View File
@@ -0,0 +1,225 @@
package captcha
import (
"image"
"image/color"
"image/draw"
"math"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
)
// Image 图片
type Image struct {
*image.RGBA
}
// NewImage 创建一个新的图片
func NewImage(w, h int) *Image {
img := &Image{image.NewRGBA(image.Rect(0, 0, w, h))}
return img
}
func sign(x int) int {
if x > 0 {
return 1
}
return -1
}
// DrawLine 画直线
// Bresenham算法(https://zh.wikipedia.org/zh-cn/布雷森漢姆直線演算法)
// x1,y1 起点 x2,y2终点
func (img *Image) DrawLine(x1, y1, x2, y2 int, c color.Color) {
dx, dy, flag := int(math.Abs(float64(x2-x1))),
int(math.Abs(float64(y2-y1))),
false
if dy > dx {
flag = true
x1, y1 = y1, x1
x2, y2 = y2, x2
dx, dy = dy, dx
}
ix, iy := sign(x2-x1), sign(y2-y1)
n2dy := dy * 2
n2dydx := (dy - dx) * 2
d := n2dy - dx
for x1 != x2 {
if d < 0 {
d += n2dy
} else {
y1 += iy
d += n2dydx
}
if flag {
img.Set(y1, x1, c)
} else {
img.Set(x1, y1, c)
}
x1 += ix
}
}
func (img *Image) drawCircle8(xc, yc, x, y int, c color.Color) {
img.Set(xc+x, yc+y, c)
img.Set(xc-x, yc+y, c)
img.Set(xc+x, yc-y, c)
img.Set(xc-x, yc-y, c)
img.Set(xc+y, yc+x, c)
img.Set(xc-y, yc+x, c)
img.Set(xc+y, yc-x, c)
img.Set(xc-y, yc-x, c)
}
// DrawCircle 画圆
// xc,yc 圆心坐标 r 半径 fill是否填充颜色
func (img *Image) DrawCircle(xc, yc, r int, fill bool, c color.Color) {
size := img.Bounds().Size()
// 如果圆在图片可见区域外,直接退出
if xc+r < 0 || xc-r >= size.X || yc+r < 0 || yc-r >= size.Y {
return
}
x, y, d := 0, r, 3-2*r
for x <= y {
if fill {
for yi := x; yi <= y; yi++ {
img.drawCircle8(xc, yc, x, yi, c)
}
} else {
img.drawCircle8(xc, yc, x, y, c)
}
if d < 0 {
d = d + 4*x + 6
} else {
d = d + 4*(x-y) + 10
y--
}
x++
}
}
// DrawString 写字
func (img *Image) DrawString(font *truetype.Font, c color.Color, str string, fontsize float64) {
ctx := freetype.NewContext()
// default 72dpi
ctx.SetDst(img)
ctx.SetClip(img.Bounds())
ctx.SetSrc(image.NewUniform(c))
ctx.SetFontSize(fontsize)
ctx.SetFont(font)
// 写入文字的位置
pt := freetype.Pt(0, int(-fontsize/6)+ctx.PointToFixed(fontsize).Ceil())
ctx.DrawString(str, pt)
}
// Rotate 旋转
func (img *Image) Rotate(angle float64) image.Image {
return new(rotate).Rotate(angle, img.RGBA).transformRGBA()
}
// 填充背景
func (img *Image) FillBkg(c image.Image) {
draw.Draw(img, img.Bounds(), c, image.ZP, draw.Over)
}
// 水波纹, amplude=振幅, period=周期
// copy from https://github.com/dchest/captcha/blob/master/image.go
func (img *Image) distortTo(amplude float64, period float64) {
w := img.Bounds().Max.X
h := img.Bounds().Max.Y
oldm := img.RGBA
dx := 1.4 * math.Pi / period
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
xo := amplude * math.Sin(float64(y)*dx)
yo := amplude * math.Cos(float64(x)*dx)
rgba := oldm.RGBAAt(x+int(xo), y+int(yo))
if rgba.A > 0 {
oldm.SetRGBA(x, y, rgba)
}
}
}
}
func inBounds(b image.Rectangle, x, y float64) bool {
if x < float64(b.Min.X) || x >= float64(b.Max.X) {
return false
}
if y < float64(b.Min.Y) || y >= float64(b.Max.Y) {
return false
}
return true
}
type rotate struct {
dx float64
dy float64
sin float64
cos float64
neww float64
newh float64
src *image.RGBA
}
func radian(angle float64) float64 {
return angle * math.Pi / 180.0
}
func (r *rotate) Rotate(angle float64, src *image.RGBA) *rotate {
r.src = src
srsize := src.Bounds().Size()
width, height := srsize.X, srsize.Y
// 源图四个角的坐标(以图像中心为坐标系原点)
// 左下角,右下角,左上角,右上角
srcwp, srchp := float64(width)*0.5, float64(height)*0.5
srcx1, srcy1 := -srcwp, srchp
srcx2, srcy2 := srcwp, srchp
srcx3, srcy3 := -srcwp, -srchp
srcx4, srcy4 := srcwp, -srchp
r.sin, r.cos = math.Sincos(radian(angle))
// 旋转后的四角坐标
desx1, desy1 := r.cos*srcx1+r.sin*srcy1, -r.sin*srcx1+r.cos*srcy1
desx2, desy2 := r.cos*srcx2+r.sin*srcy2, -r.sin*srcx2+r.cos*srcy2
desx3, desy3 := r.cos*srcx3+r.sin*srcy3, -r.sin*srcx3+r.cos*srcy3
desx4, desy4 := r.cos*srcx4+r.sin*srcy4, -r.sin*srcx4+r.cos*srcy4
// 新的高度很宽度
r.neww = math.Max(math.Abs(desx4-desx1), math.Abs(desx3-desx2)) + 0.5
r.newh = math.Max(math.Abs(desy4-desy1), math.Abs(desy3-desy2)) + 0.5
r.dx = -0.5*r.neww*r.cos - 0.5*r.newh*r.sin + srcwp
r.dy = 0.5*r.neww*r.sin - 0.5*r.newh*r.cos + srchp
return r
}
func (r *rotate) pt(x, y int) (float64, float64) {
return float64(-y)*r.sin + float64(x)*r.cos + r.dy,
float64(y)*r.cos + float64(x)*r.sin + r.dx
}
func (r *rotate) transformRGBA() image.Image {
srcb := r.src.Bounds()
b := image.Rect(0, 0, int(r.neww), int(r.newh))
dst := image.NewRGBA(b)
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
sx, sy := r.pt(x, y)
if inBounds(srcb, sx, sy) {
// 消除锯齿填色
c := bili.RGBA(r.src, sx, sy)
off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
dst.Pix[off+0] = c.R
dst.Pix[off+1] = c.G
dst.Pix[off+2] = c.B
dst.Pix[off+3] = c.A
}
}
}
return dst
}
+71
View File
@@ -0,0 +1,71 @@
# golang实现的验证码 golang captcha
## 优点
1. 使用简单
2. 不依赖第三方图形库 直接go get 就Ok
3. 丰富自定义设置(字体,多颜色,验证码大小,文字模式,文字数量,干扰强度)
## demo
![color](http://afocus.github.io/captcha/imgs/background.png)![sample](http://afocus.github.io/captcha/imgs/default.png)![color2](http://afocus.github.io/captcha/imgs/colors.png)
## 使用 Start using it
Download and install it:
```
go get github.com/afocus/captcha
```
**必须设置font**
#### 最简单的示例 sample use
```go
cap = captcha.New()
// 设置字体
cap.SetFont("comic.ttf")
// 创建验证码 4个字符 captcha.NUM 字符模式数字类型
// 返回验证码图像对象以及验证码字符串 后期可以对字符串进行对比 判断验证
img,str := cap.Create(4,captcha.NUM)
```
#### 设置 set options
```go
cap = captcha.New()
// 可以设置多个字体 或使用cap.AddFont("xx.ttf")追加
cap.SetFont("comic.ttf", "xxx.ttf")
// 设置验证码大小
cap.SetSize(128, 64)
// 设置干扰强度
cap.SetDisturbance(captcha.MEDIUM)
// 设置前景色 可以多个 随机替换文字颜色 默认黑色
cap.SetFrontColor(color.RGBA{255, 255, 255, 255})
// 设置背景色 可以多个 随机替换背景色 默认白色
cap.SetBkgColor(color.RGBA{255, 0, 0, 255}, color.RGBA{0, 0, 255, 255}, color.RGBA{0, 153, 0, 255})
img,str := cap.Create(4,captcha.NUM)
img1,str1 := cap.Create(6,captcha.ALL)
```
#### 自定义字符串 custom captcha words
```go
cap = captcha.New()
// 设置字体
cap.SetFont("comic.ttf")
img := cap.CreateCustom("hello")
```
#### 网站中如果使用? how to use for web
look `examples/main.go`