all repos — captcha @ 0c4201560e56c676764a62c9f1b5614bf346ec87

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

image: use paletted image.

This simplifies code and makes it faster. It also produces smaller PNGs.

Before:

captcha.BenchmarkNewImage	500	 6198230 ns/op
captcha.BenchmarkImageWriteTo	100	17517110 ns/op   0.14 MB/s

After:

captcha.BenchmarkNewImage	500	 3869968 ns/op
captcha.BenchmarkImageWriteTo	200	 7237604 ns/op   0.23 MB/s

(Core 2 Duo @ 2.26 GHz, MacBook)

Currently the background is white, not transparent, because PNG encoder
doesn't support paletted images with alpha channel.

Submitted CL: http://codereview.appspot.com/4432078

Change alpha to 0x00 once it's accepted to make captcha transparent.
Dmitry Chestnykh dmitry@codingrobots.com
Thu, 28 Apr 2011 20:09:50 +0200
commit

0c4201560e56c676764a62c9f1b5614bf346ec87

parent

fe75d4b6ccdec05c8e3cd271f1815240625c7c2a

1 files changed, 56 insertions(+), 36 deletions(-)

jump to
M image.goimage.go

@@ -16,31 +16,49 @@ StdWidth = 240

StdHeight = 80 // Maximum absolute skew factor of a single digit. maxSkew = 0.7 + // Number of background circles. + circleCount = 20 ) type Image struct { - *image.NRGBA - primaryColor image.NRGBAColor - numWidth int - numHeight int - dotSize int + *image.Paletted + numWidth int + numHeight int + dotSize int } func init() { rand.Seed(time.Seconds()) } -// NewImage returns a new captcha image of the given width and height with the -// given digits, where each digit must be in range 0-9. -func NewImage(digits []byte, width, height int) *Image { - img := new(Image) - img.NRGBA = image.NewNRGBA(width, height) - img.primaryColor = image.NRGBAColor{ +func randomPalette() image.PalettedColorModel { + p := make([]image.Color, circleCount+1) + // Transparent color. + // TODO(dchest). Currently it's white, not transparent, because PNG + // encoder doesn't support paletted images with alpha channel. + // Submitted CL: http://codereview.appspot.com/4432078 Change alpha to + // 0x00 once it's accepted. + p[0] = image.RGBAColor{0xFF, 0xFF, 0xFF, 0xFF} + // Primary color. + prim := image.RGBAColor{ uint8(rand.Intn(129)), uint8(rand.Intn(129)), uint8(rand.Intn(129)), 0xFF, } + p[1] = prim + // Circle colors. + for i := 2; i <= circleCount; i++ { + p[i] = randomBrightness(prim, 255) + } + return p +} + +// NewImage returns a new captcha image of the given width and height with the +// given digits, where each digit must be in range 0-9. +func NewImage(digits []byte, width, height int) *Image { + img := new(Image) + img.Paletted = image.NewPaletted(width, height, randomPalette()) img.calculateSizes(width, height, len(digits)) // Randomly position captcha inside the image. maxx := width - (img.numWidth+img.dotSize)*len(digits) - img.dotSize

@@ -73,7 +91,7 @@ // doesn't report this.

// WriteTo writes captcha image in PNG format into the given writer. func (img *Image) WriteTo(w io.Writer) (int64, os.Error) { - return 0, png.Encode(w, img) + return 0, png.Encode(w, img.Paletted) } func (img *Image) calculateSizes(width, height, ncount int) {

@@ -110,22 +128,22 @@ img.numWidth = int(nw) - img.dotSize

img.numHeight = int(nh) } -func (img *Image) drawHorizLine(color image.Color, fromX, toX, y int) { +func (img *Image) drawHorizLine(fromX, toX, y int, colorIndex uint8) { for x := fromX; x <= toX; x++ { - img.Set(x, y, color) + img.SetColorIndex(x, y, colorIndex) } } -func (img *Image) drawCircle(color image.Color, x, y, radius int) { +func (img *Image) drawCircle(x, y, radius int, colorIndex uint8) { f := 1 - radius dfx := 1 dfy := -2 * radius xx := 0 yy := radius - img.Set(x, y+radius, color) - img.Set(x, y-radius, color) - img.drawHorizLine(color, x-radius, x+radius, y) + img.SetColorIndex(x, y+radius, colorIndex) + img.SetColorIndex(x, y-radius, colorIndex) + img.drawHorizLine(x-radius, x+radius, y, colorIndex) for xx < yy { if f >= 0 {

@@ -136,21 +154,20 @@ }

xx++ dfx += 2 f += dfx - img.drawHorizLine(color, x-xx, x+xx, y+yy) - img.drawHorizLine(color, x-xx, x+xx, y-yy) - img.drawHorizLine(color, x-yy, x+yy, y+xx) - img.drawHorizLine(color, x-yy, x+yy, y-xx) + img.drawHorizLine(x-xx, x+xx, y+yy, colorIndex) + img.drawHorizLine(x-xx, x+xx, y-yy, colorIndex) + img.drawHorizLine(x-yy, x+yy, y+xx, colorIndex) + img.drawHorizLine(x-yy, x+yy, y-xx, colorIndex) } } func (img *Image) fillWithCircles(n, maxradius int) { - color := img.primaryColor maxx := img.Bounds().Max.X maxy := img.Bounds().Max.Y for i := 0; i < n; i++ { - setRandomBrightness(&color, 255) + colorIndex := uint8(rnd(1, circleCount-1)) r := rnd(1, maxradius) - img.drawCircle(color, rnd(r, maxx-r), rnd(r, maxy-r), r) + img.drawCircle(rnd(r, maxx-r), rnd(r, maxy-r), r, colorIndex) } }

@@ -166,7 +183,7 @@ xo := amplitude * math.Cos(float64(y)*dx)

yo := amplitude * math.Sin(float64(x)*dx) for yn := 0; yn < img.dotSize; yn++ { r := rnd(0, img.dotSize) - img.drawCircle(img.primaryColor, x+int(xo), y+int(yo)+(yn*img.dotSize), r/2) + img.drawCircle(x+int(xo), y+int(yo)+(yn*img.dotSize), r/2, 1) } } }

@@ -183,7 +200,7 @@ continue

} ox := x + xx*img.dotSize oy := y + yy*img.dotSize - img.drawCircle(img.primaryColor, ox, oy, r) + img.drawCircle(ox, oy, r, 1) } xs += skf x = int(xs)

@@ -194,30 +211,33 @@ func (img *Image) distort(amplude float64, period float64) {

w := img.Bounds().Max.X h := img.Bounds().Max.Y - oldImg := img.NRGBA - newImg := image.NewNRGBA(w, h) + oldImg := img.Paletted + newImg := image.NewPaletted(w, h, oldImg.Palette) dx := 2.0 * math.Pi / period for x := 0; x < w; x++ { for y := 0; y < h; y++ { ox := amplude * math.Sin(float64(y)*dx) oy := amplude * math.Cos(float64(x)*dx) - newImg.Set(x, y, oldImg.At(x+int(ox), y+int(oy))) + newImg.SetColorIndex(x, y, oldImg.ColorIndexAt(x+int(ox), y+int(oy))) } } - img.NRGBA = newImg + img.Paletted = newImg } -func setRandomBrightness(c *image.NRGBAColor, max uint8) { +func randomBrightness(c image.RGBAColor, max uint8) image.RGBAColor { minc := min3(c.R, c.G, c.B) maxc := max3(c.R, c.G, c.B) if maxc > max { - return + return c } n := rand.Intn(int(max-maxc)) - int(minc) - c.R = uint8(int(c.R) + n) - c.G = uint8(int(c.G) + n) - c.B = uint8(int(c.B) + n) + return image.RGBAColor{ + uint8(int(c.R) + n), + uint8(int(c.G) + n), + uint8(int(c.B) + n), + uint8(c.A), + } } func min3(x, y, z uint8) (o uint8) {