picgo/corelib/captcha/image.go

340 lines
8.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package captcha
import (
"crypto/rand"
"github.com/golang/freetype"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"image"
"image/color"
"log"
"math"
"math/big"
"os"
"path/filepath"
"strconv"
)
const (
chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
operator = "+-*/"
defaultLen = 4
defaultFontSize = 20
defaultDpi = 72
)
// Captcha 图形验证码 使用字体默认ttf格式
// w 图片宽度, h图片高度CodeLen验证码的个数
// FontSize 字体大小, Dpi 清晰度FontPath 字体目录, FontName 字体名字
// mode 验证模式 0普通字符串110以内简单数学公式
type Captcha struct {
W, H, CodeLen int
FontSize float64
Dpi int
FontPath, FontName string
mode int
}
// NewCaptcha 实例化验证码
func NewCaptcha(w, h, CodeLen int) *Captcha {
return &Captcha{W: w, H: h, CodeLen: CodeLen}
}
// OutPut 输出
func (captcha *Captcha) OutPut() (string, *image.RGBA) {
img := captcha.initCanvas()
return captcha.doImage(img)
}
// RangeRand 获取区间[-m, n]的随机数
func (captcha *Captcha) RangeRand(min, max int64) int64 {
if min > max {
panic("the min is greater than max!")
}
if min < 0 {
f64Min := math.Abs(float64(min))
i64Min := int64(f64Min)
result, _ := rand.Int(rand.Reader, big.NewInt(max+1+i64Min))
return result.Int64() - i64Min
} else {
result, _ := rand.Int(rand.Reader, big.NewInt(max-min+1))
return min + result.Int64()
}
}
// 随机字符串
func (captcha *Captcha) getRandCode() string {
if captcha.CodeLen <= 0 {
captcha.CodeLen = defaultLen
}
var code = ""
for l := 0; l < captcha.CodeLen; l++ {
charsPos := captcha.RangeRand(0, int64(len(chars)-1))
code += string(chars[charsPos])
}
return code
}
// 获取算术运算公式
func (captcha *Captcha) getFormulaMixData() (string, []string) {
num1 := int(captcha.RangeRand(11, 20))
num2 := int(captcha.RangeRand(1, 10))
opArr := []rune(operator)
opRand := opArr[captcha.RangeRand(0, 3)]
strNum1 := strconv.Itoa(num1)
strNum2 := strconv.Itoa(num2)
var ret int
var opRet string
switch string(opRand) {
case "+":
ret = num1 + num2
opRet = "+"
case "-":
if num1-num2 > 0 {
ret = num1 - num2
opRet = "-"
} else {
ret = num1 + num2
opRet = "+"
}
case "*":
if num1*num2 < 100 {
ret = num1 * num2
//opRet = "×"
opRet = "×"
} else {
ret = num1 + num2
opRet = "+"
}
case "/":
if num1%num2 == 0 {
ret = num1 / num2
opRet = "÷"
} else {
ret = num1 + num2
opRet = "+"
}
}
return strconv.Itoa(ret), []string{strNum1, opRet, strNum2, "=", "?"}
}
// 初始化画布
func (captcha *Captcha) initCanvas() *image.RGBA {
dest := image.NewRGBA(image.Rect(0, 0, captcha.W, captcha.H))
// 随机色
r := uint8(255) // uint8(captcha.RangeRand(50, 250))
g := uint8(255) // uint8(captcha.RangeRand(50, 250))
b := uint8(255) // uint8(captcha.RangeRand(50, 250))
// 填充背景色
for x := 0; x < captcha.W; x++ {
for y := 0; y < captcha.H; y++ {
dest.Set(x, y, color.RGBA{R: r, G: g, B: b, A: 255}) //设定alpha图片的透明度
}
}
return dest
}
// 处理图像
func (captcha *Captcha) doImage(dest *image.RGBA) (string, *image.RGBA) {
gc := draw2dimg.NewGraphicContext(dest)
defer gc.Close()
defer gc.FillStroke()
captcha.setFont(gc)
captcha.doPoint(gc)
captcha.doLine(gc)
captcha.doSinLine(gc)
var codeStr string
if captcha.mode == 1 {
ret, formula := captcha.getFormulaMixData()
// log.Println("算数验证码: ", formula)
codeStr = ret
captcha.doFormula(gc, formula)
} else {
codeStr = captcha.getRandCode()
captcha.doCode(gc, codeStr)
}
return codeStr, dest
}
// 验证码字符设置到图像上
func (captcha *Captcha) doCode(gc *draw2dimg.GraphicContext, code string) {
for l := 0; l < len(code); l++ {
y := captcha.RangeRand(int64(captcha.FontSize)-1, int64(captcha.H)+6)
x := captcha.RangeRand(1, 20)
// 随机色
r := uint8(captcha.RangeRand(0, 200))
g := uint8(captcha.RangeRand(0, 200))
b := uint8(captcha.RangeRand(0, 200))
gc.SetFillColor(color.RGBA{R: r, G: g, B: b, A: 255})
gc.FillStringAt(string(code[l]), float64(x)+captcha.FontSize*float64(l), float64(int64(captcha.H)-y)+captcha.FontSize)
gc.Stroke()
}
}
// 验证码字符设置到图像上
func (captcha *Captcha) doFormula(gc *draw2dimg.GraphicContext, formulaArr []string) {
for l := 0; l < len(formulaArr); l++ {
y := captcha.RangeRand(0, 10)
x := captcha.RangeRand(5, 10)
// 随机色
r := uint8(captcha.RangeRand(10, 200))
g := uint8(captcha.RangeRand(10, 200))
b := uint8(captcha.RangeRand(10, 200))
gc.SetFillColor(color.RGBA{R: r, G: g, B: b, A: 255})
gc.FillStringAt(formulaArr[l], float64(x)+captcha.FontSize*float64(l), captcha.FontSize+float64(y))
gc.Stroke()
}
}
// 增加干扰线
func (captcha *Captcha) doLine(gc *draw2dimg.GraphicContext) {
// 设置干扰线
for n := 0; n < 3; n++ {
gc.SetLineWidth(float64(captcha.RangeRand(1, 2)))
//gc.SetLineWidth(1)
// 随机背景色
r := uint8(captcha.RangeRand(0, 255))
g := uint8(captcha.RangeRand(0, 255))
b := uint8(captcha.RangeRand(0, 255))
gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255})
// 初始化位置
gc.MoveTo(float64(captcha.RangeRand(0, int64(captcha.W)+10)), float64(captcha.RangeRand(0, int64(captcha.H)+5)))
gc.LineTo(float64(captcha.RangeRand(0, int64(captcha.W)+10)), float64(captcha.RangeRand(0, int64(captcha.H)+5)))
gc.Stroke()
}
}
// 增加干扰点
func (captcha *Captcha) doPoint(gc *draw2dimg.GraphicContext) {
for n := 0; n < 10; n++ {
gc.SetLineWidth(float64(captcha.RangeRand(1, 3)))
// 随机色
r := uint8(captcha.RangeRand(0, 255))
g := uint8(captcha.RangeRand(0, 255))
b := uint8(captcha.RangeRand(0, 255))
gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255})
x := captcha.RangeRand(0, int64(captcha.W)+10) + 1
y := captcha.RangeRand(0, int64(captcha.H)+5) + 1
gc.MoveTo(float64(x), float64(y))
gc.LineTo(float64(x+captcha.RangeRand(1, 2)), float64(y+captcha.RangeRand(1, 2)))
gc.Stroke()
}
}
// 增加正弦干扰线
func (captcha *Captcha) doSinLine(gc *draw2dimg.GraphicContext) {
h1 := captcha.RangeRand(-12, 12)
h2 := captcha.RangeRand(-1, 1)
w2 := captcha.RangeRand(5, 20)
h3 := captcha.RangeRand(5, 10)
h := float64(captcha.H)
w := float64(captcha.W)
// 随机色
r := uint8(captcha.RangeRand(128, 255))
g := uint8(captcha.RangeRand(128, 255))
b := uint8(captcha.RangeRand(128, 255))
gc.SetStrokeColor(color.RGBA{R: r, G: g, B: b, A: 255})
gc.SetLineWidth(float64(captcha.RangeRand(1, 2)))
var i float64
for i = -w / 2; i < w/2; i = i + 0.1 {
y := h/float64(h3)*math.Sin(i/float64(w2)) + h/2 + float64(h1)
gc.LineTo(i+w/2, y)
if h2 == 0 {
gc.LineTo(i+w/2, y+float64(h2))
}
}
gc.Stroke()
}
// SetMode 设置模式
func (captcha *Captcha) SetMode(mode int) {
captcha.mode = mode
}
// SetFontPath 设置字体路径
func (captcha *Captcha) SetFontPath(FontPath string) {
captcha.FontPath = FontPath
}
// SetFontName 设置字体名称
func (captcha *Captcha) SetFontName(FontName string) {
captcha.FontName = FontName
}
// SetFontSize 设置字体大小
func (captcha *Captcha) SetFontSize(fontSize float64) {
captcha.FontSize = fontSize
}
// 设置相关字体
func (captcha *Captcha) setFont(gc *draw2dimg.GraphicContext) {
if captcha.FontPath == "" {
panic("the font path is empty!")
}
if captcha.FontName == "" {
panic("the font name is empty!")
}
// 字体文件
fontFile := filepath.Join(captcha.FontPath, captcha.FontName+".ttf")
fontBytes, err := os.ReadFile(fontFile)
if err != nil {
log.Println(err)
return
}
font, err := freetype.ParseFont(fontBytes)
if err != nil {
log.Println(err)
return
}
// 设置自定义字体相关信息
gc.FontCache = draw2d.NewSyncFolderFontCache(captcha.FontPath)
gc.FontCache.Store(draw2d.FontData{Name: captcha.FontName, Family: 0, Style: draw2d.FontStyleNormal}, font)
gc.SetFontData(draw2d.FontData{Name: captcha.FontName, Style: draw2d.FontStyleNormal})
//设置清晰度
if captcha.Dpi <= 0 {
captcha.Dpi = defaultDpi
}
gc.SetDPI(captcha.Dpi)
// 设置字体大小
if captcha.FontSize <= 0 {
captcha.FontSize = defaultFontSize
}
gc.SetFontSize(captcha.FontSize)
}