img/draw.go
2025-07-29 16:59:49 +08:00

848 lines
20 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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