You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
5.9 KiB
259 lines
5.9 KiB
2 years ago
|
package base64Captcha
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/base64"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"github.com/golang/freetype"
|
||
|
"github.com/golang/freetype/truetype"
|
||
|
"golang.org/x/image/font"
|
||
|
"image"
|
||
|
"image/color"
|
||
|
"image/draw"
|
||
|
"image/png"
|
||
|
"io"
|
||
|
"log"
|
||
|
"math"
|
||
|
"math/rand"
|
||
|
)
|
||
|
|
||
|
//ItemChar captcha item of unicode characters
|
||
|
type ItemChar struct {
|
||
|
bgColor color.Color
|
||
|
width int
|
||
|
height int
|
||
|
nrgba *image.NRGBA
|
||
|
}
|
||
|
|
||
|
//NewItemChar creates a captcha item of characters
|
||
|
func NewItemChar(w int, h int, bgColor color.RGBA) *ItemChar {
|
||
|
d := ItemChar{width: w, height: h}
|
||
|
m := image.NewNRGBA(image.Rect(0, 0, w, h))
|
||
|
draw.Draw(m, m.Bounds(), &image.Uniform{bgColor}, image.ZP, draw.Src)
|
||
|
d.nrgba = m
|
||
|
return &d
|
||
|
}
|
||
|
|
||
|
//drawHollowLine draw strong and bold white line.
|
||
|
func (item *ItemChar) drawHollowLine() *ItemChar {
|
||
|
|
||
|
first := item.width / 20
|
||
|
end := first * 19
|
||
|
|
||
|
lineColor := RandLightColor()
|
||
|
|
||
|
x1 := float64(rand.Intn(first))
|
||
|
//y1 := float64(rand.Intn(y)+y);
|
||
|
|
||
|
x2 := float64(rand.Intn(first) + end)
|
||
|
|
||
|
multiple := float64(rand.Intn(5)+3) / float64(5)
|
||
|
if int(multiple*10)%3 == 0 {
|
||
|
multiple = multiple * -1.0
|
||
|
}
|
||
|
|
||
|
w := item.height / 20
|
||
|
|
||
|
for ; x1 < x2; x1++ {
|
||
|
|
||
|
y := math.Sin(x1*math.Pi*multiple/float64(item.width)) * float64(item.height/3)
|
||
|
|
||
|
if multiple < 0 {
|
||
|
y = y + float64(item.height/2)
|
||
|
}
|
||
|
item.nrgba.Set(int(x1), int(y), lineColor)
|
||
|
|
||
|
for i := 0; i <= w; i++ {
|
||
|
item.nrgba.Set(int(x1), int(y)+i, lineColor)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
//drawSineLine draw a sine line.
|
||
|
func (item *ItemChar) drawSineLine() *ItemChar {
|
||
|
var py float64
|
||
|
|
||
|
//振幅
|
||
|
a := rand.Intn(item.height / 2)
|
||
|
|
||
|
//Y轴方向偏移量
|
||
|
b := random(int64(-item.height/4), int64(item.height/4))
|
||
|
|
||
|
//X轴方向偏移量
|
||
|
f := random(int64(-item.height/4), int64(item.height/4))
|
||
|
// 周期
|
||
|
var t float64
|
||
|
if item.height > item.width/2 {
|
||
|
t = random(int64(item.width/2), int64(item.height))
|
||
|
} else if item.height == item.width/2 {
|
||
|
t = float64(item.height)
|
||
|
} else {
|
||
|
t = random(int64(item.height), int64(item.width/2))
|
||
|
}
|
||
|
w := float64((2 * math.Pi) / t)
|
||
|
|
||
|
// 曲线横坐标起始位置
|
||
|
px1 := 0
|
||
|
px2 := int(random(int64(float64(item.width)*0.8), int64(item.width)))
|
||
|
|
||
|
c := RandDeepColor()
|
||
|
|
||
|
for px := px1; px < px2; px++ {
|
||
|
if w != 0 {
|
||
|
py = float64(a)*math.Sin(w*float64(px)+f) + b + (float64(item.width) / float64(5))
|
||
|
i := item.height / 5
|
||
|
for i > 0 {
|
||
|
item.nrgba.Set(px+i, int(py), c)
|
||
|
//fmt.Println(px + i,int(py) )
|
||
|
i--
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
//drawSlimLine draw n slim-random-color lines.
|
||
|
func (item *ItemChar) drawSlimLine(num int) *ItemChar {
|
||
|
|
||
|
first := item.width / 10
|
||
|
end := first * 9
|
||
|
|
||
|
y := item.height / 3
|
||
|
|
||
|
for i := 0; i < num; i++ {
|
||
|
|
||
|
point1 := point{X: rand.Intn(first), Y: rand.Intn(y)}
|
||
|
point2 := point{X: rand.Intn(first) + end, Y: rand.Intn(y)}
|
||
|
|
||
|
if i%2 == 0 {
|
||
|
point1.Y = rand.Intn(y) + y*2
|
||
|
point2.Y = rand.Intn(y)
|
||
|
} else {
|
||
|
point1.Y = rand.Intn(y) + y*(i%2)
|
||
|
point2.Y = rand.Intn(y) + y*2
|
||
|
}
|
||
|
|
||
|
item.drawBeeline(point1, point2, RandDeepColor())
|
||
|
|
||
|
}
|
||
|
return item
|
||
|
}
|
||
|
|
||
|
func (item *ItemChar) drawBeeline(point1 point, point2 point, lineColor color.RGBA) {
|
||
|
dx := math.Abs(float64(point1.X - point2.X))
|
||
|
|
||
|
dy := math.Abs(float64(point2.Y - point1.Y))
|
||
|
sx, sy := 1, 1
|
||
|
if point1.X >= point2.X {
|
||
|
sx = -1
|
||
|
}
|
||
|
if point1.Y >= point2.Y {
|
||
|
sy = -1
|
||
|
}
|
||
|
err := dx - dy
|
||
|
for {
|
||
|
item.nrgba.Set(point1.X, point1.Y, lineColor)
|
||
|
item.nrgba.Set(point1.X+1, point1.Y, lineColor)
|
||
|
item.nrgba.Set(point1.X-1, point1.Y, lineColor)
|
||
|
item.nrgba.Set(point1.X+2, point1.Y, lineColor)
|
||
|
item.nrgba.Set(point1.X-2, point1.Y, lineColor)
|
||
|
if point1.X == point2.X && point1.Y == point2.Y {
|
||
|
return
|
||
|
}
|
||
|
e2 := err * 2
|
||
|
if e2 > -dy {
|
||
|
err -= dy
|
||
|
point1.X += sx
|
||
|
}
|
||
|
if e2 < dx {
|
||
|
err += dx
|
||
|
point1.Y += sy
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (item *ItemChar) drawNoise(noiseText string, fonts []*truetype.Font) error {
|
||
|
|
||
|
c := freetype.NewContext()
|
||
|
c.SetDPI(imageStringDpi)
|
||
|
|
||
|
c.SetClip(item.nrgba.Bounds())
|
||
|
c.SetDst(item.nrgba)
|
||
|
c.SetHinting(font.HintingFull)
|
||
|
rawFontSize := float64(item.height) / (1 + float64(rand.Intn(7))/float64(10))
|
||
|
|
||
|
for _, char := range noiseText {
|
||
|
rw := rand.Intn(item.width)
|
||
|
rh := rand.Intn(item.height)
|
||
|
fontSize := rawFontSize/2 + float64(rand.Intn(5))
|
||
|
c.SetSrc(image.NewUniform(RandLightColor()))
|
||
|
c.SetFontSize(fontSize)
|
||
|
c.SetFont(randFontFrom(fonts))
|
||
|
pt := freetype.Pt(rw, rh)
|
||
|
if _, err := c.DrawString(string(char), pt); err != nil {
|
||
|
log.Println(err)
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
//drawText draw captcha string to image.把文字写入图像验证码
|
||
|
|
||
|
func (item *ItemChar) drawText(text string, fonts []*truetype.Font) error {
|
||
|
c := freetype.NewContext()
|
||
|
c.SetDPI(imageStringDpi)
|
||
|
c.SetClip(item.nrgba.Bounds())
|
||
|
c.SetDst(item.nrgba)
|
||
|
c.SetHinting(font.HintingFull)
|
||
|
|
||
|
if len(text) == 0 {
|
||
|
return errors.New("text must not be empty, there is nothing to draw")
|
||
|
}
|
||
|
|
||
|
fontWidth := item.width / len(text)
|
||
|
|
||
|
for i, s := range text {
|
||
|
fontSize := item.height * (rand.Intn(7) + 7) / 16
|
||
|
c.SetSrc(image.NewUniform(RandDeepColor()))
|
||
|
c.SetFontSize(float64(fontSize))
|
||
|
c.SetFont(randFontFrom(fonts))
|
||
|
x := fontWidth*i + fontWidth/fontSize
|
||
|
y := item.height/2 + fontSize/2 - rand.Intn(item.height/16*3)
|
||
|
pt := freetype.Pt(x, y)
|
||
|
if _, err := c.DrawString(string(s), pt); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
//BinaryEncoding encodes an image to PNG and returns a byte slice.
|
||
|
func (item *ItemChar) BinaryEncoding() []byte {
|
||
|
var buf bytes.Buffer
|
||
|
if err := png.Encode(&buf, item.nrgba); err != nil {
|
||
|
panic(err.Error())
|
||
|
}
|
||
|
return buf.Bytes()
|
||
|
}
|
||
|
|
||
|
// WriteTo writes captcha character in png format into the given io.Writer, and
|
||
|
// returns the number of bytes written and an error if any.
|
||
|
func (item *ItemChar) WriteTo(w io.Writer) (int64, error) {
|
||
|
n, err := w.Write(item.BinaryEncoding())
|
||
|
return int64(n), err
|
||
|
}
|
||
|
|
||
|
// EncodeB64string encodes an image to base64 string
|
||
|
func (item *ItemChar) EncodeB64string() string {
|
||
|
return fmt.Sprintf("data:%s;base64,%s", MimeTypeImage, base64.StdEncoding.EncodeToString(item.BinaryEncoding()))
|
||
|
}
|
||
|
|
||
|
type point struct {
|
||
|
X int
|
||
|
Y int
|
||
|
}
|