all repos — captcha @ bb39fc5cc9ecb8543238feaf8ded574e94ab03d9

Go package captcha implements generation and verification of image and audio CAPTCHAs.

image.go (view raw)

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