123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- 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)
- }
|