image.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  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() 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. prim := color.RGBA{
  262. uint8(randIntn(129)),
  263. uint8(randIntn(129)),
  264. uint8(randIntn(129)),
  265. 0xFF,
  266. }
  267. p[1] = prim
  268. // Circle colors.
  269. for i := 2; i <= circleCount; i++ {
  270. p[i] = randomBrightness(prim, 255)
  271. }
  272. return p
  273. }
  274. // NewImage returns a new captcha image of the given width and height with the
  275. // given digits, where each digit must be in range 0-9.
  276. func NewImage(digits []byte, width, height int) *Image {
  277. m := new(Image)
  278. m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette())
  279. m.calculateSizes(width, height, len(digits))
  280. // Randomly position captcha inside the image.
  281. maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
  282. maxy := height - m.numHeight - m.dotSize*2
  283. var border int
  284. if width > height {
  285. border = height / 5
  286. } else {
  287. border = width / 5
  288. }
  289. x := randInt(border, maxx-border)
  290. y := randInt(border, maxy-border)
  291. // Draw digits.
  292. for _, n := range digits {
  293. m.drawDigit(font[n], x, y)
  294. x += m.numWidth + m.dotSize
  295. }
  296. // Draw strike-through line.
  297. m.strikeThrough()
  298. // Apply wave distortion.
  299. m.distort(randFloat(5, 10), randFloat(100, 200))
  300. // Fill image with random circles.
  301. m.fillWithCircles(circleCount, m.dotSize)
  302. return m
  303. }
  304. // encodedPNG encodes an image to PNG and returns
  305. // the result as a byte slice.
  306. func (m *Image) encodedPNG() []byte {
  307. var buf bytes.Buffer
  308. if err := png.Encode(&buf, m.Paletted); err != nil {
  309. panic(err.Error())
  310. }
  311. return buf.Bytes()
  312. }
  313. // WriteTo writes captcha image in PNG format into the given writer.
  314. func (m *Image) WriteTo(w io.Writer) (int64, error) {
  315. n, err := w.Write(m.encodedPNG())
  316. return int64(n), err
  317. }
  318. func (m *Image) calculateSizes(width, height, ncount int) {
  319. // Goal: fit all digits inside the image.
  320. var border int
  321. if width > height {
  322. border = height / 4
  323. } else {
  324. border = width / 4
  325. }
  326. // Convert everything to floats for calculations.
  327. w := float64(width - border*2)
  328. h := float64(height - border*2)
  329. // fw takes into account 1-dot spacing between digits.
  330. fw := float64(fontWidth + 1)
  331. fh := float64(fontHeight)
  332. nc := float64(ncount)
  333. // Calculate the width of a single digit taking into account only the
  334. // width of the image.
  335. nw := w / nc
  336. // Calculate the height of a digit from this width.
  337. nh := nw * fh / fw
  338. // Digit too high?
  339. if nh > h {
  340. // Fit digits based on height.
  341. nh = h
  342. nw = fw / fh * nh
  343. }
  344. // Calculate dot size.
  345. m.dotSize = int(nh / fh)
  346. // Save everything, making the actual width smaller by 1 dot to account
  347. // for spacing between digits.
  348. m.numWidth = int(nw) - m.dotSize
  349. m.numHeight = int(nh)
  350. }
  351. func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
  352. for x := fromX; x <= toX; x++ {
  353. m.SetColorIndex(x, y, colorIdx)
  354. }
  355. }
  356. func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
  357. f := 1 - radius
  358. dfx := 1
  359. dfy := -2 * radius
  360. xo := 0
  361. yo := radius
  362. m.SetColorIndex(x, y+radius, colorIdx)
  363. m.SetColorIndex(x, y-radius, colorIdx)
  364. m.drawHorizLine(x-radius, x+radius, y, colorIdx)
  365. for xo < yo {
  366. if f >= 0 {
  367. yo--
  368. dfy += 2
  369. f += dfy
  370. }
  371. xo++
  372. dfx += 2
  373. f += dfx
  374. m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
  375. m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
  376. m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
  377. m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
  378. }
  379. }
  380. func (m *Image) fillWithCircles(n, maxradius int) {
  381. maxx := m.Bounds().Max.X
  382. maxy := m.Bounds().Max.Y
  383. for i := 0; i < n; i++ {
  384. colorIdx := uint8(randInt(1, circleCount-1))
  385. r := randInt(1, maxradius)
  386. m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
  387. }
  388. }
  389. func (m *Image) strikeThrough() {
  390. maxx := m.Bounds().Max.X
  391. maxy := m.Bounds().Max.Y
  392. y := randInt(maxy/3, maxy-maxy/3)
  393. amplitude := randFloat(5, 20)
  394. period := randFloat(80, 180)
  395. dx := 2.0 * math.Pi / period
  396. for x := 0; x < maxx; x++ {
  397. xo := amplitude * math.Cos(float64(y)*dx)
  398. yo := amplitude * math.Sin(float64(x)*dx)
  399. for yn := 0; yn < m.dotSize; yn++ {
  400. r := randInt(0, m.dotSize)
  401. m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
  402. }
  403. }
  404. }
  405. func (m *Image) drawDigit(digit []byte, x, y int) {
  406. skf := randFloat(-maxSkew, maxSkew)
  407. xs := float64(x)
  408. r := m.dotSize / 2
  409. y += randInt(-r, r)
  410. for yo := 0; yo < fontHeight; yo++ {
  411. for xo := 0; xo < fontWidth; xo++ {
  412. if digit[yo*fontWidth+xo] != blackChar {
  413. continue
  414. }
  415. m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
  416. }
  417. xs += skf
  418. x = int(xs)
  419. }
  420. }
  421. func (m *Image) distort(amplude float64, period float64) {
  422. w := m.Bounds().Max.X
  423. h := m.Bounds().Max.Y
  424. oldm := m.Paletted
  425. newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
  426. dx := 2.0 * math.Pi / period
  427. for x := 0; x < w; x++ {
  428. for y := 0; y < h; y++ {
  429. xo := amplude * math.Sin(float64(y)*dx)
  430. yo := amplude * math.Cos(float64(x)*dx)
  431. newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
  432. }
  433. }
  434. m.Paletted = newm
  435. }
  436. func randomBrightness(c color.RGBA, max uint8) color.RGBA {
  437. minc := min3(c.R, c.G, c.B)
  438. maxc := max3(c.R, c.G, c.B)
  439. if maxc > max {
  440. return c
  441. }
  442. n := randIntn(int(max-maxc)) - int(minc)
  443. return color.RGBA{
  444. uint8(int(c.R) + n),
  445. uint8(int(c.G) + n),
  446. uint8(int(c.B) + n),
  447. uint8(c.A),
  448. }
  449. }
  450. func min3(x, y, z uint8) (m uint8) {
  451. m = x
  452. if y < m {
  453. m = y
  454. }
  455. if z < m {
  456. m = z
  457. }
  458. return
  459. }
  460. func max3(x, y, z uint8) (m uint8) {
  461. m = x
  462. if y > m {
  463. m = y
  464. }
  465. if z > m {
  466. m = z
  467. }
  468. return
  469. }