848 lines
20 KiB
Go
848 lines
20 KiB
Go
package img
|
||
|
||
import (
|
||
"fmt"
|
||
"image"
|
||
"image/color"
|
||
"math"
|
||
"math/rand"
|
||
"unicode"
|
||
|
||
"github.com/disintegration/imaging"
|
||
"github.com/fogleman/gg"
|
||
"github.com/ssgo/u"
|
||
)
|
||
|
||
type LineCap struct {
|
||
Butt string
|
||
Round string
|
||
Square string
|
||
}
|
||
|
||
var lineCap = LineCap{
|
||
Butt: "butt",
|
||
Round: "round",
|
||
Square: "square",
|
||
}
|
||
|
||
type LineJoin struct {
|
||
Bevel string
|
||
Miter string
|
||
Round string
|
||
}
|
||
|
||
var lineJoin = LineJoin{
|
||
Bevel: "bevel",
|
||
Round: "round",
|
||
}
|
||
|
||
type FillRule struct {
|
||
Winding string
|
||
Evenodd string
|
||
}
|
||
|
||
var fillRule = FillRule{
|
||
Winding: "winding",
|
||
Evenodd: "evenodd",
|
||
}
|
||
|
||
type DrawStyle struct {
|
||
LineColor string
|
||
LineWidth float64
|
||
LineCap string
|
||
LineJoin string
|
||
Dash []float64
|
||
DashOffset float64
|
||
FillColor string
|
||
FillRule string
|
||
ShadowColor string
|
||
ShadowOffset float64
|
||
ShadowBlur float64
|
||
}
|
||
|
||
func (g *Graphics) draw(fn func(offset float64), opt *DrawStyle) {
|
||
if opt == nil {
|
||
opt = &DrawStyle{}
|
||
}
|
||
|
||
needFill := opt.FillColor != ""
|
||
needStroke := !needFill || opt.LineColor != "" || opt.LineWidth >= 0.01
|
||
|
||
// 绘制阴影
|
||
if opt.ShadowColor != "" || (opt.ShadowOffset >= 0.01 || opt.ShadowBlur <= -0.01) {
|
||
if opt.ShadowColor == "" {
|
||
opt.ShadowColor = "#333333"
|
||
}
|
||
if opt.ShadowOffset > -0.01 && opt.ShadowOffset < 0.01 {
|
||
opt.ShadowOffset = 2
|
||
}
|
||
|
||
useBlur := opt.ShadowBlur >= 0.01
|
||
var tmpdc, olddc *gg.Context
|
||
if useBlur {
|
||
bounds := g.dc.Image().Bounds()
|
||
tmpdc = gg.NewContext(bounds.Dx(), bounds.Dy())
|
||
olddc = g.dc
|
||
g.dc = tmpdc
|
||
}
|
||
|
||
fn(opt.ShadowOffset)
|
||
g.dc.SetColor(hexToRGBA(opt.ShadowColor))
|
||
if opt.LineWidth >= 0.01 {
|
||
g.dc.SetLineWidth(opt.LineWidth)
|
||
}
|
||
if needFill {
|
||
g.dc.Fill()
|
||
} else {
|
||
g.dc.Stroke()
|
||
}
|
||
|
||
if needFill {
|
||
g.dc.Fill()
|
||
} else {
|
||
g.dc.Stroke()
|
||
}
|
||
if useBlur {
|
||
|
||
g.dc = olddc
|
||
tmpimg := tmpdc.Image()
|
||
imaging.Blur(tmpimg, opt.ShadowBlur)
|
||
g.dc.DrawImage(tmpimg, 0, 0)
|
||
}
|
||
}
|
||
|
||
// 绘制图形
|
||
fn(0)
|
||
if needFill {
|
||
g.dc.SetColor(hexToRGBA(opt.FillColor))
|
||
if opt.FillRule != "" {
|
||
switch opt.FillRule {
|
||
case fillRule.Winding:
|
||
g.dc.SetFillRule(gg.FillRuleWinding)
|
||
case fillRule.Evenodd:
|
||
g.dc.SetFillRule(gg.FillRuleEvenOdd)
|
||
}
|
||
}
|
||
if needStroke {
|
||
g.dc.FillPreserve()
|
||
} else if needFill {
|
||
g.dc.Fill()
|
||
}
|
||
}
|
||
if needStroke {
|
||
if opt.LineWidth >= 0.01 {
|
||
g.dc.SetLineWidth(opt.LineWidth)
|
||
needStroke = true
|
||
}
|
||
if opt.LineCap != "" {
|
||
switch opt.LineCap {
|
||
case lineCap.Butt:
|
||
g.dc.SetLineCap(gg.LineCapButt)
|
||
case lineCap.Round:
|
||
g.dc.SetLineCap(gg.LineCapRound)
|
||
case lineCap.Square:
|
||
g.dc.SetLineCap(gg.LineCapSquare)
|
||
}
|
||
}
|
||
if opt.LineJoin != "" {
|
||
switch opt.LineJoin {
|
||
case lineJoin.Bevel:
|
||
g.dc.SetLineJoin(gg.LineJoinBevel)
|
||
case lineJoin.Round:
|
||
g.dc.SetLineJoin(gg.LineJoinRound)
|
||
}
|
||
}
|
||
if opt.Dash != nil {
|
||
g.dc.SetDash(opt.Dash...)
|
||
}
|
||
if opt.DashOffset >= 0.01 {
|
||
g.dc.SetDashOffset(opt.DashOffset)
|
||
}
|
||
if opt.LineColor != "" {
|
||
g.SetColor(opt.LineColor)
|
||
} else if g.lastColor != "" {
|
||
g.SetColor(g.lastColor)
|
||
}
|
||
g.dc.Stroke()
|
||
|
||
if opt.Dash != nil || opt.DashOffset >= 0.01 {
|
||
g.dc.SetDash()
|
||
}
|
||
}
|
||
}
|
||
|
||
// DrawRectangle 绘制矩形
|
||
func (g *Graphics) Rect(x, y, w, h float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.DrawRectangle(x+offset, y+offset, w, h)
|
||
}, opt)
|
||
}
|
||
|
||
func (g *Graphics) RoundedRect(x, y, w, h, r float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.DrawRoundedRectangle(x+offset, y+offset, w, h, r)
|
||
}, opt)
|
||
}
|
||
|
||
// Point 绘制点
|
||
func (g *Graphics) Point(x, y float64, c *string) {
|
||
if c != nil && *c != "" {
|
||
g.SetColor(*c)
|
||
}
|
||
g.dc.DrawPoint(x, y, 1)
|
||
g.dc.Stroke()
|
||
}
|
||
|
||
// Line 绘制直线
|
||
func (g *Graphics) Line(x1, y1, x2, y2 float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.MoveTo(x1+offset, y1+offset)
|
||
g.dc.LineTo(x2+offset, y2+offset)
|
||
}, opt)
|
||
}
|
||
|
||
func convertPoints(points []float64) []gg.Point {
|
||
p := make([]gg.Point, 0, len(points)/2)
|
||
for i := 0; i < len(points)-1; i += 2 {
|
||
p = append(p, gg.Point{X: points[i], Y: points[i+1]})
|
||
}
|
||
return p
|
||
}
|
||
|
||
// Lines 绘制多条连续线段
|
||
func (g *Graphics) Lines(points []float64, opt *DrawStyle) {
|
||
pp := convertPoints(points)
|
||
g.draw(func(offset float64) {
|
||
if len(pp) >= 0 {
|
||
g.dc.MoveTo(pp[0].X+offset, pp[0].Y+offset)
|
||
for _, p := range pp[1:] {
|
||
g.dc.LineTo(p.X+offset, p.Y+offset)
|
||
}
|
||
}
|
||
}, opt)
|
||
}
|
||
|
||
// Circle 绘制圆形
|
||
func (g *Graphics) Circle(x, y, r float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.DrawCircle(x+offset, y+offset, r)
|
||
}, opt)
|
||
}
|
||
|
||
// Ellipse 绘制椭圆
|
||
func (g *Graphics) Ellipse(x, y, rx, ry float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.DrawEllipse(x+offset, y+offset, rx, ry)
|
||
}, opt)
|
||
}
|
||
|
||
// Arc 绘制圆弧
|
||
func (g *Graphics) Arc(x, y, r, angle1, angle2 float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.DrawArc(x+offset, y+offset, r, angle1, angle2)
|
||
}, opt)
|
||
}
|
||
|
||
// Sector 绘制扇形
|
||
func (g *Graphics) Sector(x, y, r, angle1, angle2 float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.DrawArc(x+offset, y+offset, r, angle1, angle2)
|
||
g.dc.LineTo(x+offset, y+offset)
|
||
g.dc.LineTo(x+offset+r*math.Cos(angle1), y+offset+r*math.Sin(angle1))
|
||
}, opt)
|
||
}
|
||
|
||
// Ring 绘制圆环
|
||
func (g *Graphics) Ring(x, y, innerRadius, outerRadius float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
// 绘制外圆
|
||
g.dc.DrawCircle(x+offset, y+offset, outerRadius)
|
||
// 绘制内圆(作为"洞")
|
||
g.dc.DrawCircle(x+offset, y+offset, innerRadius)
|
||
g.dc.SetFillRule(gg.FillRuleEvenOdd)
|
||
}, opt)
|
||
}
|
||
|
||
// ArcRing 绘制扇形环
|
||
func (g *Graphics) ArcRing(x, y, innerRadius, outerRadius, angle1, angle2 float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
// 绘制外圆弧
|
||
g.dc.DrawArc(x+offset, y+offset, outerRadius, angle1, angle2)
|
||
// 绘制内圆弧(反向)
|
||
g.dc.DrawArc(x+offset, y+offset, innerRadius, angle2, angle1)
|
||
g.dc.ClosePath()
|
||
}, opt)
|
||
}
|
||
|
||
// Triangle 绘制三角形
|
||
func (g *Graphics) Triangle(x1, y1, x2, y2, x3, y3 float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
g.dc.MoveTo(x1+offset, y1+offset)
|
||
g.dc.LineTo(x2+offset, y2+offset)
|
||
g.dc.LineTo(x3+offset, y3+offset)
|
||
g.dc.ClosePath()
|
||
}, opt)
|
||
}
|
||
|
||
// Polygon 绘制多边形
|
||
func (g *Graphics) Polygon(points []float64, opt *DrawStyle) {
|
||
pp := convertPoints(points)
|
||
g.draw(func(offset float64) {
|
||
if len(pp) > 0 {
|
||
pp := convertPoints(points)
|
||
g.dc.MoveTo(pp[0].X+offset, pp[0].Y+offset)
|
||
for _, p := range pp[1:] {
|
||
g.dc.LineTo(p.X+offset, p.Y+offset)
|
||
}
|
||
g.dc.ClosePath()
|
||
}
|
||
}, opt)
|
||
}
|
||
|
||
// RegularPolygon 绘制正多边形
|
||
func (g *Graphics) RegularPolygon(n int, x, y, r float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
for i := 0; i < n; i++ {
|
||
angle := float64(i)*2*math.Pi/float64(n) - math.Pi/2
|
||
px := x + offset + r*math.Cos(angle)
|
||
py := y + offset + r*math.Sin(angle)
|
||
if i == 0 {
|
||
g.dc.MoveTo(px, py)
|
||
} else {
|
||
g.dc.LineTo(px, py)
|
||
}
|
||
}
|
||
g.dc.ClosePath()
|
||
}, opt)
|
||
}
|
||
|
||
// Star 绘制星形
|
||
func (g *Graphics) Star(n int, x, y, outerRadius, innerRadius float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
for i := 0; i < n*2; i++ {
|
||
angle := float64(i)*math.Pi/float64(n) - math.Pi/2
|
||
r := outerRadius
|
||
if i%2 == 1 {
|
||
r = innerRadius
|
||
}
|
||
px := x + offset + r*math.Cos(angle)
|
||
py := y + offset + r*math.Sin(angle)
|
||
if i == 0 {
|
||
g.dc.MoveTo(px, py)
|
||
} else {
|
||
g.dc.LineTo(px, py)
|
||
}
|
||
}
|
||
g.dc.ClosePath()
|
||
}, opt)
|
||
}
|
||
|
||
// BezierCurve 绘制贝塞尔曲线
|
||
func (g *Graphics) BezierCurve(points []float64, opt *DrawStyle) {
|
||
pp := convertPoints(points)
|
||
g.draw(func(offset float64) {
|
||
if len(pp) >= 4 && len(pp)%3 == 1 {
|
||
g.dc.MoveTo(pp[0].X+offset, pp[0].Y+offset)
|
||
for i := 1; i < len(pp); i += 3 {
|
||
g.dc.CubicTo(
|
||
pp[i].X+offset, pp[i].Y+offset,
|
||
pp[i+1].X+offset, pp[i+1].Y+offset,
|
||
pp[i+2].X+offset, pp[i+2].Y+offset,
|
||
)
|
||
}
|
||
}
|
||
}, opt)
|
||
}
|
||
|
||
// QuadraticCurve 绘制二次贝塞尔曲线
|
||
func (g *Graphics) QuadraticCurve(points []float64, opt *DrawStyle) {
|
||
pp := convertPoints(points)
|
||
g.draw(func(offset float64) {
|
||
if len(pp) >= 3 && len(pp)%2 == 1 {
|
||
g.dc.MoveTo(pp[0].X+offset, pp[0].Y+offset)
|
||
for i := 1; i < len(pp); i += 2 {
|
||
g.dc.QuadraticTo(
|
||
pp[i].X+offset, pp[i].Y+offset,
|
||
pp[i+1].X+offset, pp[i+1].Y+offset,
|
||
)
|
||
}
|
||
}
|
||
}, opt)
|
||
}
|
||
|
||
type PathCmd struct {
|
||
Cmd string
|
||
Args []string
|
||
}
|
||
|
||
type RelativePoint struct {
|
||
X, Y float64
|
||
}
|
||
|
||
func (p *RelativePoint) Parse(cmd string, arg string) gg.Point {
|
||
a := u.SplitWithoutNone(arg, ",")
|
||
if len(a) != 2 {
|
||
a = append(a, "")
|
||
}
|
||
|
||
if unicode.IsLower(rune(cmd[0])) {
|
||
p.X += u.Float64(a[0])
|
||
p.Y += u.Float64(a[1])
|
||
} else {
|
||
if a[0] != "" {
|
||
p.X = u.Float64(a[0])
|
||
}
|
||
if a[1] != "" {
|
||
p.Y = u.Float64(a[1])
|
||
}
|
||
}
|
||
return gg.Point{X: p.X, Y: p.Y}
|
||
}
|
||
|
||
// Path 绘制复杂路径(支持直线和曲线)
|
||
func (g *Graphics) Path(commands string, opt *DrawStyle) {
|
||
a := u.SplitWithoutNone(commands, " ")
|
||
cmds := make([]PathCmd, 0)
|
||
lastCmd := "M"
|
||
args := []string{}
|
||
for _, cmd := range a {
|
||
w1 := cmd[0]
|
||
if (w1 >= 'A' && w1 <= 'Z') || (w1 >= 'a' && w1 <= 'z') {
|
||
if len(args) > 0 {
|
||
// 遇到字母,先处理之前的命令
|
||
cmds = append(cmds, PathCmd{
|
||
Cmd: lastCmd,
|
||
Args: args,
|
||
})
|
||
args = []string{}
|
||
}
|
||
lastCmd = string(w1)
|
||
cmd = cmd[1:]
|
||
}
|
||
|
||
// 遇到结束命令
|
||
if lastCmd == "Z" || lastCmd == "z" {
|
||
cmds = append(cmds, PathCmd{Cmd: lastCmd})
|
||
break
|
||
}
|
||
|
||
// 无参数,继续等待
|
||
if len(cmd) > 0 {
|
||
args = append(args, cmd)
|
||
}
|
||
}
|
||
|
||
// 添加最后一个命令
|
||
if len(args) > 0 || lastCmd == "Z" || lastCmd == "z" {
|
||
cmds = append(cmds, PathCmd{Cmd: lastCmd, Args: args})
|
||
}
|
||
|
||
g.draw(func(offset float64) {
|
||
// 状态跟踪
|
||
p := RelativePoint{}
|
||
var startP *gg.Point
|
||
var prevControl gg.Point
|
||
for _, cmd := range cmds {
|
||
// 绝对/相对模式判断
|
||
switch cmd.Cmd {
|
||
case "M", "m":
|
||
if len(cmd.Args) > 0 {
|
||
pp := p.Parse(cmd.Cmd, cmd.Args[0])
|
||
startP = &pp
|
||
g.dc.MoveTo(pp.X+offset, pp.Y+offset)
|
||
// 如果后面还有参数,则视为隐式的直线命令(L或l)
|
||
for i := 1; i < len(cmd.Args); i++ {
|
||
pp = p.Parse(cmd.Cmd, cmd.Args[i])
|
||
g.dc.LineTo(pp.X+offset, pp.Y+offset)
|
||
}
|
||
}
|
||
case "L", "l":
|
||
for _, arg := range cmd.Args {
|
||
pp := p.Parse(cmd.Cmd, arg)
|
||
g.dc.LineTo(pp.X+offset, pp.Y+offset)
|
||
}
|
||
case "C", "c":
|
||
for i := 0; i < len(cmd.Args)-2; i += 3 {
|
||
pp1 := p.Parse(cmd.Cmd, cmd.Args[i])
|
||
pp2 := p.Parse(cmd.Cmd, cmd.Args[i+1])
|
||
pp3 := p.Parse(cmd.Cmd, cmd.Args[i+2])
|
||
g.dc.CubicTo(
|
||
pp1.X+offset, pp1.Y+offset,
|
||
pp2.X+offset, pp2.Y+offset,
|
||
pp3.X+offset, pp3.Y+offset,
|
||
)
|
||
prevControl = pp2
|
||
}
|
||
|
||
case "S", "s":
|
||
// 简化三次贝塞尔曲线
|
||
for i := 0; i < len(cmd.Args); i += 2 {
|
||
if i+1 >= len(cmd.Args) {
|
||
break
|
||
}
|
||
|
||
// 计算第一个控制点(对称点)
|
||
cp1 := gg.Point{
|
||
X: 2*p.X - prevControl.X,
|
||
Y: 2*p.Y - prevControl.Y,
|
||
}
|
||
|
||
cp2 := p.Parse(cmd.Cmd, cmd.Args[i])
|
||
end := p.Parse(cmd.Cmd, cmd.Args[i+1])
|
||
|
||
g.dc.CubicTo(
|
||
cp1.X+offset, cp1.Y+offset,
|
||
cp2.X+offset, cp2.Y+offset,
|
||
end.X+offset, end.Y+offset,
|
||
)
|
||
|
||
prevControl = cp2
|
||
}
|
||
|
||
case "Q", "q":
|
||
// 二次贝塞尔曲线
|
||
for i := 0; i < len(cmd.Args); i += 2 {
|
||
if i+1 >= len(cmd.Args) {
|
||
break
|
||
}
|
||
cp := p.Parse(cmd.Cmd, cmd.Args[i])
|
||
end := p.Parse(cmd.Cmd, cmd.Args[i+1])
|
||
|
||
// 将二次转换为三次贝塞尔曲线
|
||
qToC1, qToC2 := quadToCubic(p.X, p.Y, cp.X, cp.Y, end.X, end.Y)
|
||
g.dc.CubicTo(
|
||
qToC1.X+offset, qToC1.Y+offset,
|
||
qToC2.X+offset, qToC2.Y+offset,
|
||
end.X+offset, end.Y+offset,
|
||
)
|
||
prevControl = cp
|
||
}
|
||
|
||
case "T", "t":
|
||
// 简化二次贝塞尔曲线
|
||
for _, arg := range cmd.Args {
|
||
// 计算控制点(对称点)
|
||
cp := gg.Point{
|
||
X: 2*p.X - prevControl.X,
|
||
Y: 2*p.Y - prevControl.Y,
|
||
}
|
||
|
||
end := p.Parse(cmd.Cmd, arg)
|
||
|
||
// 将二次转换为三次贝塞尔曲线
|
||
qToC1, qToC2 := quadToCubic(p.X, p.Y, cp.X, cp.Y, end.X, end.Y)
|
||
g.dc.CubicTo(
|
||
qToC1.X+offset, qToC1.Y+offset,
|
||
qToC2.X+offset, qToC2.Y+offset,
|
||
end.X+offset, end.Y+offset,
|
||
)
|
||
|
||
prevControl = cp
|
||
}
|
||
|
||
case "H", "h":
|
||
// 水平线
|
||
for _, arg := range cmd.Args {
|
||
pp := p.Parse(cmd.Cmd, arg+",")
|
||
g.dc.LineTo(pp.X+offset, pp.Y+offset)
|
||
}
|
||
|
||
case "V", "v":
|
||
// 垂直线
|
||
for _, arg := range cmd.Args {
|
||
pp := p.Parse(cmd.Cmd, ","+arg)
|
||
g.dc.LineTo(pp.X+offset, pp.Y+offset)
|
||
}
|
||
|
||
case "Z", "z":
|
||
if startP != nil {
|
||
g.dc.LineTo(startP.X+offset, startP.Y+offset)
|
||
}
|
||
g.dc.ClosePath()
|
||
}
|
||
}
|
||
}, opt)
|
||
}
|
||
|
||
// 辅助函数:二次到三次贝塞尔转换
|
||
func quadToCubic(x0, y0, cx, cy, x1, y1 float64) (gg.Point, gg.Point) {
|
||
return gg.Point{
|
||
X: x0 + (2.0/3.0)*(cx-x0),
|
||
Y: y0 + (2.0/3.0)*(cy-y0),
|
||
}, gg.Point{
|
||
X: x1 + (2.0/3.0)*(cx-x1),
|
||
Y: y1 + (2.0/3.0)*(cy-y1),
|
||
}
|
||
}
|
||
|
||
// Arrow 绘制箭头
|
||
func (g *Graphics) Arrow(x1, y1, x2, y2, headSize float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
// 计算箭头方向
|
||
dx := x2 - x1
|
||
dy := y2 - y1
|
||
angle := math.Atan2(dy, dx)
|
||
length := math.Sqrt(dx*dx + dy*dy)
|
||
|
||
// 绘制线
|
||
g.dc.MoveTo(x1+offset, y1+offset)
|
||
g.dc.LineTo(x2+offset, y2+offset)
|
||
|
||
// 绘制箭头头部
|
||
if headSize > 0 && length > headSize {
|
||
// 箭头头部点1
|
||
x3 := x2 - headSize*math.Cos(angle-math.Pi/6)
|
||
y3 := y2 - headSize*math.Sin(angle-math.Pi/6)
|
||
|
||
// 箭头头部点2
|
||
x4 := x2 - headSize*math.Cos(angle+math.Pi/6)
|
||
y4 := y2 - headSize*math.Sin(angle+math.Pi/6)
|
||
|
||
// 绘制箭头头部
|
||
g.dc.MoveTo(x2+offset, y2+offset)
|
||
g.dc.LineTo(x3+offset, y3+offset)
|
||
g.dc.MoveTo(x2+offset, y2+offset)
|
||
g.dc.LineTo(x4+offset, y4+offset)
|
||
}
|
||
}, opt)
|
||
}
|
||
|
||
// Grid 绘制网格
|
||
func (g *Graphics) Grid(x, y, width, height, step float64, opt *DrawStyle) {
|
||
g.draw(func(offset float64) {
|
||
// 水平线
|
||
for iy := y; iy <= y+height; iy += step {
|
||
g.dc.MoveTo(x+offset, iy+offset)
|
||
g.dc.LineTo(x+width+offset, iy+offset)
|
||
}
|
||
|
||
// 垂直线
|
||
for ix := x; ix <= x+width; ix += step {
|
||
g.dc.MoveTo(ix+offset, y+offset)
|
||
g.dc.LineTo(ix+offset, y+height+offset)
|
||
}
|
||
}, opt)
|
||
}
|
||
|
||
// Cross 绘制十字标记
|
||
func (g *Graphics) Cross(x, y, size float64, opt *DrawStyle) {
|
||
half := size / 2
|
||
g.draw(func(offset float64) {
|
||
// 水平线
|
||
g.dc.MoveTo(x-half+offset, y+offset)
|
||
g.dc.LineTo(x+half+offset, y+offset)
|
||
|
||
// 垂直线
|
||
g.dc.MoveTo(x+offset, y-half+offset)
|
||
g.dc.LineTo(x+offset, y+half+offset)
|
||
}, opt)
|
||
}
|
||
|
||
// RandBG 绘制随机干扰背景(1-10档)
|
||
// 档位说明:
|
||
//
|
||
// 1-3级: 轻微干扰,适合验证码文本前的背景
|
||
// 4-6级: 中等干扰,适合文本后的干扰层
|
||
// 7-10级: 强干扰,有效防止OCR识别
|
||
func (g *Graphics) RandBG(level int) {
|
||
if level < 1 {
|
||
level = 1
|
||
} else if level > 10 {
|
||
level = 10
|
||
}
|
||
|
||
bounds := g.dc.Image().Bounds()
|
||
w, h := bounds.Dx(), bounds.Dy()
|
||
|
||
// 根据级别确定干扰元素数量 - 大幅增加密度
|
||
elements := 30 + level*150 // 30到1530个元素
|
||
|
||
// 绘制多种干扰元素 - 提高不透明度和尺寸
|
||
for i := 0; i < elements; i++ {
|
||
// 随机位置
|
||
x := rand.Float64() * float64(w)
|
||
y := rand.Float64() * float64(h)
|
||
|
||
// 大幅提高透明度 - 减少半透明效果
|
||
minAlpha := 120
|
||
maxAlpha := 230
|
||
if level >= 7 {
|
||
minAlpha = 150
|
||
maxAlpha = 255
|
||
}
|
||
r := uint8(rand.Intn(180))
|
||
gVal := uint8(rand.Intn(180))
|
||
bVal := uint8(rand.Intn(180))
|
||
a := uint8(minAlpha + rand.Intn(maxAlpha-minAlpha))
|
||
color := fmt.Sprintf("#%02X%02X%02X%02X", r, gVal, bVal, a)
|
||
|
||
// 提高元素尺寸上限
|
||
minSize := float64(1)
|
||
maxSize := 7.0 + float64(level)*1.5 // 最大尺寸从10提高到20
|
||
size := rand.Float64()*(maxSize-minSize) + minSize
|
||
|
||
// 线宽也要随级别增加
|
||
lineWidth := 0.5 + rand.Float64()*(0.5+float64(level)*0.3)
|
||
|
||
// 元素选择权重
|
||
elementType := rand.Intn(100)
|
||
|
||
// 增加高等级特殊干扰的概率
|
||
if level >= 7 && rand.Intn(100) < (level-6)*15 {
|
||
elementType = 95 + rand.Intn(5) // 特殊干扰类型
|
||
}
|
||
|
||
switch {
|
||
case elementType < 20: // 20%概率画点
|
||
g.Point(x, y, &color)
|
||
|
||
case elementType < 40: // 20%概率画短线
|
||
angle := rand.Float64() * 2 * math.Pi
|
||
length := 3 + rand.Float64()*float64(level)*3
|
||
x2 := x + math.Cos(angle)*length
|
||
y2 := y + math.Sin(angle)*length
|
||
g.Line(x, y, x2, y2, &DrawStyle{
|
||
LineColor: color,
|
||
LineWidth: lineWidth,
|
||
})
|
||
|
||
case elementType < 60: // 20%概率画小圆
|
||
radius := 0.5 + rand.Float64()*size
|
||
g.Circle(x, y, radius, &DrawStyle{
|
||
LineWidth: lineWidth,
|
||
LineColor: color,
|
||
})
|
||
|
||
case elementType < 80: // 20%概率画小矩形 - 增加比例
|
||
w := 1 + rand.Float64()*size*5
|
||
h := 1 + rand.Float64()*size*3
|
||
g.Rect(x, y, w, h, &DrawStyle{
|
||
LineWidth: lineWidth,
|
||
LineColor: color,
|
||
})
|
||
|
||
case elementType < 95: // 15%概率画微弧
|
||
startAngle := rand.Float64() * 2 * math.Pi
|
||
endAngle := startAngle + math.Pi/2 + rand.Float64()*math.Pi
|
||
g.Arc(x, y, size, startAngle, endAngle, &DrawStyle{
|
||
LineWidth: lineWidth,
|
||
LineColor: color,
|
||
})
|
||
|
||
default: // 5%概率画特殊干扰
|
||
// 随机选择一种高级干扰
|
||
switch rand.Intn(4) {
|
||
case 0: // 复杂多边形
|
||
points := make([]float64, 0, 14)
|
||
for j := 0; j < 7; j++ {
|
||
points = append(points, x+rand.Float64()*size*3, y+rand.Float64()*size*3)
|
||
}
|
||
g.Polygon(points, &DrawStyle{
|
||
LineWidth: lineWidth,
|
||
LineColor: color,
|
||
FillColor: color,
|
||
})
|
||
|
||
case 1: // 交叉网格
|
||
size := 2 + rand.Float64()*size
|
||
g.Line(x-size, y, x+size, y, &DrawStyle{LineColor: color, LineWidth: lineWidth})
|
||
g.Line(x, y-size, x, y+size, &DrawStyle{LineColor: color, LineWidth: lineWidth})
|
||
g.Line(x-size, y-size, x+size, y+size, &DrawStyle{LineColor: color, LineWidth: lineWidth})
|
||
g.Line(x+size, y-size, x-size, y+size, &DrawStyle{LineColor: color, LineWidth: lineWidth})
|
||
|
||
case 2: // 扇形环
|
||
inner := size * 0.3
|
||
outer := size
|
||
start := rand.Float64() * 2 * math.Pi
|
||
end := start + math.Pi/2 + rand.Float64()*math.Pi
|
||
g.ArcRing(x, y, inner, outer, start, end, &DrawStyle{
|
||
LineWidth: lineWidth,
|
||
LineColor: color,
|
||
FillColor: color,
|
||
})
|
||
|
||
case 3: // 星形
|
||
points := 4 + rand.Intn(5)
|
||
inner := size * 0.4
|
||
outer := size
|
||
g.Star(points, x, y, outer, inner, &DrawStyle{
|
||
LineWidth: lineWidth,
|
||
LineColor: color,
|
||
FillColor: color,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 高级干扰特性
|
||
if level >= 7 {
|
||
// 增加高级干扰曲线的数量
|
||
g.drawCurveDisturbance(w, h, level)
|
||
|
||
// 增加噪点干扰
|
||
if level >= 8 {
|
||
g.addNoise(level, w, h)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 绘制干扰曲线 - 修复参数错误
|
||
func (g *Graphics) drawCurveDisturbance(w, h, level int) {
|
||
// 大幅增加曲线数量
|
||
curves := 15 + (level-7)*20
|
||
for i := 0; i < curves; i++ {
|
||
// 使用更不透明的颜色
|
||
r := uint8(rand.Intn(150))
|
||
gVal := uint8(rand.Intn(150))
|
||
bVal := uint8(rand.Intn(150))
|
||
a := uint8(160 + rand.Intn(80))
|
||
color := fmt.Sprintf("#%02X%02X%02X%02X", r, gVal, bVal, a)
|
||
|
||
// 生成控制点 - 避免单个点问题
|
||
points := make([]float64, 0, 8)
|
||
for j := 0; j < 4; j++ {
|
||
points = append(points, rand.Float64()*float64(w))
|
||
points = append(points, rand.Float64()*float64(h))
|
||
}
|
||
|
||
// 绘制曲线 - 使用更宽的线条
|
||
g.BezierCurve(points, &DrawStyle{
|
||
LineColor: color,
|
||
LineWidth: 1.0 + rand.Float64()*3.0,
|
||
})
|
||
}
|
||
}
|
||
|
||
// 添加噪点干扰 - 修复并增强效果
|
||
func (g *Graphics) addNoise(level int, w, h int) {
|
||
// 创建噪点图层
|
||
noiseImg := image.NewRGBA(image.Rect(0, 0, w, h))
|
||
|
||
// 设置噪点密度 - 随等级增加
|
||
density := 0.002 + float64(level-8)*0.003
|
||
|
||
for y := 0; y < h; y++ {
|
||
for x := 0; x < w; x++ {
|
||
if rand.Float64() < density {
|
||
// 随机颜色 - 高对比度
|
||
r := uint8(rand.Intn(200))
|
||
g := uint8(rand.Intn(200))
|
||
b := uint8(rand.Intn(200))
|
||
a := uint8(160 + rand.Intn(80))
|
||
noiseImg.SetRGBA(x, y, color.RGBA{r, g, b, a})
|
||
} else {
|
||
// 透明点
|
||
noiseImg.SetRGBA(x, y, color.RGBA{0, 0, 0, 0})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 应用高斯模糊使噪点更自然
|
||
if level > 8 {
|
||
blurred := imaging.Blur(noiseImg, 0.8+float64(level-8)*0.4)
|
||
g.dc.DrawImage(blurred, 0, 0)
|
||
} else {
|
||
g.dc.DrawImage(noiseImg, 0, 0)
|
||
}
|
||
}
|