image.go 13 KB

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