|
|
@@ -16,31 +16,49 @@ const (
|
|
|
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 @@ func NewImage(digits []byte, width, height int) *Image {
|
|
|
|
|
|
// 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 @@ func (img *Image) calculateSizes(width, height, ncount int) {
|
|
|
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 @@ func (img *Image) drawCircle(color image.Color, x, y, radius int) {
|
|
|
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 @@ func (img *Image) strikeThrough() {
|
|
|
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 @@ func (img *Image) drawDigit(digit []byte, x, y int) {
|
|
|
}
|
|
|
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) {
|