vision/canvas.go

153 lines
3.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vision
import (
"bytes"
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
_ "image/png"
"strings"
"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 path == "" {
return nil, fmt.Errorf("path is empty")
}
if !file.Exists(path) {
return nil, fmt.Errorf("file not found: %s", path)
}
data, err := file.ReadBytes(path)
if err != nil {
return nil, err
}
img, _, err := image.Decode(bytes.NewReader(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 := file.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,
}
}