vision/canvas.go

151 lines
3.4 KiB
Go
Raw Permalink Normal View History

package vision
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
_ "image/png"
"os"
"strings"
"apigo.cc/go/cast"
"apigo.cc/go/file"
"github.com/fogleman/gg"
"golang.org/x/image/font"
)
// Canvas 代表一个绘图画布,封装了图像处理与绘制能力
type Canvas struct {
dc *gg.Context
bgColor string
lastColor string
lastFont font.Face
}
// New 创建一个新的画布
func New(width, height int, backgroundColor ...string) *Canvas {
img := image.NewRGBA(image.Rect(0, 0, width, height))
dc := gg.NewContextForImage(img)
c := &Canvas{
dc: dc,
}
if len(backgroundColor) > 0 && backgroundColor[0] != "" {
c.bgColor = backgroundColor[0]
c.SetColor(c.bgColor)
c.dc.Clear()
}
return c
}
// Load 从文件加载图像并创建画布
func Load(path string) (*Canvas, error) {
if !file.Exists(path) {
return nil, fmt.Errorf("file not found: %s", path)
}
data, err := file.Read(path)
if err != nil {
return nil, err
}
img, _, err := image.Decode(strings.NewReader(cast.String(data)))
if err != nil {
return nil, fmt.Errorf("decode image failed: %v", err)
}
return &Canvas{
dc: gg.NewContextForImage(img),
}, nil
}
// Save 将画布保存到文件
func Save(c *Canvas, path string, quality ...int) error {
var err error
if strings.HasSuffix(strings.ToLower(path), ".jpg") || strings.HasSuffix(strings.ToLower(path), ".jpeg") {
q := 85
if len(quality) > 0 {
q = quality[0]
}
// gg 没有内置 SaveJPG 到 context我们需要手动编码
f, createErr := os.Create(path)
if createErr != nil {
return createErr
}
defer f.Close()
err = jpeg.Encode(f, c.dc.Image(), &jpeg.Options{Quality: q})
} else {
err = c.dc.SavePNG(path)
}
if err != nil {
return fmt.Errorf("save image failed: %w", err)
}
return nil
}
// Image 返回底层图像
func (c *Canvas) Image() image.Image {
return c.dc.Image()
}
// Width 返回画布宽度
func (c *Canvas) Width() int {
return c.dc.Width()
}
// Height 返回画布高度
func (c *Canvas) Height() int {
return c.dc.Height()
}
// SetColor 设置当前绘图颜色 (支持 hex 格式)
func (c *Canvas) SetColor(hex string) {
c.lastColor = hex
c.dc.SetColor(ParseColor(hex))
}
// Clear 清除指定区域,如果设置了背景色则填充背景色,否则填充透明
func (c *Canvas) Clear(x, y, w, h int) {
if c.bgColor != "" {
c.dc.Push()
c.dc.DrawRectangle(float64(x), float64(y), float64(w), float64(h))
c.dc.SetColor(ParseColor(c.bgColor))
c.dc.Fill()
c.dc.Pop()
return
}
if img, ok := c.dc.Image().(*image.RGBA); ok {
transparent := image.NewUniform(color.RGBA{0, 0, 0, 0})
draw.Draw(img, image.Rect(x, y, x+w, y+h), transparent, image.Point{}, draw.Src)
}
}
// Sub 提取子区域并返回新画布
func (c *Canvas) Sub(x, y, w, h int) *Canvas {
newImg := image.NewRGBA(image.Rect(0, 0, w, h))
draw.Draw(newImg, newImg.Bounds(), c.dc.Image(), image.Pt(x, y), draw.Src)
newDC := gg.NewContextForImage(newImg)
return &Canvas{
dc: newDC,
bgColor: c.bgColor,
lastColor: c.lastColor,
lastFont: c.lastFont,
}
}
// Clone 克隆当前画布
func (c *Canvas) Clone() *Canvas {
bounds := c.dc.Image().Bounds()
newImg := image.NewRGBA(bounds)
draw.Draw(newImg, bounds, c.dc.Image(), bounds.Min, draw.Src)
return &Canvas{
dc: gg.NewContextForImage(newImg),
bgColor: c.bgColor,
lastColor: c.lastColor,
lastFont: c.lastFont,
}
}