all repos — captcha @ d88ce81917aa0fc9629bb41df9646ff90edd2250

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

Started work on code cleanup.
Dmitry Chestnykh dmitry@codingrobots.com
Thu, 21 Apr 2011 01:12:31 +0200
commit

d88ce81917aa0fc9629bb41df9646ff90edd2250

parent

9da9f1ada866fcf89e2eb56dd08558585b17be76

6 files changed, 178 insertions(+), 143 deletions(-)

jump to
M MakefileMakefile

@@ -3,7 +3,6 @@

TARG=github.com/dchest/captcha GOFILES=\ captcha.go\ - draw.go\ font.go\ image.go
M captcha.gocaptcha.go

@@ -2,7 +2,6 @@ package captcha

import ( "bytes" - "image/png" "os" "rand" "time"

@@ -14,8 +13,6 @@ "sync"

) const ( - dotSize = 6 - maxSkew = 3 expiration = 2 * 60 // 2 minutes collectNum = 100 // number of items that triggers collection )

@@ -47,7 +44,7 @@ rand.Seed(time.Seconds())

} func randomNumbers() []byte { - n := make([]byte, 6) + n := make([]byte, 3) if _, err := io.ReadFull(crand.Reader, n); err != nil { panic(err) }

@@ -72,14 +69,14 @@ }

return id } -func WriteImage(w io.Writer, id string) os.Error { +func WriteImage(w io.Writer, id string, width, height int) os.Error { store.mu.RLock() defer store.mu.RUnlock() ns, ok := store.ids[id] if !ok { return os.NewError("captcha id not found") } - return png.Encode(w, NewImage(ns)) + return NewImage(ns, width, height).PNGEncode(w) } func Verify(w io.Writer, id string, ns []byte) bool {
M cmd/main.gocmd/main.go

@@ -6,5 +6,6 @@ "os"

) func main() { - captcha.EncodeNewImage(os.Stdout) + img, _ := captcha.NewRandomImage(300, 80) + img.PNGEncode(os.Stdout) }
D draw.go

@@ -1,118 +0,0 @@

-package captcha - -import ( - "image" - "rand" -) - -func drawHorizLine(img *image.NRGBA, color image.Color, fromX, toX, y int) { - for x := fromX; x <= toX; x++ { - img.Set(x, y, color) - } -} - -func drawCircle(img *image.NRGBA, color image.Color, x0, y0, radius int) { - f := 1 - radius - ddFx := 1 - ddFy := -2 * radius - x := 0 - y := radius - - img.Set(x0, y0+radius, color) - img.Set(x0, y0-radius, color) - drawHorizLine(img, color, x0-radius, x0+radius, y0) - - for x < y { - if f >= 0 { - y-- - ddFy += 2 - f += ddFy - } - x++ - ddFx += 2 - f += ddFx - drawHorizLine(img, color, x0-x, x0+x, y0+y) - drawHorizLine(img, color, x0-x, x0+x, y0-y) - drawHorizLine(img, color, x0-y, x0+y, y0+x) - drawHorizLine(img, color, x0-y, x0+y, y0-x) - } -} - - -func min3(x, y, z uint8) (o uint8) { - o = x - if y < o { - o = y - } - if z < o { - o = z - } - return -} - -func max3(x, y, z uint8) (o uint8) { - o = x - if y > o { - o = y - } - if z > o { - o = z - } - return -} - -func setRandomBrightness(c *image.NRGBAColor, max uint8) { - minc := min3(c.R, c.G, c.B) - maxc := max3(c.R, c.G, c.B) - if maxc > max { - return - } - 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) -} - -func fillWithCircles(img *image.NRGBA, color image.NRGBAColor, n, maxradius int) { - maxx := img.Bounds().Max.X - maxy := img.Bounds().Max.Y - for i := 0; i < n; i++ { - setRandomBrightness(&color, 255) - r := rand.Intn(maxradius-1) + 1 - drawCircle(img, color, rand.Intn(maxx-r*2)+r, rand.Intn(maxy-r*2)+r, r) - } -} - -func drawCirclesLine(img *image.NRGBA, color image.Color) { - r := 0 - maxx := img.Bounds().Max.X - maxy := img.Bounds().Max.Y - y := rand.Intn(maxy/2+maxy/3) - rand.Intn(maxy/3) - for x := 0; x < maxx; x += r { - r = rand.Intn(dotSize/2) - y += rand.Intn(3) - 1 - if y <= 0 || y >= maxy { - y = rand.Intn(maxy/2) + rand.Intn(maxy/2) - } - drawCircle(img, color, x, y, r) - } -} - -func drawNumber(img *image.NRGBA, number []byte, x, y int, color image.NRGBAColor) { - skf := rand.Intn(maxSkew) - maxSkew/2 - if skf < 0 { - x -= skf * numberHeight - } - for y0 := 0; y0 < numberHeight; y0++ { - for x0 := 0; x0 < numberWidth; x0++ { - radius := rand.Intn(dotSize/2) + dotSize/2 - addx := rand.Intn(radius / 4) - addy := rand.Intn(radius / 4) - if number[y0*numberWidth+x0] == 1 { - drawCircle(img, color, x+x0*dotSize+dotSize+addx, - y+y0*dotSize+dotSize+addy, radius) - } - } - x += skf - } -}
M font.gofont.go

@@ -1,8 +1,8 @@

package captcha const ( - numberWidth = 5 - numberHeight = 8 + fontWidth = 5 + fontHeight = 8 ) var font = [][]byte{
M image.goimage.go

@@ -8,28 +8,184 @@ "os"

"rand" ) +const ( + maxSkew = 3 +) -func NewImage(numbers []byte) *image.NRGBA { - w := numberWidth * (dotSize + 3) * len(numbers) - h := numberHeight * (dotSize + 5) - img := image.NewNRGBA(w, h) - color := image.NRGBAColor{uint8(rand.Intn(50)), uint8(rand.Intn(50)), uint8(rand.Intn(128)), 0xFF} - fillWithCircles(img, color, 40, 4) - x := rand.Intn(dotSize) - y := 0 - setRandomBrightness(&color, 180) +type CaptchaImage struct { + *image.NRGBA + primaryColor image.NRGBAColor + numberWidth int + dotRadius int +} + +func NewImage(numbers []byte, width, height int) *CaptchaImage { + img := new(CaptchaImage) + img.NRGBA = image.NewNRGBA(width, height) + img.primaryColor = image.NRGBAColor{uint8(rand.Intn(50)), uint8(rand.Intn(50)), uint8(rand.Intn(128)), 0xFF} + //width -= 30 + //height -= 30 + var border int = 0 + // if width < height { + // border = width/4 + // } else { + // border = height/4 + // } + bwidth := width - border*2 + bheight := height - border*2 + //border := 15 + //fullNumberWidth := int(float64(width/len(numbers)) * + // float64(fontWidth) / float64(fontHeight)) + var fullNumberWidth int + if float64(fontWidth)/float64(fontHeight) > float64(bwidth)/float64(bheight) { + fullNumberWidth = bheight / fontHeight * fontWidth + } else { + fullNumberWidth = bwidth / len(numbers) + } + // add spacing + img.numberWidth = fullNumberWidth - fullNumberWidth/fontWidth + // center numbers in image + x := border + y := border + setRandomBrightness(&img.primaryColor, 180) for _, n := range numbers { - y = rand.Intn(dotSize * 4) - drawNumber(img, font[n], x, y, color) - x += dotSize*numberWidth + rand.Intn(maxSkew) + 8 + //y = rand.Intn(dotSize * 4) + img.drawNumber(font[n], x, y) + x += fullNumberWidth } - drawCirclesLine(img, color) + //img.strikeThrough(img.primaryColor) return img } -func EncodeNewImage(w io.Writer) (numbers []byte, err os.Error) { +func NewRandomImage(width, height int) (img *CaptchaImage, numbers []byte) { numbers = randomNumbers() - err = png.Encode(w, NewImage(numbers)) + img = NewImage(numbers, width, height) return } +func (img *CaptchaImage) PNGEncode(w io.Writer) os.Error { + return png.Encode(w, img) +} + +func (img *CaptchaImage) drawHorizLine(color image.Color, fromX, toX, y int) { + for x := fromX; x <= toX; x++ { + img.Set(x, y, color) + } +} + +func (img *CaptchaImage) drawCircle(color image.Color, x, y, radius int) { + 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) + + for xx < yy { + if f >= 0 { + yy-- + dfy += 2 + f += dfy + } + 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) + } +} + + +func min3(x, y, z uint8) (o uint8) { + o = x + if y < o { + o = y + } + if z < o { + o = z + } + return +} + +func max3(x, y, z uint8) (o uint8) { + o = x + if y > o { + o = y + } + if z > o { + o = z + } + return +} + +func setRandomBrightness(c *image.NRGBAColor, max uint8) { + minc := min3(c.R, c.G, c.B) + maxc := max3(c.R, c.G, c.B) + if maxc > max { + return + } + 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) +} + +func rnd(from, to int) int { + return rand.Intn(to+1-from) + from +} + +func (img *CaptchaImage) fillWithCircles(color image.NRGBAColor, n, maxradius int) { + maxx := img.Bounds().Max.X + maxy := img.Bounds().Max.Y + for i := 0; i < n; i++ { + setRandomBrightness(&color, 255) + r := rnd(1, maxradius) + img.drawCircle(color, rnd(r, maxx), rnd(r, maxy), r) + } +} + +// func (img *CaptchaImage) strikeThrough(color image.Color) { +// r := 0 +// maxx := img.Bounds().Max.X +// maxy := img.Bounds().Max.Y +// y := rnd(maxy/3, maxy-maxy/3) +// for x := 0; x < maxx; x += r { +// r = rnd(1, dotSize/2-1) +// y += rnd(-2, 2) +// if y <= 0 || y >= maxy { +// y = rnd(maxy/3, maxy-maxy/3) +// } +// img.drawCircle(color, x, y, r) +// } +// } + +func (img *CaptchaImage) drawNumber(number []byte, x, y int) { + //skf := rand.Intn(maxSkew) - maxSkew/2 + //if skf < 0 { + // x -= skf * numberHeight + //} + d := img.numberWidth / fontWidth // number height is ignored + println(img.numberWidth) + srad := d/2 // standard (minumum) radius + mrad := d //srad + srad/2 // maximum radius + // x += srad + // y += srad + for yy := 0; yy < fontHeight; yy++ { + for xx := 0; xx < fontWidth; xx++ { + if number[yy*fontWidth+xx] != 1 { + continue + } + // introduce random variations + or := srad //rnd(srad, mrad) + ox := x + (xx * mrad) //+ rnd(0, or/2) + oy := y + (yy * mrad) //+ rnd(0, or/2) + img.drawCircle(img.primaryColor, ox, oy, or) + } + //x += skf + } +}