248 lines
5.8 KiB
Go
248 lines
5.8 KiB
Go
package service
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"io/ioutil"
|
|
"math"
|
|
"math/rand"
|
|
"time"
|
|
|
|
"go-common/app/interface/main/captcha/conf"
|
|
|
|
"github.com/golang/freetype"
|
|
"github.com/golang/freetype/truetype"
|
|
)
|
|
|
|
// CONST VALUE.
|
|
const (
|
|
NORMAL = int(4)
|
|
MEDIUM = int(8)
|
|
HIGH = int(16)
|
|
MinLenStart = int(4)
|
|
MinWidth = int(48)
|
|
MinLength = int(20)
|
|
Length48 = int(48)
|
|
|
|
TypeNone = int(0)
|
|
TypeLOWER = int(1)
|
|
TypeUPPER = int(2)
|
|
TypeALL = int(3)
|
|
)
|
|
|
|
var fontKinds = [][]int{[]int{10, 48}, []int{26, 97}, []int{26, 65}}
|
|
|
|
func sign(x int) int {
|
|
if x > 0 {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// NewCaptcha new a captcha.
|
|
func newCaptcha(c *conf.Captcha) *Captcha {
|
|
captcha := &Captcha{
|
|
disturbLevel: NORMAL,
|
|
}
|
|
captcha.frontColors = []color.Color{color.Black}
|
|
captcha.bkgColors = []color.Color{color.White}
|
|
captcha.setFont(c.Fonts...)
|
|
colors := []color.Color{}
|
|
for _, v := range c.BkgColors {
|
|
colors = append(colors, v)
|
|
}
|
|
captcha.setBkgColor(colors...)
|
|
colors = []color.Color{}
|
|
for _, v := range c.FrontColors {
|
|
colors = append(colors, v)
|
|
}
|
|
captcha.setFontColor(colors...)
|
|
captcha.setDisturbance(c.DisturbLevel)
|
|
return captcha
|
|
}
|
|
|
|
// addFont add font.
|
|
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
|
|
}
|
|
|
|
// setFont set font.
|
|
func (c *Captcha) setFont(paths ...string) (err error) {
|
|
for _, v := range paths {
|
|
if err = c.addFont(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// setBkgColor set backgroud color.
|
|
func (c *Captcha) setBkgColor(colors ...color.Color) {
|
|
if len(colors) > 0 {
|
|
c.bkgColors = c.bkgColors[:0]
|
|
c.bkgColors = append(c.bkgColors, colors...)
|
|
}
|
|
}
|
|
|
|
func (c *Captcha) randFont() *truetype.Font {
|
|
return c.fonts[rand.Intn(len(c.fonts))]
|
|
}
|
|
|
|
// setBkgsetFontColorColor set font color.
|
|
func (c *Captcha) setFontColor(colors ...color.Color) {
|
|
if len(colors) > 0 {
|
|
c.frontColors = c.frontColors[:0]
|
|
c.frontColors = append(c.frontColors, colors...)
|
|
}
|
|
}
|
|
|
|
// setDisturbance set disturbance.
|
|
func (c *Captcha) setDisturbance(d int) {
|
|
if d > 0 {
|
|
c.disturbLevel = d
|
|
}
|
|
}
|
|
|
|
func (c *Captcha) createImage(lenStart, lenEnd, width, length, t int) (image *Image, str string) {
|
|
num := MinLenStart
|
|
if lenStart < MinLenStart {
|
|
lenStart = MinLenStart
|
|
}
|
|
if lenEnd > lenStart {
|
|
// rand.Seed(time.Now().UnixNano())
|
|
num = rand.Intn(lenEnd-lenStart+1) + lenStart
|
|
}
|
|
str = c.randStr(num, t)
|
|
return c.createCustom(str, width, length), str
|
|
}
|
|
|
|
func (c *Captcha) createCustom(str string, width, length int) *Image {
|
|
// boundary check
|
|
if len(str) == 0 {
|
|
str = "bilibili"
|
|
}
|
|
if width < MinWidth {
|
|
width = MinWidth
|
|
}
|
|
if length < MinLength {
|
|
length = MinLength
|
|
}
|
|
dst := newImage(width, length)
|
|
c.drawBkg(dst)
|
|
c.drawNoises(dst)
|
|
c.drawString(dst, str, width, length)
|
|
return dst
|
|
}
|
|
|
|
// randStr ascII random
|
|
// 48~57 -> 0~9 number
|
|
// 65~90 -> A~Z uppercase
|
|
// 98~122 -> a~z lowcase
|
|
func (c *Captcha) randStr(size, kind int) string {
|
|
ikind, result := kind, make([]byte, size)
|
|
isAll := kind > TypeUPPER || kind < TypeNone
|
|
// rand.Seed(time.Now().UnixNano())
|
|
for i := 0; i < size; i++ {
|
|
if isAll {
|
|
ikind = rand.Intn(TypeALL)
|
|
}
|
|
scope, base := fontKinds[ikind][0], fontKinds[ikind][1]
|
|
result[i] = uint8(base + rand.Intn(scope))
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
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()))
|
|
//// 待绘制图片的尺寸
|
|
point := img.Bounds().Size()
|
|
disturbLevel := c.disturbLevel
|
|
// 绘制干扰斑点
|
|
for i := 0; i < disturbLevel; i++ {
|
|
x := ra.Intn(point.X)
|
|
y := ra.Intn(point.Y)
|
|
radius := ra.Intn(point.Y/20) + 1
|
|
colorindex := ra.Intn(len(c.frontColors))
|
|
img.drawCircle(x, y, radius, i%4 != 0, c.frontColors[colorindex])
|
|
}
|
|
// 绘制干扰线
|
|
for i := 0; i < disturbLevel; i++ {
|
|
x := ra.Intn(point.X)
|
|
y := ra.Intn(point.Y)
|
|
o := int(math.Pow(-1, float64(i)))
|
|
w := ra.Intn(point.Y) * o
|
|
h := ra.Intn(point.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, width, length int) {
|
|
|
|
if c.fonts == nil {
|
|
panic("没有设置任何字体")
|
|
}
|
|
tmp := newImage(width, length)
|
|
|
|
// 文字大小为图片高度的 0.6
|
|
fsize := int(float64(length) * 0.6)
|
|
// 用于生成随机角度
|
|
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
// 文字之间的距离
|
|
// 左右各留文字的1/4大小为内部边距
|
|
padding := fsize / 4
|
|
gap := (width - 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 := (length - s.Y) / 2
|
|
// 绘制到图片上
|
|
draw.Draw(tmp, image.Rect(left, top, left+s.X, top+s.Y), rs, image.ZP, draw.Over)
|
|
}
|
|
if length >= Length48 {
|
|
// 高度大于48添加波纹 小于48波纹影响用户识别
|
|
tmp.distortTo(float64(fsize)/10, 200.0)
|
|
}
|
|
draw.Draw(img, tmp.Bounds(), tmp, image.ZP, draw.Over)
|
|
}
|