package origin import ( "image" "image/color" "image/draw" "math" "sort" "golang.org/x/image/font" "golang.org/x/image/math/fixed" ) // ==================== 核心结构 ==================== // Context 画布上下文 type Context struct { img *image.RGBA // 底层图像 state *contextState // 当前状态 states []*contextState // 状态栈 path *path // 当前路径 } type contextState struct { fillStyle interface{} // color.Color 或 Gradient strokeStyle interface{} // color.Color 或 Gradient lineWidth float64 globalAlpha float64 transform [6]float64 // 变换矩阵 [a, b, c, d, e, f] fontFace font.Face textAlign string textBaseline string shadowColor color.Color shadowOffsetX float64 shadowOffsetY float64 shadowBlur float64 } type path struct { points []image.Point start image.Point } // ==================== 渐变实现 ==================== // Gradient 渐变接口 type Gradient interface { At(x, y int) color.Color } // LinearGradient 线性渐变 type LinearGradient struct { X0, Y0, X1, Y1 float64 Stops []GradientStop } type GradientStop struct { Offset float64 Color color.Color } func NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient { return &LinearGradient{ X0: x0, Y0: y0, X1: x1, Y1: y1, Stops: []GradientStop{}, } } func (g *LinearGradient) AddColorStop(offset float64, color color.Color) { g.Stops = append(g.Stops, GradientStop{Offset: offset, Color: color}) sort.Slice(g.Stops, func(i, j int) bool { return g.Stops[i].Offset < g.Stops[j].Offset }) } func (g *LinearGradient) At(x, y int) color.Color { if len(g.Stops) == 0 { return color.Transparent } // 计算投影位置 (0-1) dx := g.X1 - g.X0 dy := g.Y1 - g.Y0 lenSq := dx*dx + dy*dy t := 0.0 if lenSq > 0 { t = ((float64(x)-g.X0)*dx + (float64(y)-g.Y0)*dy) / lenSq if t < 0 { t = 0 } else if t > 1 { t = 1 } } // 查找色标区间 var start, end GradientStop for i, stop := range g.Stops { if stop.Offset >= t { if i == 0 { return stop.Color } start = g.Stops[i-1] end = stop break } } if start.Color == nil { return g.Stops[len(g.Stops)-1].Color } // 区间插值 localT := (t - start.Offset) / (end.Offset - start.Offset) return interpolateColor(start.Color, end.Color, localT) } // RadialGradient 径向渐变 type RadialGradient struct { X0, Y0, R0, X1, Y1, R1 float64 Stops []GradientStop } func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient { return &RadialGradient{ X0: x0, Y0: y0, R0: r0, X1: x1, Y1: y1, R1: r1, Stops: []GradientStop{}, } } func (g *RadialGradient) AddColorStop(offset float64, color color.Color) { g.Stops = append(g.Stops, GradientStop{Offset: offset, Color: color}) sort.Slice(g.Stops, func(i, j int) bool { return g.Stops[i].Offset < g.Stops[j].Offset }) } func (g *RadialGradient) At(x, y int) color.Color { if len(g.Stops) == 0 { return color.Transparent } // 计算到焦点的距离 // dx0, dy0 := float64(x)-g.X0, float64(y)-g.Y0 dx1, dy1 := float64(x)-g.X1, float64(y)-g.Y1 // d0 := math.Sqrt(dx0*dx0 + dy0*dy0) d1 := math.Sqrt(dx1*dx1 + dy1*dy1) // 计算梯度值 (0-1) t := (d1 - g.R0) / (g.R1 - g.R0) if t < 0 { t = 0 } else if t > 1 { t = 1 } // 查找色标区间 var start, end GradientStop for i, stop := range g.Stops { if stop.Offset >= t { if i == 0 { return stop.Color } start = g.Stops[i-1] end = stop break } } if start.Color == nil { return g.Stops[len(g.Stops)-1].Color } // 区间插值 localT := (t - start.Offset) / (end.Offset - start.Offset) return interpolateColor(start.Color, end.Color, localT) } // ==================== 画布初始化 ==================== func NewContext(width, height int) *Context { img := image.NewRGBA(image.Rect(0, 0, width, height)) // 白色背景 draw.Draw(img, img.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) initialState := &contextState{ fillStyle: color.Black, strokeStyle: color.Black, lineWidth: 1.0, globalAlpha: 1.0, transform: [6]float64{1, 0, 0, 1, 0, 0}, // 单位矩阵 textAlign: "start", textBaseline: "alphabetic", shadowColor: color.RGBA{0, 0, 0, 128}, shadowOffsetX: 0, shadowOffsetY: 0, shadowBlur: 0, } return &Context{ img: img, state: initialState, states: []*contextState{}, path: &path{}, } } func (c *Context) Image() image.Image { return c.img } // ==================== 状态管理 ==================== func (c *Context) Save() { // 深拷贝当前状态 copyState := *c.state c.states = append(c.states, ©State) } func (c *Context) Restore() { if len(c.states) > 0 { lastIndex := len(c.states) - 1 c.state = c.states[lastIndex] c.states = c.states[:lastIndex] } } // ==================== 变换操作 ==================== func (c *Context) Translate(x, y float64) { c.state.transform[4] += x*c.state.transform[0] + y*c.state.transform[2] c.state.transform[5] += x*c.state.transform[1] + y*c.state.transform[3] } func (c *Context) Rotate(angle float64) { sin, cos := math.Sin(angle), math.Cos(angle) c.Transform(cos, sin, -sin, cos, 0, 0) } func (c *Context) Scale(sx, sy float64) { c.Transform(sx, 0, 0, sy, 0, 0) } func (c *Context) Transform(a, b, cVal, d, e, f float64) { newMatrix := [6]float64{ a*c.state.transform[0] + cVal*c.state.transform[1], b*c.state.transform[0] + d*c.state.transform[1], a*c.state.transform[2] + cVal*c.state.transform[3], b*c.state.transform[2] + d*c.state.transform[3], a*c.state.transform[4] + cVal*c.state.transform[5] + e, b*c.state.transform[4] + d*c.state.transform[5] + f, } c.state.transform = newMatrix } func (c *Context) SetTransform(a, b, c1, d, e, f float64) { c.state.transform = [6]float64{a, b, c1, d, e, f} } // ==================== 路径操作 ==================== func (c *Context) BeginPath() { c.path = &path{} } func (c *Context) MoveTo(x, y float64) { pt := c.transformPoint(x, y) c.path.points = append(c.path.points, pt) c.path.start = pt } func (c *Context) LineTo(x, y float64) { if len(c.path.points) == 0 { c.MoveTo(x, y) return } c.path.points = append(c.path.points, c.transformPoint(x, y)) } func (c *Context) Rect(x, y, width, height float64) { c.MoveTo(x, y) c.LineTo(x+width, y) c.LineTo(x+width, y+height) c.LineTo(x, y+height) c.ClosePath() } func (c *Context) Arc(x, y, radius, startAngle, endAngle float64) { // 确保角度在0-2π之间 for startAngle < 0 { startAngle += 2 * math.Pi } for endAngle < 0 { endAngle += 2 * math.Pi } // 确定步数 angleRange := endAngle - startAngle if angleRange < 0 { angleRange += 2 * math.Pi } steps := int(math.Ceil(angleRange * 10)) // 每弧度10步 for i := 0; i <= steps; i++ { t := float64(i) / float64(steps) angle := startAngle + t*angleRange px := x + radius*math.Cos(angle) py := y + radius*math.Sin(angle) if i == 0 { c.MoveTo(px, py) } else { c.LineTo(px, py) } } } func (c *Context) ClosePath() { if len(c.path.points) > 0 { c.LineTo(float64(c.path.start.X), float64(c.path.start.Y)) } } // ==================== 绘制操作 ==================== func (c *Context) Fill() { if len(c.path.points) < 3 { return } // 应用阴影 if c.state.shadowBlur > 0 || c.state.shadowOffsetX != 0 || c.state.shadowOffsetY != 0 { c.drawShadow(true) } // 创建多边形 poly := make([]image.Point, len(c.path.points)) for i, p := range c.path.points { poly[i] = p } // 绘制填充 switch style := c.state.fillStyle.(type) { case color.Color: fillColor := c.applyAlpha(style) draw.DrawMask(c.img, c.img.Bounds(), image.NewUniform(fillColor), image.Point{}, &filledPolygon{poly}, image.Point{}, draw.Over) case Gradient: draw.DrawMask(c.img, c.img.Bounds(), &gradientImage{grad: style, alpha: c.state.globalAlpha}, image.Point{}, &filledPolygon{poly}, image.Point{}, draw.Over) } } func (c *Context) Stroke() { if len(c.path.points) < 2 { return } // 应用阴影 if c.state.shadowBlur > 0 || c.state.shadowOffsetX != 0 || c.state.shadowOffsetY != 0 { c.drawShadow(false) } // 绘制线段 for i := 0; i < len(c.path.points)-1; i++ { c.drawLine(c.path.points[i], c.path.points[i+1]) } } func (c *Context) FillRect(x, y, width, height float64) { c.BeginPath() c.Rect(x, y, width, height) c.Fill() } func (c *Context) StrokeRect(x, y, width, height float64) { c.BeginPath() c.Rect(x, y, width, height) c.Stroke() } func (c *Context) ClearRect(x, y, width, height float64) { rect := image.Rect( int(x), int(y), int(x+width), int(y+height), ) draw.Draw(c.img, rect, image.Transparent, image.Point{}, draw.Src) } // ==================== 文本渲染 ==================== // SetFont 设置字体 func (c *Context) SetFont(face font.Face) { c.state.fontFace = face } // SetTextAlign 设置文本对齐 func (c *Context) SetTextAlign(align string) { c.state.textAlign = align } // SetTextBaseline 设置文本基线 func (c *Context) SetTextBaseline(baseline string) { c.state.textBaseline = baseline } // FillText 绘制填充文本 func (c *Context) FillText(text string, x, y float64) { if c.state.fontFace == nil { return } // 应用阴影 if c.state.shadowBlur > 0 || c.state.shadowOffsetX != 0 || c.state.shadowOffsetY != 0 { c.drawTextShadow(text, x, y, true) } c.drawText(text, x, y, true) } // StrokeText 绘制描边文本 func (c *Context) StrokeText(text string, x, y float64) { if c.state.fontFace == nil { return } // 应用阴影 if c.state.shadowBlur > 0 || c.state.shadowOffsetX != 0 || c.state.shadowOffsetY != 0 { c.drawTextShadow(text, x, y, false) } c.drawText(text, x, y, false) } // MeasureText 测量文本宽度 func (c *Context) MeasureText(text string) float64 { if c.state.fontFace == nil { return 0 } width := 0 for _, r := range text { aw, _ := c.state.fontFace.GlyphAdvance(r) width += aw.Round() } return float64(width) } // ==================== 样式设置 ==================== func (c *Context) SetFillStyle(style interface{}) { switch s := style.(type) { case color.Color, Gradient: c.state.fillStyle = s } } func (c *Context) SetStrokeStyle(style interface{}) { switch s := style.(type) { case color.Color, Gradient: c.state.strokeStyle = s } } func (c *Context) SetFillColor(color color.Color) { c.state.fillStyle = color } func (c *Context) SetStrokeColor(color color.Color) { c.state.strokeStyle = color } func (c *Context) SetLineWidth(width float64) { c.state.lineWidth = width } func (c *Context) SetGlobalAlpha(alpha float64) { c.state.globalAlpha = math.Max(0, math.Min(1, alpha)) } func (c *Context) SetShadow(offsetX, offsetY, blur float64, color color.Color) { c.state.shadowOffsetX = offsetX c.state.shadowOffsetY = offsetY c.state.shadowBlur = blur c.state.shadowColor = color } // ==================== 辅助函数 ==================== // 变换点坐标 func (c *Context) transformPoint(x, y float64) image.Point { m := c.state.transform tx := m[0]*x + m[2]*y + m[4] ty := m[1]*x + m[3]*y + m[5] return image.Pt(int(tx+0.5), int(ty+0.5)) } // 应用透明度 func (c *Context) applyAlpha(col color.Color) color.Color { if c.state.globalAlpha == 1 { return col } r, g, b, a := col.RGBA() alpha := uint16(float64(a) * c.state.globalAlpha) return color.RGBA64{ uint16(r), uint16(g), uint16(b), uint16(alpha), } } // 颜色插值 func interpolateColor(c1, c2 color.Color, t float64) color.Color { r1, g1, b1, a1 := c1.RGBA() r2, g2, b2, a2 := c2.RGBA() return color.RGBA{ uint8(int(float64(r1>>8)*(1-t) + float64(r2>>8)*t)), uint8(int(float64(g1>>8)*(1-t) + float64(g2>>8)*t)), uint8(int(float64(b1>>8)*(1-t) + float64(b2>>8)*t)), uint8(int(float64(a1>>8)*(1-t) + float64(a2>>8)*t)), } } // 绘制线段 func (c *Context) drawLine(p1, p2 image.Point) { dx := p2.X - p1.X dy := p2.Y - p1.Y steps := int(math.Max(math.Abs(float64(dx)), math.Abs(float64(dy)))) if steps == 0 { return } xStep := float64(dx) / float64(steps) yStep := float64(dy) / float64(steps) x := float64(p1.X) y := float64(p1.Y) for i := 0; i <= steps; i++ { switch style := c.state.strokeStyle.(type) { case color.Color: strokeColor := c.applyAlpha(style) c.img.Set(int(x), int(y), strokeColor) case Gradient: gradCol := style.At(int(x), int(y)) c.img.Set(int(x), int(y), c.applyAlpha(gradCol)) } x += xStep y += yStep } } // 绘制文本 func (c *Context) drawText(text string, x, y float64, fill bool) { pt := c.transformPoint(x, y) // 计算文本宽度用于对齐 textWidth := c.MeasureText(text) // 应用对齐 switch c.state.textAlign { case "center": pt.X -= int(textWidth / 2) case "end", "right": pt.X -= int(textWidth) } // 应用基线 metrics := c.state.fontFace.Metrics() switch c.state.textBaseline { case "top": pt.Y += metrics.Ascent.Round() case "middle": pt.Y += (metrics.Ascent + metrics.Descent).Round() / 2 case "bottom": pt.Y += metrics.Descent.Round() } // 创建文本绘制器 drawer := font.Drawer{ Dst: c.img, Face: c.state.fontFace, Dot: fixed.P(pt.X, pt.Y), } // 设置颜色 if fill { switch style := c.state.fillStyle.(type) { case color.Color: drawer.Src = image.NewUniform(c.applyAlpha(style)) case Gradient: drawer.Src = &gradientImage{grad: style, alpha: c.state.globalAlpha} } } else { // 描边文本 - 简单实现,实际应使用路径 switch style := c.state.strokeStyle.(type) { case color.Color: drawer.Src = image.NewUniform(c.applyAlpha(style)) case Gradient: drawer.Src = &gradientImage{grad: style, alpha: c.state.globalAlpha} } } // 绘制文本 if fill { drawer.DrawString(text) } else { // 简单描边实现 - 实际应用中应使用更高级的路径方法 offset := 1 for dx := -offset; dx <= offset; dx++ { for dy := -offset; dy <= offset; dy++ { if dx != 0 || dy != 0 { drawer.Dot = fixed.P(pt.X+dx, pt.Y+dy) drawer.DrawString(text) } } } } } // 绘制文本阴影 func (c *Context) drawTextShadow(text string, x, y float64, fill bool) { // 保存当前状态 c.Save() // 设置阴影样式 shadowStyle := c.state.shadowColor c.SetFillColor(shadowStyle) c.SetStrokeColor(shadowStyle) c.SetGlobalAlpha(0.5 * c.state.globalAlpha) // 阴影透明度 // 应用阴影偏移 x += c.state.shadowOffsetX y += c.state.shadowOffsetY // 绘制阴影文本 if fill { c.FillText(text, x, y) } else { c.StrokeText(text, x, y) } // 恢复状态 c.Restore() } // 绘制阴影 func (c *Context) drawShadow(fill bool) { // 保存当前状态 c.Save() // 设置阴影样式 shadowStyle := c.state.shadowColor c.SetFillColor(shadowStyle) c.SetStrokeColor(shadowStyle) c.SetGlobalAlpha(0.5 * c.state.globalAlpha) // 阴影透明度 // 应用阴影偏移 c.Translate(c.state.shadowOffsetX, c.state.shadowOffsetY) // 绘制阴影 if fill { c.Fill() } else { c.Stroke() } // 恢复状态 c.Restore() } // ==================== 内部类型 ==================== // 多边形填充 type filledPolygon struct{ points []image.Point } func (p *filledPolygon) ColorModel() color.Model { return color.AlphaModel } func (p *filledPolygon) Bounds() image.Rectangle { return boundingBox(p.points) } func (p *filledPolygon) At(x, y int) color.Color { if pointInPolygon(image.Pt(x, y), p.points) { return color.Alpha{255} } return color.Alpha{0} } // 渐变图像适配器 type gradientImage struct { grad Gradient alpha float64 } func (g *gradientImage) ColorModel() color.Model { return color.RGBAModel } func (g *gradientImage) Bounds() image.Rectangle { return image.Rect(-1e9, -1e9, 1e9, 1e9) } func (g *gradientImage) At(x, y int) color.Color { col := g.grad.At(x, y) if g.alpha == 1 { return col } r, gVal, b, a := col.RGBA() newA := uint16(float64(a) * g.alpha) return color.RGBA64{ uint16(r), uint16(gVal), uint16(b), newA, } } // ==================== 几何辅助函数 ==================== // 计算多边形边界框 func boundingBox(points []image.Point) image.Rectangle { if len(points) == 0 { return image.Rect(0, 0, 0, 0) } minX, minY := points[0].X, points[0].Y maxX, maxY := minX, minY for _, p := range points { if p.X < minX { minX = p.X } if p.X > maxX { maxX = p.X } if p.Y < minY { minY = p.Y } if p.Y > maxY { maxY = p.Y } } return image.Rect(minX, minY, maxX, maxY) } // 判断点是否在多边形内 func pointInPolygon(point image.Point, polygon []image.Point) bool { if len(polygon) < 3 { return false } intersections := 0 j := len(polygon) - 1 for i := 0; i < len(polygon); i++ { if (polygon[i].Y > point.Y) != (polygon[j].Y > point.Y) && point.X < (polygon[j].X-polygon[i].X)*(point.Y-polygon[i].Y)/ (polygon[j].Y-polygon[i].Y)+polygon[i].X { intersections++ } j = i } return intersections%2 != 0 }