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