777 lines
17 KiB
Go
777 lines
17 KiB
Go
package canvas
|
|
|
|
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
|
|
}
|