Browse Source

Support GIFs as Animation

master 0.0.5
maze 1 year ago
parent
commit
b42032e810
4 changed files with 114 additions and 0 deletions
  1. +54
    -0
      image.go
  2. +60
    -0
      image_test.go
  3. BIN
      testdata/gif/1942.gif
  4. BIN
      testdata/gif/afterburner.gif

+ 54
- 0
image.go View File

@ -11,6 +11,60 @@ import (
"maze.io/x/dmd/bitmap"
)
type gifAnimation struct {
name string
gif *gif.GIF
pos int
}
func (g gifAnimation) Name() string { return g.name }
func (g gifAnimation) IsMask() bool { return false }
func (g gifAnimation) Size() (width, height int) { return g.gif.Config.Width, g.gif.Config.Height }
func (g gifAnimation) ClockInFront() bool { return false }
func (g gifAnimation) ClockOffset() image.Point { return image.Point{} }
func (g gifAnimation) Len() int { return len(g.gif.Image) }
func (g gifAnimation) Duration() (total time.Duration) {
for _, delay := range g.gif.Delay {
total += time.Duration(delay) * 10 * time.Millisecond
}
return
}
func (g gifAnimation) NextFrame() (image.Image, time.Duration, error) {
if g.pos+1 >= len(g.gif.Image) {
return nil, 0, io.EOF
}
pos := g.pos
g.pos++
return g.gif.Image[pos], time.Duration(g.gif.Delay[pos]) * 10 * time.Millisecond, nil
}
func (g *gifAnimation) SeekFrame(pos int) error {
if pos < 0 || pos >= len(g.gif.Image) {
return ErrSeek
}
g.pos = pos
return nil
}
func DecodeGIF(r io.Reader) (Animation, error) {
g, err := gif.DecodeAll(r)
if err != nil {
return nil, err
}
var name string
if n, ok := r.(namer); ok {
name = n.Name()
}
return &gifAnimation{
name: name,
gif: g,
}, nil
}
func EncodeToGIF(w io.Writer, a Animation) error {
g := &gif.GIF{
Config: image.Config{},


+ 60
- 0
image_test.go View File

@ -0,0 +1,60 @@
package dmd
import (
"os"
"path/filepath"
"testing"
"time"
)
func TestDecodeGIF(t *testing.T) {
tests := []struct {
Name string
Width, Height int
Len int
Duration time.Duration
}{
{
Name: "1942.gif",
Width: 128,
Height: 32,
Len: 969,
Duration: 19*time.Second + 380*time.Millisecond,
},
{
Name: "afterburner.gif",
Width: 128,
Height: 32,
Len: 600,
Duration: 12 * time.Second,
},
}
for _, test := range tests {
t.Run(test.Name, func(it *testing.T) {
f, err := os.Open(filepath.Join("testdata", "gif", test.Name))
if err != nil {
it.Fatal(err)
}
defer func() { _ = f.Close() }()
a, err := DecodeGIF(f)
if err != nil {
it.Fatal(err)
}
if v := a.Len(); v != test.Len {
it.Errorf("expected %d frames, got %d", test.Len, v)
}
if v := a.Duration(); v != test.Duration {
it.Errorf("expected %s duration, got %s", test.Duration, v)
}
w, h := a.Size()
if w != test.Width || h != test.Height {
it.Errorf("expected %dx%d size, got %dx%d", test.Width, test.Height, w, h)
}
if err = f.Close(); err != nil {
it.Fatal(err)
}
})
}
}

BIN
testdata/gif/1942.gif View File

Before After
Width: 128  |  Height: 32  |  Size: 760 KiB

BIN
testdata/gif/afterburner.gif View File

Before After
Width: 128  |  Height: 32  |  Size: 372 KiB

Loading…
Cancel
Save