image.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. package captcha
  2. import (
  3. "image"
  4. "image/png"
  5. "io"
  6. "os"
  7. "rand"
  8. )
  9. const (
  10. maxSkew = 2
  11. // Standard width and height for captcha image
  12. StdWidth = 300
  13. StdHeight = 80
  14. )
  15. type CaptchaImage struct {
  16. *image.NRGBA
  17. primaryColor image.NRGBAColor
  18. numberWidth int
  19. numberHeight int
  20. dotSize int
  21. }
  22. // NewImage returns a new captcha image of the given width and height with the
  23. // given slice of numbers, where each number is 0-9.
  24. func NewImage(numbers []byte, width, height int) *CaptchaImage {
  25. img := new(CaptchaImage)
  26. img.NRGBA = image.NewNRGBA(width, height)
  27. img.primaryColor = image.NRGBAColor{uint8(rand.Intn(50)), uint8(rand.Intn(50)), uint8(rand.Intn(128)), 0xFF}
  28. // We need some space, so calculate border
  29. var border int
  30. if width > height {
  31. border = height / 5
  32. } else {
  33. border = width / 5
  34. }
  35. bwidth := width - border*2
  36. bheight := height - border*2
  37. img.calculateSizes(bwidth, bheight, len(numbers))
  38. // Background
  39. img.fillWithCircles(10, img.dotSize)
  40. maxx := width - (img.numberWidth+img.dotSize)*len(numbers) - img.dotSize
  41. maxy := height - img.numberHeight - img.dotSize*2
  42. x := rnd(img.dotSize*2, maxx)
  43. y := rnd(img.dotSize*2, maxy)
  44. setRandomBrightness(&img.primaryColor, 180)
  45. for _, n := range numbers {
  46. img.drawNumber(font[n], x, y)
  47. x += img.numberWidth + img.dotSize
  48. }
  49. img.strikeThrough()
  50. return img
  51. }
  52. // NewRandomImage generates random numbers and returns a new captcha image of
  53. // the given width and height with those numbers printed on it, and the numbers
  54. // themselves.
  55. func NewRandomImage(width, height int) (img *CaptchaImage, numbers []byte) {
  56. numbers = randomNumbers()
  57. img = NewImage(numbers, width, height)
  58. return
  59. }
  60. // PNGEncode writes captcha image in PNG format into the given writer.
  61. func (img *CaptchaImage) PNGEncode(w io.Writer) os.Error {
  62. return png.Encode(w, img)
  63. }
  64. func (img *CaptchaImage) calculateSizes(width, height, ncount int) {
  65. // Goal: fit all numbers into the image.
  66. // Convert everything to floats for calculations.
  67. w := float64(width)
  68. h := float64(height)
  69. // fontWidth includes 1-dot spacing between numbers
  70. fw := float64(fontWidth) + 1
  71. fh := float64(fontHeight)
  72. nc := float64(ncount)
  73. // Calculate width of a sigle number if we only take into
  74. // account the width
  75. nw := w / nc
  76. // Calculate the number height from this width
  77. nh := nw * fh / fw
  78. // Number height too large?
  79. if nh > h {
  80. // Fit numbers based on height
  81. nh = h
  82. nw = fw / fh * nh
  83. }
  84. // Calculate dot size
  85. img.dotSize = int(nh / fh)
  86. // Save everything, making actual width smaller by 1 dot,
  87. // to account for spacing between numbers
  88. img.numberWidth = int(nw)
  89. img.numberHeight = int(nh) - img.dotSize
  90. }
  91. func (img *CaptchaImage) drawHorizLine(color image.Color, fromX, toX, y int) {
  92. for x := fromX; x <= toX; x++ {
  93. img.Set(x, y, color)
  94. }
  95. }
  96. func (img *CaptchaImage) drawCircle(color image.Color, x, y, radius int) {
  97. f := 1 - radius
  98. dfx := 1
  99. dfy := -2 * radius
  100. xx := 0
  101. yy := radius
  102. img.Set(x, y+radius, color)
  103. img.Set(x, y-radius, color)
  104. img.drawHorizLine(color, x-radius, x+radius, y)
  105. for xx < yy {
  106. if f >= 0 {
  107. yy--
  108. dfy += 2
  109. f += dfy
  110. }
  111. xx++
  112. dfx += 2
  113. f += dfx
  114. img.drawHorizLine(color, x-xx, x+xx, y+yy)
  115. img.drawHorizLine(color, x-xx, x+xx, y-yy)
  116. img.drawHorizLine(color, x-yy, x+yy, y+xx)
  117. img.drawHorizLine(color, x-yy, x+yy, y-xx)
  118. }
  119. }
  120. func min3(x, y, z uint8) (o uint8) {
  121. o = x
  122. if y < o {
  123. o = y
  124. }
  125. if z < o {
  126. o = z
  127. }
  128. return
  129. }
  130. func max3(x, y, z uint8) (o uint8) {
  131. o = x
  132. if y > o {
  133. o = y
  134. }
  135. if z > o {
  136. o = z
  137. }
  138. return
  139. }
  140. func setRandomBrightness(c *image.NRGBAColor, max uint8) {
  141. minc := min3(c.R, c.G, c.B)
  142. maxc := max3(c.R, c.G, c.B)
  143. if maxc > max {
  144. return
  145. }
  146. n := rand.Intn(int(max-maxc)) - int(minc)
  147. c.R = uint8(int(c.R) + n)
  148. c.G = uint8(int(c.G) + n)
  149. c.B = uint8(int(c.B) + n)
  150. }
  151. func rnd(from, to int) int {
  152. return rand.Intn(to+1-from) + from
  153. }
  154. func (img *CaptchaImage) fillWithCircles(n, maxradius int) {
  155. color := img.primaryColor
  156. maxx := img.Bounds().Max.X
  157. maxy := img.Bounds().Max.Y
  158. for i := 0; i < n; i++ {
  159. setRandomBrightness(&color, 255)
  160. r := rnd(1, maxradius)
  161. img.drawCircle(color, rnd(r, maxx-r), rnd(r, maxy-r), r)
  162. }
  163. }
  164. func (img *CaptchaImage) strikeThrough() {
  165. r := 0
  166. maxx := img.Bounds().Max.X
  167. maxy := img.Bounds().Max.Y
  168. y := rnd(maxy/3, maxy-maxy/3)
  169. for x := 0; x < maxx; x += r {
  170. r = rnd(1, img.dotSize/2-1)
  171. y += rnd(-img.dotSize/2, img.dotSize/2)
  172. if y <= 0 || y >= maxy {
  173. y = rnd(maxy/3, maxy-maxy/3)
  174. }
  175. img.drawCircle(img.primaryColor, x, y, r)
  176. }
  177. }
  178. func (img *CaptchaImage) drawNumber(number []byte, x, y int) {
  179. skf := rand.Float64() * float64(rnd(-maxSkew, maxSkew))
  180. xs := float64(x)
  181. minr := img.dotSize / 2 // minumum radius
  182. maxr := img.dotSize/2 + img.dotSize/4 // maximum radius
  183. y += rnd(-minr, minr)
  184. for yy := 0; yy < fontHeight; yy++ {
  185. for xx := 0; xx < fontWidth; xx++ {
  186. if number[yy*fontWidth+xx] != 1 {
  187. continue
  188. }
  189. // introduce random variations
  190. or := rnd(minr, maxr)
  191. ox := x + (xx * img.dotSize) + rnd(0, or/2)
  192. oy := y + (yy * img.dotSize) + rnd(0, or/2)
  193. img.drawCircle(img.primaryColor, ox, oy, or)
  194. }
  195. xs += skf
  196. x = int(xs)
  197. }
  198. }