all repos — captcha @ f9f7db1f433c5e6c539629a98e1def6e6be38f79

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