"重构Canvas库结构,完善文档和示例,优化绘图功能实现"
This commit is contained in:
parent
ed591f80b4
commit
a07d5940fc
154
README.md
154
README.md
|
|
@ -1,2 +1,154 @@
|
||||||
# canvas
|
# Canvas
|
||||||
|
|
||||||
|
Canvas 是一个用 Go 语言实现的 2D 绘图库,提供类似于 HTML5 Canvas API 的功能。它允许你在 Go 程序中创建和操作图像,支持基本的绘图操作、变换、渐变和文本渲染。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 基本图形绘制:线条、矩形、圆形、路径
|
||||||
|
- 填充和描边操作
|
||||||
|
- 线性和径向渐变
|
||||||
|
- 文本渲染
|
||||||
|
- 变换操作:平移、旋转、缩放
|
||||||
|
- 状态保存和恢复
|
||||||
|
- 阴影效果
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/yourusername/canvas
|
||||||
|
```
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
以下是一个简单的示例,展示如何使用 Canvas 库创建一个包含矩形、圆形和文本的图像:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
"golang.org/x/image/font/gofont/goregular"
|
||||||
|
"github.com/yourusername/canvas"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 创建一个300x200的画布
|
||||||
|
ctx := canvas.NewContext(300, 200)
|
||||||
|
|
||||||
|
// 设置背景色
|
||||||
|
ctx.SetFillColor(color.RGBA{240, 240, 240, 255})
|
||||||
|
ctx.FillRect(0, 0, 300, 200)
|
||||||
|
|
||||||
|
// 绘制矩形
|
||||||
|
ctx.SetFillColor(color.RGBA{200, 0, 0, 200})
|
||||||
|
ctx.FillRect(20, 20, 100, 80)
|
||||||
|
|
||||||
|
// 绘制圆形
|
||||||
|
ctx.BeginPath()
|
||||||
|
ctx.SetFillColor(color.RGBA{0, 0, 200, 200})
|
||||||
|
ctx.Arc(200, 60, 40, 0, 2*3.14159)
|
||||||
|
ctx.Fill()
|
||||||
|
|
||||||
|
// 创建线性渐变
|
||||||
|
gradient := canvas.NewLinearGradient(20, 120, 280, 180)
|
||||||
|
gradient.AddColorStop(0, color.RGBA{255, 0, 0, 255})
|
||||||
|
gradient.AddColorStop(0.5, color.RGBA{0, 255, 0, 255})
|
||||||
|
gradient.AddColorStop(1, color.RGBA{0, 0, 255, 255})
|
||||||
|
|
||||||
|
// 使用渐变填充矩形
|
||||||
|
ctx.SetFillStyle(gradient)
|
||||||
|
ctx.FillRect(20, 120, 260, 60)
|
||||||
|
|
||||||
|
// 加载字体
|
||||||
|
font, _ := truetype.Parse(goregular.TTF)
|
||||||
|
face := truetype.NewFace(font, &truetype.Options{Size: 20})
|
||||||
|
|
||||||
|
// 设置字体和文本属性
|
||||||
|
ctx.SetFont(face)
|
||||||
|
ctx.SetTextAlign("center")
|
||||||
|
ctx.SetTextBaseline("middle")
|
||||||
|
ctx.SetFillColor(color.RGBA{0, 0, 0, 255})
|
||||||
|
ctx.FillText("Canvas 示例", 150, 30)
|
||||||
|
|
||||||
|
// 保存为PNG图片
|
||||||
|
img := ctx.Image()
|
||||||
|
f, _ := os.Create("example.png")
|
||||||
|
defer f.Close()
|
||||||
|
png.Encode(f, img)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 参考
|
||||||
|
|
||||||
|
### 上下文创建
|
||||||
|
|
||||||
|
- `NewContext(width, height int) *Context` - 创建新的绘图上下文
|
||||||
|
|
||||||
|
### 路径操作
|
||||||
|
|
||||||
|
- `BeginPath()` - 开始新路径
|
||||||
|
- `MoveTo(x, y float64)` - 移动到指定位置
|
||||||
|
- `LineTo(x, y float64)` - 绘制线段到指定位置
|
||||||
|
- `Arc(x, y, radius, startAngle, endAngle float64)` - 绘制圆弧
|
||||||
|
- `Rect(x, y, width, height float64)` - 绘制矩形路径
|
||||||
|
- `ClosePath()` - 闭合路径
|
||||||
|
|
||||||
|
### 绘制操作
|
||||||
|
|
||||||
|
- `Fill()` - 填充当前路径
|
||||||
|
- `Stroke()` - 描边当前路径
|
||||||
|
- `FillRect(x, y, width, height float64)` - 填充矩形
|
||||||
|
- `StrokeRect(x, y, width, height float64)` - 描边矩形
|
||||||
|
- `ClearRect(x, y, width, height float64)` - 清除矩形区域
|
||||||
|
|
||||||
|
### 样式设置
|
||||||
|
|
||||||
|
- `SetFillStyle(style interface{})` - 设置填充样式
|
||||||
|
- `SetStrokeStyle(style interface{})` - 设置描边样式
|
||||||
|
- `SetFillColor(color color.Color)` - 设置填充颜色
|
||||||
|
- `SetStrokeColor(color color.Color)` - 设置描边颜色
|
||||||
|
- `SetLineWidth(width float64)` - 设置线宽
|
||||||
|
- `SetGlobalAlpha(alpha float64)` - 设置全局透明度
|
||||||
|
- `SetShadow(offsetX, offsetY, blur float64, color color.Color)` - 设置阴影
|
||||||
|
|
||||||
|
### 渐变
|
||||||
|
|
||||||
|
- `NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient` - 创建线性渐变
|
||||||
|
- `NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) *RadialGradient` - 创建径向渐变
|
||||||
|
- `AddColorStop(offset float64, color color.Color)` - 添加渐变色标
|
||||||
|
|
||||||
|
### 文本操作
|
||||||
|
|
||||||
|
- `SetFont(face font.Face)` - 设置字体
|
||||||
|
- `SetTextAlign(align string)` - 设置文本对齐方式
|
||||||
|
- `SetTextBaseline(baseline string)` - 设置文本基线
|
||||||
|
- `FillText(text string, x, y float64)` - 绘制填充文本
|
||||||
|
- `StrokeText(text string, x, y float64)` - 绘制描边文本
|
||||||
|
- `MeasureText(text string) float64` - 测量文本宽度
|
||||||
|
|
||||||
|
### 变换操作
|
||||||
|
|
||||||
|
- `Save()` - 保存当前状态
|
||||||
|
- `Restore()` - 恢复上一个状态
|
||||||
|
- `Translate(x, y float64)` - 平移变换
|
||||||
|
- `Rotate(angle float64)` - 旋转变换
|
||||||
|
- `Scale(sx, sy float64)` - 缩放变换
|
||||||
|
- `Transform(a, b, c, d, e, f float64)` - 应用变换矩阵
|
||||||
|
- `SetTransform(a, b, c, d, e, f float64)` - 设置变换矩阵
|
||||||
|
|
||||||
|
### 图像操作
|
||||||
|
|
||||||
|
- `Image() *image.RGBA` - 获取底层图像
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
- `golang.org/x/image/font` - 用于文本渲染
|
||||||
|
- `github.com/golang/freetype/truetype` - 用于字体处理(示例中使用)
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext 创建新的画布上下文
|
||||||
|
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{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image 返回底层图像
|
||||||
|
func (c *Context) Image() image.Image {
|
||||||
|
return c.img
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fill 填充当前路径
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stroke 描边当前路径
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FillRect 填充矩形
|
||||||
|
func (c *Context) FillRect(x, y, width, height float64) {
|
||||||
|
c.BeginPath()
|
||||||
|
c.Rect(x, y, width, height)
|
||||||
|
c.Fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrokeRect 描边矩形
|
||||||
|
func (c *Context) StrokeRect(x, y, width, height float64) {
|
||||||
|
c.BeginPath()
|
||||||
|
c.Rect(x, y, width, height)
|
||||||
|
c.Stroke()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearRect 清除矩形区域
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制线段
|
||||||
|
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) 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()
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
|
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"image/png"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/golang/freetype/truetype"
|
||||||
|
"golang.org/x/image/font/gofont/goregular"
|
||||||
|
|
||||||
|
"git.kingecg.top/kingecg/canvas" // 导入本地canvas包
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 创建一个300x200的画布
|
||||||
|
ctx := canvas.NewContext(300, 200)
|
||||||
|
|
||||||
|
// 设置背景色
|
||||||
|
ctx.SetFillColor(color.RGBA{240, 240, 240, 255})
|
||||||
|
ctx.FillRect(0, 0, 300, 200)
|
||||||
|
|
||||||
|
// 绘制矩形
|
||||||
|
ctx.SetFillColor(color.RGBA{200, 0, 0, 200})
|
||||||
|
ctx.FillRect(20, 20, 100, 80)
|
||||||
|
|
||||||
|
// 绘制圆形
|
||||||
|
ctx.BeginPath()
|
||||||
|
ctx.SetFillColor(color.RGBA{0, 0, 200, 200})
|
||||||
|
ctx.Arc(200, 60, 40, 0, 2*3.14159)
|
||||||
|
ctx.Fill()
|
||||||
|
|
||||||
|
// 创建线性渐变
|
||||||
|
gradient := canvas.NewLinearGradient(20, 120, 280, 180)
|
||||||
|
gradient.AddColorStop(0, color.RGBA{255, 0, 0, 255})
|
||||||
|
gradient.AddColorStop(0.5, color.RGBA{0, 255, 0, 255})
|
||||||
|
gradient.AddColorStop(1, color.RGBA{0, 0, 255, 255})
|
||||||
|
|
||||||
|
// 使用渐变填充矩形
|
||||||
|
ctx.SetFillStyle(gradient)
|
||||||
|
ctx.FillRect(20, 120, 260, 60)
|
||||||
|
|
||||||
|
// 绘制文本
|
||||||
|
// 加载字体
|
||||||
|
font, err := truetype.Parse(goregular.TTF)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("无法解析字体: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建字体face
|
||||||
|
face := truetype.NewFace(font, &truetype.Options{
|
||||||
|
Size: 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置字体和文本属性
|
||||||
|
ctx.SetFont(face)
|
||||||
|
ctx.SetTextAlign("center")
|
||||||
|
ctx.SetTextBaseline("middle")
|
||||||
|
ctx.SetFillColor(color.RGBA{0, 0, 0, 255})
|
||||||
|
ctx.FillText("Canvas 示例", 150, 30)
|
||||||
|
|
||||||
|
// 保存为PNG图片
|
||||||
|
img := ctx.Image()
|
||||||
|
f, err := os.Create("example.png")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("无法创建文件: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err := png.Encode(f, img); err != nil {
|
||||||
|
log.Fatalf("无法编码PNG: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("图像已保存为 example.png")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 变换点坐标
|
||||||
|
func (c *Context) transformPoint(x, y float64) image.Point {
|
||||||
|
// 应用变换矩阵 [a, b, c, d, e, f]
|
||||||
|
// | a c e | | x |
|
||||||
|
// | b d f | * | y |
|
||||||
|
// | 0 0 1 | | 1 |
|
||||||
|
tx := x*c.state.transform[0] + y*c.state.transform[2] + c.state.transform[4]
|
||||||
|
ty := x*c.state.transform[1] + y*c.state.transform[3] + c.state.transform[5]
|
||||||
|
return image.Point{X: int(tx), Y: int(ty)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算两点之间的距离
|
||||||
|
func distance(x1, y1, x2, y2 float64) float64 {
|
||||||
|
dx := x2 - x1
|
||||||
|
dy := y2 - y1
|
||||||
|
return math.Sqrt(dx*dx + dy*dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算贝塞尔曲线点
|
||||||
|
func bezierPoint(t float64, p0, p1, p2, p3 float64) float64 {
|
||||||
|
u := 1 - t
|
||||||
|
tt := t * t
|
||||||
|
uu := u * u
|
||||||
|
uuu := uu * u
|
||||||
|
ttt := tt * t
|
||||||
|
|
||||||
|
// (1-t)^3 * P0 + 3 * (1-t)^2 * t * P1 + 3 * (1-t) * t^2 * P2 + t^3 * P3
|
||||||
|
return uuu*p0 + 3*uu*t*p1 + 3*u*tt*p2 + ttt*p3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算二次贝塞尔曲线点
|
||||||
|
func quadraticPoint(t float64, p0, p1, p2 float64) float64 {
|
||||||
|
u := 1 - t
|
||||||
|
return u*u*p0 + 2*u*t*p1 + t*t*p2
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算椭圆上的点
|
||||||
|
func ellipsePoint(cx, cy, rx, ry, angle float64) (float64, float64) {
|
||||||
|
x := cx + rx*math.Cos(angle)
|
||||||
|
y := cy + ry*math.Sin(angle)
|
||||||
|
return x, y
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算圆上的点
|
||||||
|
func circlePoint(cx, cy, r, angle float64) (float64, float64) {
|
||||||
|
return ellipsePoint(cx, cy, r, r, angle)
|
||||||
|
}
|
||||||
5
go.mod
5
go.mod
|
|
@ -2,4 +2,7 @@ module git.kingecg.top/kingecg/canvas
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require golang.org/x/image v0.28.0
|
require (
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
|
golang.org/x/image v0.28.0
|
||||||
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -1,2 +1,4 @@
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
|
||||||
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Gradient 渐变接口
|
||||||
|
type Gradient interface {
|
||||||
|
At(x, y int) color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// GradientStop 渐变色标
|
||||||
|
type GradientStop struct {
|
||||||
|
Offset float64
|
||||||
|
Color color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinearGradient 线性渐变
|
||||||
|
type LinearGradient struct {
|
||||||
|
X0, Y0, X1, Y1 float64
|
||||||
|
Stops []GradientStop
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLinearGradient 创建线性渐变
|
||||||
|
func NewLinearGradient(x0, y0, x1, y1 float64) *LinearGradient {
|
||||||
|
return &LinearGradient{
|
||||||
|
X0: x0, Y0: y0, X1: x1, Y1: y1,
|
||||||
|
Stops: []GradientStop{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddColorStop 添加色标
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// At 获取指定位置的颜色
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRadialGradient 创建径向渐变
|
||||||
|
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{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddColorStop 添加色标
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// At 获取指定位置的颜色
|
||||||
|
func (g *RadialGradient) At(x, y int) color.Color {
|
||||||
|
if len(g.Stops) == 0 {
|
||||||
|
return color.Transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算到焦点的距离
|
||||||
|
dx1, dy1 := float64(x)-g.X1, float64(y)-g.Y1
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package canvas
|
package origin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
"image"
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// path 路径结构
|
||||||
|
type path struct {
|
||||||
|
points []image.Point
|
||||||
|
start image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginPath 开始新路径
|
||||||
|
func (c *Context) BeginPath() {
|
||||||
|
c.path = &path{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoveTo 移动到指定位置
|
||||||
|
func (c *Context) MoveTo(x, y float64) {
|
||||||
|
pt := c.transformPoint(x, y)
|
||||||
|
c.path.points = append(c.path.points, pt)
|
||||||
|
c.path.start = pt
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineTo 绘制线段到指定位置
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rect 绘制矩形路径
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arc 绘制圆弧路径
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosePath 闭合路径
|
||||||
|
func (c *Context) ClosePath() {
|
||||||
|
if len(c.path.points) > 0 {
|
||||||
|
c.LineTo(float64(c.path.start.X), float64(c.path.start.Y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
// Save 保存当前状态
|
||||||
|
func (c *Context) Save() {
|
||||||
|
// 深拷贝当前状态
|
||||||
|
copyState := *c.state
|
||||||
|
c.states = append(c.states, ©State)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore 恢复上一个状态
|
||||||
|
func (c *Context) Restore() {
|
||||||
|
if len(c.states) > 0 {
|
||||||
|
lastIndex := len(c.states) - 1
|
||||||
|
c.state = c.states[lastIndex]
|
||||||
|
c.states = c.states[:lastIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate 平移变换
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate 旋转变换
|
||||||
|
func (c *Context) Rotate(angle float64) {
|
||||||
|
sin, cos := math.Sin(angle), math.Cos(angle)
|
||||||
|
c.Transform(cos, sin, -sin, cos, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale 缩放变换
|
||||||
|
func (c *Context) Scale(sx, sy float64) {
|
||||||
|
c.Transform(sx, 0, 0, sy, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform 应用变换矩阵
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTransform 设置变换矩阵
|
||||||
|
func (c *Context) SetTransform(a, b, c1, d, e, f float64) {
|
||||||
|
c.state.transform = [6]float64{a, b, c1, d, e, f}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetFillStyle 设置填充样式
|
||||||
|
func (c *Context) SetFillStyle(style interface{}) {
|
||||||
|
switch s := style.(type) {
|
||||||
|
case color.Color, Gradient:
|
||||||
|
c.state.fillStyle = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStrokeStyle 设置描边样式
|
||||||
|
func (c *Context) SetStrokeStyle(style interface{}) {
|
||||||
|
switch s := style.(type) {
|
||||||
|
case color.Color, Gradient:
|
||||||
|
c.state.strokeStyle = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFillColor 设置填充颜色
|
||||||
|
func (c *Context) SetFillColor(color color.Color) {
|
||||||
|
c.state.fillStyle = color
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStrokeColor 设置描边颜色
|
||||||
|
func (c *Context) SetStrokeColor(color color.Color) {
|
||||||
|
c.state.strokeStyle = color
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLineWidth 设置线宽
|
||||||
|
func (c *Context) SetLineWidth(width float64) {
|
||||||
|
c.state.lineWidth = width
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGlobalAlpha 设置全局透明度
|
||||||
|
func (c *Context) SetGlobalAlpha(alpha float64) {
|
||||||
|
c.state.globalAlpha = math.Max(0, math.Min(1, alpha))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetShadow 设置阴影
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"golang.org/x/image/font"
|
||||||
|
"golang.org/x/image/math/fixed"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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) 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()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
package canvas
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 应用全局透明度
|
||||||
|
func (c *Context) applyAlpha(col color.Color) color.Color {
|
||||||
|
if c.state.globalAlpha >= 1.0 {
|
||||||
|
return col
|
||||||
|
}
|
||||||
|
|
||||||
|
r, g, b, a := col.RGBA()
|
||||||
|
a = uint32(float64(a) * c.state.globalAlpha)
|
||||||
|
return color.NRGBA64{
|
||||||
|
R: uint16(r),
|
||||||
|
G: uint16(g),
|
||||||
|
B: uint16(b),
|
||||||
|
A: uint16(a),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 颜色插值
|
||||||
|
func interpolateColor(c1, c2 color.Color, t float64) color.Color {
|
||||||
|
r1, g1, b1, a1 := c1.RGBA()
|
||||||
|
r2, g2, b2, a2 := c2.RGBA()
|
||||||
|
|
||||||
|
return color.RGBA64{
|
||||||
|
R: uint16(float64(r1)*(1-t) + float64(r2)*t),
|
||||||
|
G: uint16(float64(g1)*(1-t) + float64(g2)*t),
|
||||||
|
B: uint16(float64(b1)*(1-t) + float64(b2)*t),
|
||||||
|
A: uint16(float64(a1)*(1-t) + float64(a2)*t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渐变图像
|
||||||
|
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 {
|
||||||
|
c := g.grad.At(x, y)
|
||||||
|
if g.alpha < 1.0 {
|
||||||
|
r, gr, b, a := c.RGBA()
|
||||||
|
a = uint32(float64(a) * g.alpha)
|
||||||
|
return color.NRGBA64{
|
||||||
|
R: uint16(r),
|
||||||
|
G: uint16(gr),
|
||||||
|
B: uint16(b),
|
||||||
|
A: uint16(a),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充多边形
|
||||||
|
type filledPolygon struct {
|
||||||
|
points []image.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *filledPolygon) ColorModel() color.Model {
|
||||||
|
return color.AlphaModel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *filledPolygon) Bounds() image.Rectangle {
|
||||||
|
if len(p.points) == 0 {
|
||||||
|
return image.Rectangle{}
|
||||||
|
}
|
||||||
|
|
||||||
|
minX, minY := p.points[0].X, p.points[0].Y
|
||||||
|
maxX, maxY := minX, minY
|
||||||
|
|
||||||
|
for _, pt := range p.points {
|
||||||
|
if pt.X < minX {
|
||||||
|
minX = pt.X
|
||||||
|
}
|
||||||
|
if pt.X > maxX {
|
||||||
|
maxX = pt.X
|
||||||
|
}
|
||||||
|
if pt.Y < minY {
|
||||||
|
minY = pt.Y
|
||||||
|
}
|
||||||
|
if pt.Y > maxY {
|
||||||
|
maxY = pt.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return image.Rect(minX, minY, maxX+1, maxY+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *filledPolygon) At(x, y int) color.Color {
|
||||||
|
if pointInPolygon(image.Pt(x, y), p.points) {
|
||||||
|
return color.Alpha{A: 255}
|
||||||
|
}
|
||||||
|
return color.Alpha{A: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断点是否在多边形内
|
||||||
|
func pointInPolygon(pt image.Point, poly []image.Point) bool {
|
||||||
|
if len(poly) < 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
inside := false
|
||||||
|
j := len(poly) - 1
|
||||||
|
|
||||||
|
for i := 0; i < len(poly); i++ {
|
||||||
|
if (poly[i].Y > pt.Y) != (poly[j].Y > pt.Y) &&
|
||||||
|
(pt.X < poly[i].X+(poly[j].X-poly[i].X)*(pt.Y-poly[i].Y)/(poly[j].Y-poly[i].Y)) {
|
||||||
|
inside = !inside
|
||||||
|
}
|
||||||
|
j = i
|
||||||
|
}
|
||||||
|
|
||||||
|
return inside
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue