img/draw.go

848 lines
20 KiB
Go
Raw Normal View History

2025-07-29 16:59:49 +08:00
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)
}
}