captcha.go 5.5 KB

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