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