canvas/origin/canvas.go

777 lines
17 KiB
Go

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, &copyState)
}
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
}