kuvia2/vendor/github.com/flamego/captcha/internal/captchautil/captcha.go

245 lines
5.7 KiB
Go
Raw Normal View History

2022-01-14 23:09:03 +00:00
// Copyright 2016 Minho. All rights reserved.
// Copyright 2021 Flamego. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captchautil
import (
"image"
"image/color"
"image/draw"
"image/jpeg"
"io"
"math"
"math/rand"
"time"
"github.com/golang/freetype"
"github.com/pkg/errors"
"golang.org/x/image/font"
)
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
type Complexity int
const (
ComplexityLow Complexity = iota
ComplexityMedium
ComplexityHigh
)
// Image is a captcha image.
type Image struct {
width int
height int
dpi float64
nrgba *image.NRGBA
}
type Option func(*Image) error
// CreateImage creates and returns a new image with given width, height, DPI,
// background color and additional options.
func CreateImage(width, height, dpi int, bgColor color.RGBA, opts ...Option) (*Image, error) {
nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
draw.Draw(nrgba, nrgba.Bounds(), &image.Uniform{C: bgColor}, image.Point{}, draw.Src)
img := &Image{
height: height,
width: width,
dpi: float64(dpi),
nrgba: nrgba,
}
for _, opt := range opts {
err := opt(img)
if err != nil {
return nil, err
}
}
return img, nil
}
// Encode encodes the final JPEG image to the given writer.
func (img *Image) Encode(w io.Writer) error {
return jpeg.Encode(w, img.nrgba, &jpeg.Options{Quality: 100})
}
// Noise draws background noise on the image.
func Noise(complex Complexity) Option {
return func(img *Image) error {
density := 18
if complex == ComplexityLow {
density = 28
} else if complex == ComplexityMedium {
density = 18
} else if complex == ComplexityHigh {
density = 8
}
maxSize := (img.height * img.width) / density
for i := 0; i < maxSize; i++ {
rw := r.Intn(img.width)
rh := r.Intn(img.height)
img.nrgba.Set(rw, rh, randomColor())
size := r.Intn(maxSize)
if size%3 == 0 {
img.nrgba.Set(rw+1, rh+1, randomColor())
}
}
return nil
}
}
// TextNoise draws background text noise on the image.
func TextNoise(complex Complexity) Option {
return func(img *Image) error {
density := 1500
if complex == ComplexityLow {
density = 2000
} else if complex == ComplexityMedium {
density = 1500
} else if complex == ComplexityHigh {
density = 1000
}
maxSize := (img.height * img.width) / density
c := freetype.NewContext()
c.SetDPI(img.dpi)
c.SetClip(img.nrgba.Bounds())
c.SetDst(img.nrgba)
c.SetHinting(font.HintingFull)
rawFontSize := float64(img.height) / (1 + float64(r.Intn(7))/float64(10))
for i := 0; i < maxSize; i++ {
text, err := RandomText(1)
if err != nil {
return errors.Wrap(err, "TextNoise: get random text")
}
fontSize := rawFontSize/2 + float64(r.Intn(5))
c.SetSrc(image.NewUniform(RandomLightColor()))
c.SetFontSize(fontSize)
f, err := randFontFamily()
if err != nil {
return errors.Wrap(err, "TextNoise: get random font family")
}
c.SetFont(f)
rw := r.Intn(img.width)
rh := r.Intn(img.height)
pt := freetype.Pt(rw, rh)
_, err = c.DrawString(text, pt)
if err != nil {
return errors.Wrap(err, "TextNoise: draw string")
}
}
return nil
}
}
// Text draws text on the image.
func Text(text string) Option {
return func(img *Image) error {
c := freetype.NewContext()
c.SetDPI(img.dpi)
c.SetClip(img.nrgba.Bounds())
c.SetDst(img.nrgba)
c.SetHinting(font.HintingFull)
fontWidth := img.width / len(text)
for i, s := range text {
fontSize := float64(img.height) / (1 + float64(r.Intn(7))/float64(9))
c.SetSrc(image.NewUniform(randomDeepColor()))
c.SetFontSize(fontSize)
f, err := randFontFamily()
if err != nil {
return errors.Wrap(err, "Text: get random font family")
}
c.SetFont(f)
x := (fontWidth)*i + (fontWidth)/int(fontSize)
y := 5 + r.Intn(img.height/2) + int(fontSize/2)
pt := freetype.Pt(x, y)
_, err = c.DrawString(string(s), pt)
if err != nil {
return errors.Wrap(err, "Text: draw string")
}
}
return nil
}
}
// Border draws border on the image.
func Border(color color.RGBA) Option {
return func(img *Image) error {
for x := 0; x < img.width; x++ {
img.nrgba.Set(x, 0, color)
img.nrgba.Set(x, img.height-1, color)
}
for y := 0; y < img.height; y++ {
img.nrgba.Set(0, y, color)
img.nrgba.Set(img.width-1, y, color)
}
return nil
}
}
// Curve draws a curve on the image.
func Curve() Option {
return func(img *Image) error {
random := func(min, max int64) float64 {
decimal := rand.Float64()
if max <= 0 {
return (float64(rand.Int63n((min*-1)-(max*-1))+(max*-1)) + decimal) * -1
}
if min < 0 && max > 0 {
if rand.Int()%2 == 0 {
return float64(rand.Int63n(max)) + decimal
} else {
return (float64(rand.Int63n(min*-1)) + decimal) * -1
}
}
return float64(rand.Int63n(max-min)+min) + decimal
}
a := r.Intn(img.height / 2) // Amplitude
b := random(int64(-img.height/4), int64(img.height/4)) // Y-axis offset
f := random(int64(-img.height/4), int64(img.height/4)) // X-axis offset
// Period
var t float64
if img.height > img.width/2 {
t = random(int64(img.width/2), int64(img.height))
} else {
t = random(int64(img.height), int64(img.width/2))
}
w := (2 * math.Pi) / t
end := int(random(int64(float64(img.width)*0.8), int64(img.width)))
c := color.RGBA{
R: uint8(r.Intn(150)),
G: uint8(r.Intn(150)),
B: uint8(r.Intn(150)),
A: uint8(255),
}
py := float64(0)
for px := 0; px < end; px++ {
if w != 0 {
py = float64(a)*math.Sin(w*float64(px)+f) + b + (float64(img.width) / float64(5))
i := img.height / 5
for i > 0 {
img.nrgba.Set(px+i, int(py), c)
i--
}
}
}
return nil
}
}