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)
|
|||
|
}
|
|||
|
}
|