You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

505 lines
13 KiB

  1. // Copyright 2013 Beego Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. package captcha
  15. import (
  16. "bytes"
  17. "image"
  18. "image/color"
  19. "image/png"
  20. "io"
  21. "math"
  22. )
  23. const (
  24. fontWidth = 11
  25. fontHeight = 18
  26. blackChar = 1
  27. // Standard width and height of a captcha image.
  28. stdWidth = 240
  29. stdHeight = 80
  30. // Maximum absolute skew factor of a single digit.
  31. maxSkew = 0.7
  32. // Number of background circles.
  33. circleCount = 20
  34. )
  35. var font = [][]byte{
  36. { // 0
  37. 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
  38. 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  39. 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
  40. 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
  41. 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
  42. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  43. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  44. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  45. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  46. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  47. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  48. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  49. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  50. 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
  51. 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
  52. 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
  53. 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  54. 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
  55. },
  56. { // 1
  57. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  58. 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
  59. 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
  60. 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  61. 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
  62. 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
  63. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  64. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  65. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  66. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  67. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  68. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  69. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  70. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  71. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  72. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  73. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  74. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  75. },
  76. { // 2
  77. 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
  78. 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  79. 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
  80. 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  81. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  82. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  83. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  84. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  85. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  86. 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
  87. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  88. 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
  89. 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
  90. 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
  91. 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
  92. 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
  93. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  94. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  95. },
  96. { // 3
  97. 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
  98. 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  99. 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
  100. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  101. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  102. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  103. 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
  104. 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
  105. 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  106. 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
  107. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
  108. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  109. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  110. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  111. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
  112. 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
  113. 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  114. 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
  115. },
  116. { // 4
  117. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  118. 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
  119. 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
  120. 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
  121. 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
  122. 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
  123. 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
  124. 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
  125. 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
  126. 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
  127. 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  128. 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  129. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  130. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  131. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  132. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  133. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  134. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  135. },
  136. { // 5
  137. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  138. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  139. 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
  140. 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
  141. 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
  142. 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
  143. 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
  144. 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  145. 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
  146. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
  147. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  148. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  149. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  150. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  151. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
  152. 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
  153. 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  154. 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
  155. },
  156. { // 6
  157. 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
  158. 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
  159. 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
  160. 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
  161. 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
  162. 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  163. 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
  164. 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
  165. 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
  166. 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
  167. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  168. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  169. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  170. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  171. 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
  172. 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
  173. 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  174. 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
  175. },
  176. { // 7
  177. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  178. 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  179. 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
  180. 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  181. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  182. 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
  183. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  184. 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
  185. 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
  186. 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
  187. 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
  188. 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
  189. 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
  190. 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
  191. 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
  192. 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
  193. 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
  194. 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
  195. },
  196. { // 8
  197. 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
  198. 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  199. 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
  200. 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
  201. 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
  202. 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
  203. 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
  204. 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  205. 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
  206. 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
  207. 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
  208. 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
  209. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  210. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  211. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  212. 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
  213. 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  214. 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
  215. },
  216. { // 9
  217. 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
  218. 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
  219. 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
  220. 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  221. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  222. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  223. 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  224. 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
  225. 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
  226. 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
  227. 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
  228. 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
  229. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  230. 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
  231. 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
  232. 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
  233. 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
  234. 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
  235. },
  236. }
  237. type Image struct {
  238. *image.Paletted
  239. numWidth int
  240. numHeight int
  241. dotSize int
  242. }
  243. var prng = &siprng{}
  244. // randIntn returns a pseudorandom non-negative int in range [0, n).
  245. func randIntn(n int) int {
  246. return prng.Intn(n)
  247. }
  248. // randInt returns a pseudorandom int in range [from, to].
  249. func randInt(from, to int) int {
  250. return prng.Intn(to+1-from) + from
  251. }
  252. // randFloat returns a pseudorandom float64 in range [from, to].
  253. func randFloat(from, to float64) float64 {
  254. return (to-from)*prng.Float64() + from
  255. }
  256. func randomPalette(primary color.Palette) color.Palette {
  257. p := make([]color.Color, circleCount+1)
  258. // Transparent color.
  259. p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
  260. // Primary color.
  261. var prim color.RGBA
  262. if len(primary) == 0 {
  263. prim = color.RGBA{
  264. uint8(randIntn(129)),
  265. uint8(randIntn(129)),
  266. uint8(randIntn(129)),
  267. 0xFF,
  268. }
  269. } else {
  270. r, g, b, a := primary[randIntn(len(primary)-1)].RGBA()
  271. prim = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
  272. }
  273. p[1] = prim
  274. // Circle colors.
  275. for i := 2; i <= circleCount; i++ {
  276. p[i] = randomBrightness(prim, 255)
  277. }
  278. return p
  279. }
  280. // NewImage returns a new captcha image of the given width and height with the
  281. // given digits, where each digit must be in range 0-9. The digit's color is
  282. // chosen by random from the colorPalette.
  283. func NewImage(digits []byte, width, height int, colorPalette color.Palette) *Image {
  284. m := new(Image)
  285. m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette(colorPalette))
  286. m.calculateSizes(width, height, len(digits))
  287. // Randomly position captcha inside the image.
  288. maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
  289. maxy := height - m.numHeight - m.dotSize*2
  290. var border int
  291. if width > height {
  292. border = height / 5
  293. } else {
  294. border = width / 5
  295. }
  296. x := randInt(border, maxx-border)
  297. y := randInt(border, maxy-border)
  298. // Draw digits.
  299. for _, n := range digits {
  300. m.drawDigit(font[n], x, y)
  301. x += m.numWidth + m.dotSize
  302. }
  303. // Draw strike-through line.
  304. m.strikeThrough()
  305. // Apply wave distortion.
  306. m.distort(randFloat(5, 10), randFloat(100, 200))
  307. // Fill image with random circles.
  308. m.fillWithCircles(circleCount, m.dotSize)
  309. return m
  310. }
  311. // encodedPNG encodes an image to PNG and returns
  312. // the result as a byte slice.
  313. func (m *Image) encodedPNG() []byte {
  314. var buf bytes.Buffer
  315. if err := png.Encode(&buf, m.Paletted); err != nil {
  316. panic(err.Error())
  317. }
  318. return buf.Bytes()
  319. }
  320. // WriteTo writes captcha image in PNG format into the given writer.
  321. func (m *Image) WriteTo(w io.Writer) (int64, error) {
  322. n, err := w.Write(m.encodedPNG())
  323. return int64(n), err
  324. }
  325. func (m *Image) calculateSizes(width, height, ncount int) {
  326. // Goal: fit all digits inside the image.
  327. var border int
  328. if width > height {
  329. border = height / 4
  330. } else {
  331. border = width / 4
  332. }
  333. // Convert everything to floats for calculations.
  334. w := float64(width - border*2)
  335. h := float64(height - border*2)
  336. // fw takes into account 1-dot spacing between digits.
  337. fw := float64(fontWidth + 1)
  338. fh := float64(fontHeight)
  339. nc := float64(ncount)
  340. // Calculate the width of a single digit taking into account only the
  341. // width of the image.
  342. nw := w / nc
  343. // Calculate the height of a digit from this width.
  344. nh := nw * fh / fw
  345. // Digit too high?
  346. if nh > h {
  347. // Fit digits based on height.
  348. nh = h
  349. nw = fw / fh * nh
  350. }
  351. // Calculate dot size.
  352. m.dotSize = int(nh / fh)
  353. // Save everything, making the actual width smaller by 1 dot to account
  354. // for spacing between digits.
  355. m.numWidth = int(nw) - m.dotSize
  356. m.numHeight = int(nh)
  357. }
  358. func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
  359. for x := fromX; x <= toX; x++ {
  360. m.SetColorIndex(x, y, colorIdx)
  361. }
  362. }
  363. func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
  364. f := 1 - radius
  365. dfx := 1
  366. dfy := -2 * radius
  367. xo := 0
  368. yo := radius
  369. m.SetColorIndex(x, y+radius, colorIdx)
  370. m.SetColorIndex(x, y-radius, colorIdx)
  371. m.drawHorizLine(x-radius, x+radius, y, colorIdx)
  372. for xo < yo {
  373. if f >= 0 {
  374. yo--
  375. dfy += 2
  376. f += dfy
  377. }
  378. xo++
  379. dfx += 2
  380. f += dfx
  381. m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
  382. m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
  383. m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
  384. m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
  385. }
  386. }
  387. func (m *Image) fillWithCircles(n, maxradius int) {
  388. maxx := m.Bounds().Max.X
  389. maxy := m.Bounds().Max.Y
  390. for i := 0; i < n; i++ {
  391. colorIdx := uint8(randInt(1, circleCount-1))
  392. r := randInt(1, maxradius)
  393. m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
  394. }
  395. }
  396. func (m *Image) strikeThrough() {
  397. maxx := m.Bounds().Max.X
  398. maxy := m.Bounds().Max.Y
  399. y := randInt(maxy/3, maxy-maxy/3)
  400. amplitude := randFloat(5, 20)
  401. period := randFloat(80, 180)
  402. dx := 2.0 * math.Pi / period
  403. for x := 0; x < maxx; x++ {
  404. xo := amplitude * math.Cos(float64(y)*dx)
  405. yo := amplitude * math.Sin(float64(x)*dx)
  406. for yn := 0; yn < m.dotSize; yn++ {
  407. r := randInt(0, m.dotSize)
  408. m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
  409. }
  410. }
  411. }
  412. func (m *Image) drawDigit(digit []byte, x, y int) {
  413. skf := randFloat(-maxSkew, maxSkew)
  414. xs := float64(x)
  415. r := m.dotSize / 2
  416. y += randInt(-r, r)
  417. for yo := 0; yo < fontHeight; yo++ {
  418. for xo := 0; xo < fontWidth; xo++ {
  419. if digit[yo*fontWidth+xo] != blackChar {
  420. continue
  421. }
  422. m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
  423. }
  424. xs += skf
  425. x = int(xs)
  426. }
  427. }
  428. func (m *Image) distort(amplude float64, period float64) {
  429. w := m.Bounds().Max.X
  430. h := m.Bounds().Max.Y
  431. oldm := m.Paletted
  432. newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
  433. dx := 2.0 * math.Pi / period
  434. for x := 0; x < w; x++ {
  435. for y := 0; y < h; y++ {
  436. xo := amplude * math.Sin(float64(y)*dx)
  437. yo := amplude * math.Cos(float64(x)*dx)
  438. newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
  439. }
  440. }
  441. m.Paletted = newm
  442. }
  443. func randomBrightness(c color.RGBA, max uint8) color.RGBA {
  444. minc := min3(c.R, c.G, c.B)
  445. maxc := max3(c.R, c.G, c.B)
  446. if maxc > max {
  447. return c
  448. }
  449. n := randIntn(int(max-maxc)) - int(minc)
  450. return color.RGBA{
  451. uint8(int(c.R) + n),
  452. uint8(int(c.G) + n),
  453. uint8(int(c.B) + n),
  454. uint8(c.A),
  455. }
  456. }
  457. func min3(x, y, z uint8) (m uint8) {
  458. m = x
  459. if y < m {
  460. m = y
  461. }
  462. if z < m {
  463. m = z
  464. }
  465. return
  466. }
  467. func max3(x, y, z uint8) (m uint8) {
  468. m = x
  469. if y > m {
  470. m = y
  471. }
  472. if z > m {
  473. m = z
  474. }
  475. return
  476. }