From f5e32fac77c623aee750cc1fde4c41c4f2155284 Mon Sep 17 00:00:00 2001 From: Weilin Shi <934587911@qq.com> Date: Tue, 19 Sep 2017 13:12:03 +0800 Subject: [PATCH] add: hsv for better random color update: readme --- .travis.yml | 4 ++++ README.md | 37 +++++++++++++++++++++++++++++++++++++ captcha.go | 18 ++++++++++-------- example/captcha.png | Bin 0 -> 5565 bytes fonts/gen.go | 6 +++--- hsva.go | 43 +++++++++++++++++++++++++++++++++++++++++++ hsva_test.go | 10 ++++++++++ 7 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 .travis.yml create mode 100644 README.md create mode 100644 example/captcha.png create mode 100644 hsva.go create mode 100644 hsva_test.go diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..a9e5d5f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: go + +go: + - 1.9 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fbeb54 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +> Package captcha provides a simple API for captcha generation + +
+ +[![GoDoc](https://godoc.org/github.com/steambap/captcha?status.svg)](https://godoc.org/github.com/steambap/captcha) +[![Build Status](https://travis-ci.org/steambap/captcha.svg)](https://travis-ci.org/steambap/captcha) + +
+ +## install +``` +go get github.com/steambap/captcha +``` + +## usage +```Go +func handle(w http.ResponseWriter, r *http.Request) { + // create a captcha of 150x50px + data, _ := captcha.New(150, 50) + + // session come from other library such as gorilla/sessions + session.Values["captcha"] = data.Text + session.Save(r, w) + // send image data to client + data.WriteTo(w) +} + +``` + +[documentation](https://godoc.org/github.com/steambap/captcha) | +[example](example/main.go) + +## sample image +![image](example/captcha.png) + +## License +[MIT](LICENSE.md) diff --git a/captcha.go b/captcha.go index 069be28..1716fd2 100644 --- a/captcha.go +++ b/captcha.go @@ -36,8 +36,8 @@ type Options struct { // It defaults to 2. CurveNumber int - width int - height int + width int + height int } func newDefaultOption(width, height int) *Options { @@ -149,7 +149,7 @@ func drawSineCurve(img *image.NRGBA, opts *Options) { if flip { yFlip = -1.0 } - curveColor := randomDarkGray() + curveColor := randomDarkColor() for x1 := xStart; x1 <= xEnd; x1++ { y := math.Sin(math.Pi*angle*float64(x1)/float64(opts.width)) * curveHeight * yFlip @@ -157,10 +157,12 @@ func drawSineCurve(img *image.NRGBA, opts *Options) { } } -func randomDarkGray() color.Gray { - gray := rng.Intn(128) + 20 +func randomDarkColor() hsva { + hue := float64(rng.Intn(361)) / 360 + saturation := 0.6 + rng.Float64() * 0.2 + value := 0.25 + rng.Float64() * 0.2 - return color.Gray{Y: uint8(gray)} + return hsva{h: hue, s:saturation, v:value, a:uint8(255)} } func drawText(text string, img *image.NRGBA, opts *Options) error { @@ -174,10 +176,10 @@ func drawText(text string, img *image.NRGBA, opts *Options) error { fontSpacing := opts.width / len(text) for idx, char := range text { - fontScale := 1 + rng.Float64() * 0.5 + fontScale := 1 + rng.Float64()*0.5 fontSize := float64(opts.height) / fontScale ctx.SetFontSize(fontSize) - ctx.SetSrc(image.NewUniform(randomDarkGray())) + ctx.SetSrc(image.NewUniform(randomDarkColor())) x := fontSpacing*idx + fontSpacing/int(fontSize) y := opts.height/6 + rng.Intn(opts.height/3) + int(fontSize/2) pt := freetype.Pt(x, y) diff --git a/example/captcha.png b/example/captcha.png new file mode 100644 index 0000000000000000000000000000000000000000..abdd5a5a4b19ff9996b4c1ae0f9569933771dfc7 GIT binary patch literal 5565 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 LNkvXXu0mjfW-iOm literal 0 HcmV?d00001 diff --git a/fonts/gen.go b/fonts/gen.go index e50392f..fbdb431 100644 --- a/fonts/gen.go +++ b/fonts/gen.go @@ -1,11 +1,11 @@ package main import ( - "io/ioutil" - "log" "bytes" "fmt" "go/format" + "io/ioutil" + "log" "path/filepath" ) @@ -37,4 +37,4 @@ func main() { if err := ioutil.WriteFile(filepath.Join("../", "font.go"), dst, 0666); err != nil { log.Fatal(err) } -} \ No newline at end of file +} diff --git a/hsva.go b/hsva.go new file mode 100644 index 0000000..3035419 --- /dev/null +++ b/hsva.go @@ -0,0 +1,43 @@ +package captcha + +import "math" + +type hsva struct { + h, s, v float64 + a uint8 +} + +// https://gist.github.com/mjackson/5311256 +func (c hsva) RGBA() (r, g, b, a uint32) { + var i = math.Floor(c.h * 6) + var f = c.h*6 - i + var p = c.v * (1.0 - c.s) + var q = c.v * (1.0 - f*c.s) + var t = c.v * (1 - (1-f)*c.s) + + var red, green, blue float64 + switch int(i) % 6 { + case 0: + red, green, blue = c.v, t, p + case 1: + red, green, blue = q, c.v, p + case 2: + red, green, blue = p, c.v, t + case 3: + red, green, blue = p, q, c.v + case 4: + red, green, blue = t, p, c.v + case 5: + red, green, blue = c.v, p, q + } + + r = uint32(red * 255) + r |= r << 8 + g = uint32(green * 255) + g |= g << 8 + b = uint32(blue * 255) + b |= b << 8 + a = uint32(c.a) + a |= a << 8 + return +} diff --git a/hsva_test.go b/hsva_test.go new file mode 100644 index 0000000..da6b69e --- /dev/null +++ b/hsva_test.go @@ -0,0 +1,10 @@ +package captcha + +import ( + "image/color" + "testing" +) + +func TestHSVAInterface(t *testing.T) { + var _ color.Color = hsva{} +}