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