vision/captcha.go

119 lines
2.9 KiB
Go

package vision
import (
"image/color"
"math"
"apigo.cc/go/rand"
)
// CaptchaOption 定义验证码生成选项
type CaptchaOption struct {
Text string
Length int
Width int
Height int
NoiseLevel int // 1-10
}
// GenerateCaptcha 生成一个验证码画布
func GenerateCaptcha(opt *CaptchaOption) *Canvas {
if opt == nil { opt = &CaptchaOption{} }
if opt.Length == 0 { opt.Length = 4 }
if opt.Width == 0 { opt.Width = 150 }
if opt.Height == 0 { opt.Height = 50 }
if opt.NoiseLevel == 0 { opt.NoiseLevel = 3 }
if opt.Text == "" {
chars := "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnpqrstwxyz2345678"
text := make([]byte, opt.Length)
for i := 0; i < opt.Length; i++ {
text[i] = chars[rand.Int(0, len(chars)-1)]
}
opt.Text = string(text)
}
c := New(opt.Width, opt.Height, "#FFFFFF")
c.RandBG(opt.NoiseLevel)
// 计算字体大小
fontSize := math.Max(28, float64(opt.Height)*0.7)
_ = c.SetFont(fontSize)
// 绘制随机扭曲文本
c.RandText(opt.Text)
return c
}
// GenerateGIFCaptcha 生成动态 GIF 验证码
func GenerateGIFCaptcha(opt *CaptchaOption) *Animation {
if opt == nil {
opt = &CaptchaOption{Length: 4, Width: 120, Height: 40}
}
const chars = "0123456789"
code := ""
for i := 0; i < opt.Length; i++ {
code += string(chars[rand.Int(0, len(chars)-1)])
}
anim := NewAnimation()
// 每一帧显示不同的干扰和字符位移
for i := 0; i < 10; i++ {
c := New(opt.Width, opt.Height, "#FFFFFF")
c.RandBG(3)
// 绘制字符,每一帧字符的位置都有微小抖动
for idx, char := range code {
x := float64(idx)*float64(opt.Width/opt.Length) + 5 + rand.Float(0.0, 4.0) - 2.0
y := float64(opt.Height/2) + 10 + rand.Float(0.0, 6.0) - 3.0
c.dc.Push()
c.dc.RotateAbout(rand.Float(0.0, 0.4)-0.2, x, y)
c.dc.SetColor(ParseColor(RandColor()))
c.dc.DrawString(string(char), x, y)
c.dc.Pop()
}
anim.AddFrame(c, 15) // 150ms 每帧
}
return anim
}
// RandText 绘制随机扭曲文本 (用于验证码)
func (c *Canvas) RandText(text string) [][4]float64 {
w, h := float64(c.Width()), float64(c.Height())
fullWidth, _ := c.dc.MeasureString(text)
x := (w - fullWidth) / 2
y := h/2 + (c.dc.FontHeight()*0.7)/2
charPositions := make([][4]float64, 0, len(text))
for _, char := range text {
charStr := string(char)
charWidth, _ := c.dc.MeasureString(charStr)
charHeight := c.dc.FontHeight()
yOffset := rand.Float(0.0, 10.0) - 5
angle := rand.Float(0.0, 0.4) - 0.2 // ±11°
charPositions = append(charPositions, [4]float64{x, y + yOffset - charHeight, charWidth, charHeight})
c.dc.Push()
c.dc.RotateAbout(angle, x+charWidth/2, y+yOffset-charHeight/2)
// 绘制阴影
c.dc.SetColor(color.Gray{Y: 100})
c.dc.DrawString(charStr, x+1, y+yOffset+1)
// 绘制主体
c.dc.SetColor(ParseColor(RandColor()))
c.dc.DrawString(charStr, x, y+yOffset)
c.dc.Pop()
x += charWidth + rand.Float(0.0, 5.0)
}
return charPositions
}