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.
 
 

258 lines
5.9 KiB

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
}