package vision import ( "image" "image/color/palette" "image/draw" "image/gif" "apigo.cc/go/file" "github.com/fogleman/gg" ) // Animation 代表一个动画序列 (如 GIF) type Animation struct { Frames []*Canvas Delays []int // 每帧延迟时间 (单位: 1/100 秒) } // NewAnimation 创建一个空动画 func NewAnimation() *Animation { return &Animation{ Frames: make([]*Canvas, 0), Delays: make([]int, 0), } } // AddFrame 添加一帧到动画中 // delay: 延迟时间 (100 = 1秒) func (a *Animation) AddFrame(c *Canvas, delay int) { a.Frames = append(a.Frames, c.Clone()) a.Delays = append(a.Delays, delay) } // SaveGIF 将动画保存为 GIF 文件 func (a *Animation) SaveGIF(path string, loopCount int) error { out := &gif.GIF{ LoopCount: loopCount, } for i, c := range a.Frames { img := c.dc.Image() bounds := img.Bounds() // 1. 使用 Plan9 标准调色板 paletted := image.NewPaletted(bounds, palette.Plan9) // 2. 将图像绘制到调色板图像中 draw.FloydSteinberg.Draw(paletted, bounds, img, image.Point{}) out.Image = append(out.Image, paletted) out.Delay = append(out.Delay, a.Delays[i]) } f, err := file.Create(path) if err != nil { return err } defer f.Close() return gif.EncodeAll(f, out) } // LoadGIF 从文件加载 GIF 动画 func LoadGIF(path string) (*Animation, error) { f, err := file.Open(path) if err != nil { return nil, err } defer f.Close() g, err := gif.DecodeAll(f) if err != nil { return nil, err } anim := NewAnimation() for i, img := range g.Image { canvas := &Canvas{ dc: gg.NewContextForImage(img), } // 注意: GIF 帧可能是增量更新的,这里简化处理 anim.AddFrame(canvas, g.Delay[i]) } return anim, nil }