captcha.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package captcha
  2. import (
  3. "github.com/golang/freetype"
  4. "github.com/golang/freetype/truetype"
  5. "image"
  6. "image/color"
  7. "image/draw"
  8. "io/ioutil"
  9. "math"
  10. "math/rand"
  11. "time"
  12. )
  13. type Captcha struct {
  14. frontColors []color.Color
  15. bkgColors []color.Color
  16. disturlvl DisturLevel
  17. fonts []*truetype.Font
  18. size image.Point
  19. }
  20. type StrType int
  21. const (
  22. NUM StrType = iota // 数字
  23. LOWER // 小写字母
  24. UPPER // 大写字母
  25. ALL // 全部
  26. CLEAR // 去除部分易混淆的字符
  27. )
  28. type DisturLevel int
  29. const (
  30. NORMAL DisturLevel = 4
  31. MEDIUM DisturLevel = 8
  32. HIGH DisturLevel = 16
  33. )
  34. func New() *Captcha {
  35. c := &Captcha{
  36. disturlvl: HIGH,
  37. size: image.Point{82, 32},
  38. }
  39. c.frontColors = []color.Color{color.Black}
  40. c.bkgColors = []color.Color{color.Transparent}
  41. c.SetFont("config/font/comic.ttf")
  42. return c
  43. }
  44. // AddFont 添加一个字体
  45. func (c *Captcha) AddFont(path string) error {
  46. fontdata, erro := ioutil.ReadFile(path)
  47. if erro != nil {
  48. return erro
  49. }
  50. font, erro := freetype.ParseFont(fontdata)
  51. if erro != nil {
  52. return erro
  53. }
  54. if c.fonts == nil {
  55. c.fonts = []*truetype.Font{}
  56. }
  57. c.fonts = append(c.fonts, font)
  58. return nil
  59. }
  60. //AddFontFromBytes allows to load font from slice of bytes, for example, load the font packed by https://github.com/jteeuwen/go-bindata
  61. func (c *Captcha) AddFontFromBytes(contents []byte) error {
  62. font, err := freetype.ParseFont(contents)
  63. if err != nil {
  64. return err
  65. }
  66. if c.fonts == nil {
  67. c.fonts = []*truetype.Font{}
  68. }
  69. c.fonts = append(c.fonts, font)
  70. return nil
  71. }
  72. // SetFont 设置字体 可以设置多个
  73. func (c *Captcha) SetFont(paths ...string) error {
  74. for _, v := range paths {
  75. if erro := c.AddFont(v); erro != nil {
  76. return erro
  77. }
  78. }
  79. return nil
  80. }
  81. func (c *Captcha) SetDisturbance(d DisturLevel) {
  82. if d > 0 {
  83. c.disturlvl = d
  84. }
  85. }
  86. func (c *Captcha) SetFrontColor(colors ...color.Color) {
  87. if len(colors) > 0 {
  88. c.frontColors = c.frontColors[:0]
  89. for _, v := range colors {
  90. c.frontColors = append(c.frontColors, v)
  91. }
  92. }
  93. }
  94. func (c *Captcha) SetBkgColor(colors ...color.Color) {
  95. if len(colors) > 0 {
  96. c.bkgColors = c.bkgColors[:0]
  97. for _, v := range colors {
  98. c.bkgColors = append(c.bkgColors, v)
  99. }
  100. }
  101. }
  102. func (c *Captcha) SetSize(w, h int) {
  103. if w < 48 {
  104. w = 48
  105. }
  106. if h < 20 {
  107. h = 20
  108. }
  109. c.size = image.Point{w, h}
  110. }
  111. func (c *Captcha) randFont() *truetype.Font {
  112. return c.fonts[rand.Intn(len(c.fonts))]
  113. }
  114. // 绘制背景
  115. func (c *Captcha) drawBkg(img *Image) {
  116. /*ra := rand.New(rand.NewSource(time.Now().UnixNano()))
  117. //填充主背景色
  118. bgcolorindex := ra.Intn(len(c.bkgColors))
  119. bkg := image.NewUniform(c.bkgColors[bgcolorindex])
  120. img.FillBkg(bkg)*/
  121. }
  122. // 绘制噪点
  123. func (c *Captcha) drawNoises(img *Image) {
  124. ra := rand.New(rand.NewSource(time.Now().UnixNano()))
  125. // 待绘制图片的尺寸
  126. size := img.Bounds().Size()
  127. dlen := int(c.disturlvl)
  128. // 绘制干扰斑点
  129. for i := 0; i < dlen; i++ {
  130. x := ra.Intn(size.X)
  131. y := ra.Intn(size.Y)
  132. r := ra.Intn(size.Y/20) + 1
  133. colorindex := ra.Intn(len(c.frontColors))
  134. img.DrawCircle(x, y, r, i%4 != 0, c.frontColors[colorindex])
  135. }
  136. // 绘制干扰线
  137. for i := 0; i < dlen; i++ {
  138. x := ra.Intn(size.X)
  139. y := ra.Intn(size.Y)
  140. o := int(math.Pow(-1, float64(i)))
  141. w := ra.Intn(size.Y) * o
  142. h := ra.Intn(size.Y/10) * o
  143. colorindex := ra.Intn(len(c.frontColors))
  144. img.DrawLine(x, y, x+w, y+h, c.frontColors[colorindex])
  145. colorindex++
  146. }
  147. }
  148. // 绘制文字
  149. func (c *Captcha) drawString(img *Image, str string) {
  150. if c.fonts == nil {
  151. panic("没有设置任何字体")
  152. }
  153. tmp := NewImage(c.size.X, c.size.Y)
  154. // 文字大小为图片高度的 0.6
  155. fsize := int(float64(c.size.Y) * 0.6)
  156. // 用于生成随机角度
  157. r := rand.New(rand.NewSource(time.Now().UnixNano()))
  158. // 文字之间的距离
  159. // 左右各留文字的1/4大小为内部边距
  160. padding := fsize / 4
  161. gap := (c.size.X - padding*2) / (len(str))
  162. // 逐个绘制文字到图片上
  163. for i, char := range str {
  164. // 创建单个文字图片
  165. // 以文字为尺寸创建正方形的图形
  166. str := NewImage(fsize, fsize)
  167. // str.FillBkg(image.NewUniform(color.Black))
  168. // 随机取一个前景色
  169. colorindex := r.Intn(len(c.frontColors))
  170. //随机取一个字体
  171. font := c.randFont()
  172. str.DrawString(font, c.frontColors[colorindex], string(char), float64(fsize))
  173. // 转换角度后的文字图形
  174. rs := str.Rotate(float64(r.Intn(40) - 20))
  175. // 计算文字位置
  176. s := rs.Bounds().Size()
  177. left := i*gap + padding
  178. top := (c.size.Y - s.Y) / 2
  179. // 绘制到图片上
  180. draw.Draw(tmp, image.Rect(left, top, left+s.X, top+s.Y), rs, image.ZP, draw.Over)
  181. }
  182. if c.size.Y >= 48 {
  183. // 高度大于48添加波纹 小于48波纹影响用户识别
  184. tmp.distortTo(float64(fsize)/10, 200.0)
  185. }
  186. /*font := c.randFont()
  187. t := strings.ToUpper(helper.NewHash(helper.NewUUID().Bytes()).Md5().String())
  188. tmp.DrawString(font, color.RGBA{98, 98, 98, 255}, t, 6)*/
  189. draw.Draw(img, tmp.Bounds(), tmp, image.ZP, draw.Over)
  190. }
  191. // Create 生成一个验证码图片
  192. func (c *Captcha) Create(num int, t StrType) (*Image, string) {
  193. if num <= 0 {
  194. num = 4
  195. }
  196. dst := NewImage(c.size.X, c.size.Y)
  197. //tmp := NewImage(c.size.X, c.size.Y)
  198. c.drawBkg(dst)
  199. c.drawNoises(dst)
  200. str := string(c.randStr(num, int(t)))
  201. c.drawString(dst, str)
  202. /*uuid := helper.NewUUID().String()
  203. c.drawString(dst, uuid)*/
  204. return dst, str
  205. }
  206. func (c *Captcha) CreateCustom(str string) *Image {
  207. if len(str) == 0 {
  208. str = "unkown"
  209. }
  210. dst := NewImage(c.size.X, c.size.Y)
  211. c.drawBkg(dst)
  212. c.drawNoises(dst)
  213. c.drawString(dst, str)
  214. return dst
  215. }
  216. var fontKinds = [][]int{{10, 48}, {26, 97}, {26, 65}}
  217. var letters = []byte("34578acdefghjkmnpqstwxyABCDEFGHJKMNPQRSVWXY")
  218. // 生成随机字符串
  219. // size 个数 kind 模式
  220. func (c *Captcha) randStr(size int, kind int) []byte {
  221. ikind, result := kind, make([]byte, size)
  222. isAll := kind > 2 || kind < 0
  223. rand.Seed(time.Now().UnixNano())
  224. for i := 0; i < size; i++ {
  225. if isAll {
  226. ikind = rand.Intn(3)
  227. }
  228. scope, base := fontKinds[ikind][0], fontKinds[ikind][1]
  229. result[i] = uint8(base + rand.Intn(scope))
  230. // 不易混淆字符模式:重新生成字符
  231. if kind == 4 {
  232. result[i] = letters[rand.Intn(len(letters))]
  233. }
  234. }
  235. return result
  236. }