github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/kmgImage/Captcha.go (about) 1 package kmgImage 2 3 import ( 4 crand "crypto/rand" 5 "image" 6 "image/color" 7 "image/png" 8 "io" 9 "math/rand" 10 "time" 11 ) 12 13 const ( 14 stdWidth = 100 15 stdHeight = 40 16 maxSkew = 2 17 ) 18 const ( 19 fontWidth = 5 20 fontHeight = 8 21 blackChar = 1 22 ) 23 24 var font = [][]byte{ 25 { // 0 26 0, 1, 1, 1, 0, 27 1, 0, 0, 0, 1, 28 1, 0, 0, 0, 1, 29 1, 0, 0, 0, 1, 30 1, 0, 0, 0, 1, 31 1, 0, 0, 0, 1, 32 1, 0, 0, 0, 1, 33 0, 1, 1, 1, 0, 34 }, 35 { // 1 36 0, 0, 1, 0, 0, 37 0, 1, 1, 0, 0, 38 1, 0, 1, 0, 0, 39 0, 0, 1, 0, 0, 40 0, 0, 1, 0, 0, 41 0, 0, 1, 0, 0, 42 0, 0, 1, 0, 0, 43 1, 1, 1, 1, 1, 44 }, 45 { // 2 46 0, 1, 1, 1, 0, 47 1, 0, 0, 0, 1, 48 0, 0, 0, 0, 1, 49 0, 0, 0, 1, 1, 50 0, 1, 1, 0, 0, 51 1, 0, 0, 0, 0, 52 1, 0, 0, 0, 0, 53 1, 1, 1, 1, 1, 54 }, 55 { // 3 56 1, 1, 1, 1, 0, 57 0, 0, 0, 0, 1, 58 0, 0, 0, 1, 0, 59 0, 1, 1, 1, 0, 60 0, 0, 0, 1, 0, 61 0, 0, 0, 0, 1, 62 0, 0, 0, 0, 1, 63 1, 1, 1, 1, 0, 64 }, 65 { // 4 66 1, 0, 0, 1, 0, 67 1, 0, 0, 1, 0, 68 1, 0, 0, 1, 0, 69 1, 0, 0, 1, 0, 70 1, 1, 1, 1, 1, 71 0, 0, 0, 1, 0, 72 0, 0, 0, 1, 0, 73 0, 0, 0, 1, 0, 74 }, 75 { // 5 76 1, 1, 1, 1, 1, 77 1, 0, 0, 0, 0, 78 1, 0, 0, 0, 0, 79 1, 1, 1, 1, 0, 80 0, 0, 0, 0, 1, 81 0, 0, 0, 0, 1, 82 0, 0, 0, 0, 1, 83 1, 1, 1, 1, 0, 84 }, 85 { // 6 86 0, 0, 1, 1, 1, 87 0, 1, 0, 0, 0, 88 1, 0, 0, 0, 0, 89 1, 1, 1, 1, 0, 90 1, 0, 0, 0, 1, 91 1, 0, 0, 0, 1, 92 1, 0, 0, 0, 1, 93 0, 1, 1, 1, 0, 94 }, 95 { // 7 96 1, 1, 1, 1, 1, 97 0, 0, 0, 0, 1, 98 0, 0, 0, 0, 1, 99 0, 0, 0, 1, 0, 100 0, 0, 1, 0, 0, 101 0, 1, 0, 0, 0, 102 0, 1, 0, 0, 0, 103 0, 1, 0, 0, 0, 104 }, 105 { // 8 106 0, 1, 1, 1, 0, 107 1, 0, 0, 0, 1, 108 1, 0, 0, 0, 1, 109 0, 1, 1, 1, 0, 110 1, 0, 0, 0, 1, 111 1, 0, 0, 0, 1, 112 1, 0, 0, 0, 1, 113 0, 1, 1, 1, 0, 114 }, 115 { // 9 116 0, 1, 1, 1, 0, 117 1, 0, 0, 0, 1, 118 1, 0, 0, 0, 1, 119 1, 1, 0, 0, 1, 120 0, 1, 1, 1, 1, 121 0, 0, 0, 0, 1, 122 0, 0, 0, 0, 1, 123 1, 1, 1, 1, 0, 124 }, 125 } 126 127 type Image struct { 128 *image.NRGBA 129 color *color.NRGBA 130 width int //a digit width 131 height int //a digit height 132 dotsize int 133 } 134 135 func init() { 136 rand.Seed(int64(time.Second)) 137 } 138 func NewCaptchaImage(digits [4]int, width, height int) *Image { 139 img := new(Image) 140 r := image.Rect(img.width, img.height, stdWidth, stdHeight) 141 img.NRGBA = image.NewNRGBA(r) 142 img.color = &color.NRGBA{ 143 uint8(rand.Intn(129)), 144 uint8(rand.Intn(129)), 145 uint8(rand.Intn(129)), 146 0xFF, 147 } 148 // Draw background (10 random circles of random brightness) 149 img.calculateSizes(width, height, len(digits)) 150 img.fillWithCircles(10, img.dotsize) 151 maxx := width - (img.width+img.dotsize)*len(digits) - img.dotsize 152 maxy := height - img.height - img.dotsize*2 153 x := rnd(img.dotsize*2, maxx) 154 y := rnd(img.dotsize*2, maxy) 155 // Draw digits. 156 for _, n := range digits { 157 img.drawDigit(font[n], x, y) 158 x += img.width + img.dotsize 159 } 160 // Draw strike-through line. 161 img.strikeThrough() 162 return img 163 } 164 func (img *Image) WriteTo(w io.Writer) (int64, error) { 165 return 0, png.Encode(w, img) 166 } 167 func (img *Image) calculateSizes(width, height, ncount int) { 168 // Goal: fit all digits inside the image. 169 var border int 170 if width > height { 171 border = height / 5 172 } else { 173 border = width / 5 174 } 175 // Convert everything to floats for calculations. 176 w := float64(width - border*2) //268 177 h := float64(height - border*2) //48 178 // fw takes into account 1-dot spacing between digits. 179 fw := float64(fontWidth) + 1 //6 180 fh := float64(fontHeight) //8 181 nc := float64(ncount) //7 182 // Calculate the width of a single digit taking into account only the 183 // width of the image. 184 nw := w / nc //38 185 // Calculate the height of a digit from this width. 186 nh := nw * fh / fw //51 187 // Digit too high? 188 if nh > h { 189 // Fit digits based on height. 190 nh = h //nh = 44 191 nw = fw / fh * nh 192 } 193 // Calculate dot size. 194 img.dotsize = int(nh / fh) 195 // Save everything, making the actual width smaller by 1 dot to account 196 // for spacing between digits. 197 img.width = int(nw) 198 img.height = int(nh) - img.dotsize 199 } 200 func (img *Image) fillWithCircles(n, maxradius int) { 201 color := img.color 202 maxx := img.Bounds().Max.X 203 maxy := img.Bounds().Max.Y 204 for i := 0; i < n; i++ { 205 setRandomBrightness(color, 255) 206 r := rnd(1, maxradius) 207 img.drawCircle(color, rnd(r, maxx-r), rnd(r, maxy-r), r) 208 } 209 } 210 func (img *Image) drawHorizLine(color color.Color, fromX, toX, y int) { 211 for x := fromX; x <= toX; x++ { 212 img.Set(x, y, color) 213 } 214 } 215 func (img *Image) drawCircle(color color.Color, x, y, radius int) { 216 f := 1 - radius 217 dfx := 1 218 dfy := -2 * radius 219 xx := 0 220 yy := radius 221 img.Set(x, y+radius, color) 222 img.Set(x, y-radius, color) 223 img.drawHorizLine(color, x-radius, x+radius, y) 224 for xx < yy { 225 if f >= 0 { 226 yy-- 227 dfy += 2 228 f += dfy 229 } 230 xx++ 231 dfx += 2 232 f += dfx 233 img.drawHorizLine(color, x-xx, x+xx, y+yy) 234 img.drawHorizLine(color, x-xx, x+xx, y-yy) 235 img.drawHorizLine(color, x-yy, x+yy, y+xx) 236 img.drawHorizLine(color, x-yy, x+yy, y-xx) 237 } 238 } 239 func (img *Image) strikeThrough() { 240 r := 0 241 maxx := img.Bounds().Max.X 242 maxy := img.Bounds().Max.Y 243 y := rnd(maxy/3, maxy-maxy/3) 244 for x := 0; x < maxx; x += r { 245 r = rnd(1, img.dotsize/3) 246 y += rnd(-img.dotsize/2, img.dotsize/2) 247 if y <= 0 || y >= maxy { 248 y = rnd(maxy/3, maxy-maxy/3) 249 } 250 img.drawCircle(img.color, x, y, r) 251 } 252 } 253 func (img *Image) drawDigit(digit []byte, x, y int) { 254 skf := rand.Float64() * float64(rnd(-maxSkew, maxSkew)) 255 xs := float64(x) 256 minr := img.dotsize / 2 // minumum radius 257 maxr := img.dotsize/2 + img.dotsize/4 // maximum radius 258 y += rnd(-minr, minr) 259 for yy := 0; yy < fontHeight; yy++ { 260 for xx := 0; xx < fontWidth; xx++ { 261 if digit[yy*fontWidth+xx] != blackChar { 262 continue 263 } 264 // Introduce random variations. 265 or := rnd(minr, maxr) 266 ox := x + (xx * img.dotsize) + rnd(0, or/2) 267 oy := y + (yy * img.dotsize) + rnd(0, or/2) 268 img.drawCircle(img.color, ox, oy, or) 269 } 270 xs += skf 271 x = int(xs) 272 } 273 } 274 func setRandomBrightness(c *color.NRGBA, max uint8) { 275 minc := min3(c.R, c.G, c.B) 276 maxc := max3(c.R, c.G, c.B) 277 if maxc > max { 278 return 279 } 280 n := rand.Intn(int(max-maxc)) - int(minc) 281 c.R = uint8(int(c.R) + n) 282 c.G = uint8(int(c.G) + n) 283 c.B = uint8(int(c.B) + n) 284 } 285 func min3(x, y, z uint8) (o uint8) { 286 o = x 287 if y < o { 288 o = y 289 } 290 if z < o { 291 o = z 292 } 293 return 294 } 295 func max3(x, y, z uint8) (o uint8) { 296 o = x 297 if y > o { 298 o = y 299 } 300 if z > o { 301 o = z 302 } 303 return 304 } 305 306 // rnd returns a random number in range [from, to]. 307 func rnd(from, to int) int { 308 //println(to+1-from) 309 return rand.Intn(to+1-from) + from 310 } 311 312 const ( 313 // Standard length of uniuri string to achive ~95 bits of entropy. 314 StdLen = 16 315 // Length of uniurl string to achive ~119 bits of entropy, closest 316 // to what can be losslessly converted to UUIDv4 (122 bits). 317 UUIDLen = 20 318 ) 319 320 // Standard characters allowed in uniuri string. 321 var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") 322 323 // New returns a new random string of the standard length, consisting of 324 // standard characters. 325 func New() string { 326 return NewLenChars(StdLen, StdChars) 327 } 328 329 // NewLen returns a new random string of the provided length, consisting of 330 // standard characters. 331 func NewLen(length int) string { 332 return NewLenChars(length, StdChars) 333 } 334 335 // NewLenChars returns a new random string of the provided length, consisting 336 // of the provided byte slice of allowed characters (maximum 256). 337 func NewLenChars(length int, chars []byte) string { 338 b := make([]byte, length) 339 r := make([]byte, length+(length/4)) // storage for random bytes. 340 clen := byte(len(chars)) 341 maxrb := byte(256 - (256 % len(chars))) 342 i := 0 343 for { 344 if _, err := io.ReadFull(crand.Reader, r); err != nil { 345 panic("error reading from random source: " + err.Error()) 346 } 347 for _, c := range r { 348 if c >= maxrb { 349 // Skip this number to avoid modulo bias. 350 continue 351 } 352 b[i] = chars[c%clen] 353 i++ 354 if i == length { 355 return string(b) 356 } 357 } 358 } 359 panic("unreachable") 360 }