draw.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package captcha
  2. import (
  3. "bytes"
  4. "image"
  5. "image/color"
  6. "image/draw"
  7. "image/png"
  8. "math"
  9. "github.com/golang/freetype"
  10. "github.com/golang/freetype/truetype"
  11. )
  12. // Image 图片
  13. type Image struct {
  14. *image.RGBA
  15. }
  16. // NewImage 创建一个新的图片
  17. func NewImage(w, h int) *Image {
  18. img := &Image{image.NewRGBA(image.Rect(0, 0, w, h))}
  19. return img
  20. }
  21. func sign(x int) int {
  22. if x > 0 {
  23. return 1
  24. }
  25. return -1
  26. }
  27. // DrawLine 画直线
  28. // Bresenham算法(https://zh.wikipedia.org/zh-cn/布雷森漢姆直線演算法)
  29. // x1,y1 起点 x2,y2终点
  30. func (img *Image) DrawLine(x1, y1, x2, y2 int, c color.Color) {
  31. dx, dy, flag := int(math.Abs(float64(x2-x1))),
  32. int(math.Abs(float64(y2-y1))),
  33. false
  34. if dy > dx {
  35. flag = true
  36. x1, y1 = y1, x1
  37. x2, y2 = y2, x2
  38. dx, dy = dy, dx
  39. }
  40. ix, iy := sign(x2-x1), sign(y2-y1)
  41. n2dy := dy * 2
  42. n2dydx := (dy - dx) * 2
  43. d := n2dy - dx
  44. for x1 != x2 {
  45. if d < 0 {
  46. d += n2dy
  47. } else {
  48. y1 += iy
  49. d += n2dydx
  50. }
  51. if flag {
  52. img.Set(y1, x1, c)
  53. } else {
  54. img.Set(x1, y1, c)
  55. }
  56. x1 += ix
  57. }
  58. }
  59. func (img *Image) drawCircle8(xc, yc, x, y int, c color.Color) {
  60. img.Set(xc+x, yc+y, c)
  61. img.Set(xc-x, yc+y, c)
  62. img.Set(xc+x, yc-y, c)
  63. img.Set(xc-x, yc-y, c)
  64. img.Set(xc+y, yc+x, c)
  65. img.Set(xc-y, yc+x, c)
  66. img.Set(xc+y, yc-x, c)
  67. img.Set(xc-y, yc-x, c)
  68. }
  69. // DrawCircle 画圆
  70. // xc,yc 圆心坐标 r 半径 fill是否填充颜色
  71. func (img *Image) DrawCircle(xc, yc, r int, fill bool, c color.Color) {
  72. size := img.Bounds().Size()
  73. // 如果圆在图片可见区域外,直接退出
  74. if xc+r < 0 || xc-r >= size.X || yc+r < 0 || yc-r >= size.Y {
  75. return
  76. }
  77. x, y, d := 0, r, 3-2*r
  78. for x <= y {
  79. if fill {
  80. for yi := x; yi <= y; yi++ {
  81. img.drawCircle8(xc, yc, x, yi, c)
  82. }
  83. } else {
  84. img.drawCircle8(xc, yc, x, y, c)
  85. }
  86. if d < 0 {
  87. d = d + 4*x + 6
  88. } else {
  89. d = d + 4*(x-y) + 10
  90. y--
  91. }
  92. x++
  93. }
  94. }
  95. // DrawString 写字
  96. func (img *Image) DrawString(font *truetype.Font, c color.Color, str string, fontsize float64) {
  97. ctx := freetype.NewContext()
  98. // default 72dpi
  99. ctx.SetDst(img)
  100. ctx.SetClip(img.Bounds())
  101. ctx.SetSrc(image.NewUniform(c))
  102. ctx.SetFontSize(fontsize)
  103. ctx.SetFont(font)
  104. // 写入文字的位置
  105. pt := freetype.Pt(0, int(-fontsize/6)+ctx.PointToFixed(fontsize).Ceil())
  106. ctx.DrawString(str, pt)
  107. }
  108. // Rotate 旋转
  109. func (img *Image) Rotate(angle float64) image.Image {
  110. return new(rotate).Rotate(angle, img.RGBA).transformRGBA()
  111. }
  112. // 填充背景
  113. func (img *Image) FillBkg(c image.Image) {
  114. draw.Draw(img, img.Bounds(), c, image.ZP, draw.Over)
  115. }
  116. // 转为二进制数据
  117. func (img *Image) Bytes() []byte {
  118. var j_buf bytes.Buffer
  119. png.Encode(&j_buf, img)
  120. return j_buf.Bytes()
  121. }
  122. // 水波纹, amplude=振幅, period=周期
  123. // copy from https://github.com/dchest/captcha/blob/master/image.go
  124. func (img *Image) distortTo(amplude float64, period float64) {
  125. w := img.Bounds().Max.X
  126. h := img.Bounds().Max.Y
  127. oldm := img.RGBA
  128. dx := 1.4 * math.Pi / period
  129. for x := 0; x < w; x++ {
  130. for y := 0; y < h; y++ {
  131. xo := amplude * math.Sin(float64(y)*dx)
  132. yo := amplude * math.Cos(float64(x)*dx)
  133. rgba := oldm.RGBAAt(x+int(xo), y+int(yo))
  134. if rgba.A > 0 {
  135. oldm.SetRGBA(x, y, rgba)
  136. }
  137. }
  138. }
  139. }
  140. func inBounds(b image.Rectangle, x, y float64) bool {
  141. if x < float64(b.Min.X) || x >= float64(b.Max.X) {
  142. return false
  143. }
  144. if y < float64(b.Min.Y) || y >= float64(b.Max.Y) {
  145. return false
  146. }
  147. return true
  148. }
  149. type rotate struct {
  150. dx float64
  151. dy float64
  152. sin float64
  153. cos float64
  154. neww float64
  155. newh float64
  156. src *image.RGBA
  157. }
  158. func radian(angle float64) float64 {
  159. return angle * math.Pi / 180.0
  160. }
  161. func (r *rotate) Rotate(angle float64, src *image.RGBA) *rotate {
  162. r.src = src
  163. srsize := src.Bounds().Size()
  164. width, height := srsize.X, srsize.Y
  165. // 源图四个角的坐标(以图像中心为坐标系原点)
  166. // 左下角,右下角,左上角,右上角
  167. srcwp, srchp := float64(width)*0.5, float64(height)*0.5
  168. srcx1, srcy1 := -srcwp, srchp
  169. srcx2, srcy2 := srcwp, srchp
  170. srcx3, srcy3 := -srcwp, -srchp
  171. srcx4, srcy4 := srcwp, -srchp
  172. r.sin, r.cos = math.Sincos(radian(angle))
  173. // 旋转后的四角坐标
  174. desx1, desy1 := r.cos*srcx1+r.sin*srcy1, -r.sin*srcx1+r.cos*srcy1
  175. desx2, desy2 := r.cos*srcx2+r.sin*srcy2, -r.sin*srcx2+r.cos*srcy2
  176. desx3, desy3 := r.cos*srcx3+r.sin*srcy3, -r.sin*srcx3+r.cos*srcy3
  177. desx4, desy4 := r.cos*srcx4+r.sin*srcy4, -r.sin*srcx4+r.cos*srcy4
  178. // 新的高度很宽度
  179. r.neww = math.Max(math.Abs(desx4-desx1), math.Abs(desx3-desx2)) + 0.5
  180. r.newh = math.Max(math.Abs(desy4-desy1), math.Abs(desy3-desy2)) + 0.5
  181. r.dx = -0.5*r.neww*r.cos - 0.5*r.newh*r.sin + srcwp
  182. r.dy = 0.5*r.neww*r.sin - 0.5*r.newh*r.cos + srchp
  183. return r
  184. }
  185. func (r *rotate) pt(x, y int) (float64, float64) {
  186. return float64(-y)*r.sin + float64(x)*r.cos + r.dy,
  187. float64(y)*r.cos + float64(x)*r.sin + r.dx
  188. }
  189. func (r *rotate) transformRGBA() image.Image {
  190. srcb := r.src.Bounds()
  191. b := image.Rect(0, 0, int(r.neww), int(r.newh))
  192. dst := image.NewRGBA(b)
  193. for y := b.Min.Y; y < b.Max.Y; y++ {
  194. for x := b.Min.X; x < b.Max.X; x++ {
  195. sx, sy := r.pt(x, y)
  196. if inBounds(srcb, sx, sy) {
  197. // 消除锯齿填色
  198. c := bili.RGBA(r.src, sx, sy)
  199. off := (y-dst.Rect.Min.Y)*dst.Stride + (x-dst.Rect.Min.X)*4
  200. dst.Pix[off+0] = c.R
  201. dst.Pix[off+1] = c.G
  202. dst.Pix[off+2] = c.B
  203. dst.Pix[off+3] = c.A
  204. }
  205. }
  206. }
  207. return dst
  208. }