package dmd
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"image/draw"
|
|
"image/gif"
|
|
"io"
|
|
"time"
|
|
|
|
"maze.io/x/dmd/bitmap"
|
|
)
|
|
|
|
const gifDelay = 10 * time.Millisecond
|
|
|
|
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) * gifDelay
|
|
}
|
|
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]) * gifDelay, 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, c color.Color) error {
|
|
var (
|
|
out = &gif.GIF{
|
|
Config: image.Config{},
|
|
Image: make([]*image.Paletted, 0, a.Len()),
|
|
}
|
|
mask image.Image
|
|
palette color.Palette
|
|
prev color.Palette
|
|
)
|
|
|
|
if a.IsMask() {
|
|
mask = image.NewUniform(c)
|
|
palette = makeColorRamp(c)
|
|
}
|
|
|
|
for i, l := 0, a.Len(); i < l; i++ {
|
|
frame, delay, err := a.NextFrame()
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
if mask != nil {
|
|
// log.Printf("colorize %T: %+v", frame, frame)
|
|
p := image.NewPaletted(frame.Bounds(), palette)
|
|
draw.DrawMask(p, frame.Bounds(), mask, image.Point{}, frame, image.Point{}, draw.Src)
|
|
out.Image = append(out.Image, p)
|
|
} else {
|
|
// log.Printf("gif frame %d: %T %s", i, frame, frame.Bounds())
|
|
switch frame := frame.(type) {
|
|
case *image.Paletted:
|
|
p := image.NewPaletted(frame.Bounds(), nil)
|
|
copy(p.Pix, frame.Pix)
|
|
if len(frame.Palette) == 0 {
|
|
if len(prev) == 0 {
|
|
p.Palette = Mask
|
|
} else {
|
|
p.Palette = prev
|
|
}
|
|
} else {
|
|
p.Palette = make(color.Palette, len(frame.Palette))
|
|
copy(p.Palette, frame.Palette)
|
|
prev = p.Palette
|
|
}
|
|
// log.Printf("gif frame %d save: %T %d->%d", i, p, len(frame.Palette), len(p.Palette))
|
|
out.Image = append(out.Image, p)
|
|
default:
|
|
out.Image = append(out.Image, toPaletted(frame))
|
|
}
|
|
}
|
|
out.Delay = append(out.Delay, int((delay+5*time.Millisecond)/(10*time.Millisecond)))
|
|
}
|
|
|
|
return gif.EncodeAll(w, out)
|
|
}
|
|
|
|
const (
|
|
rampColors = 16
|
|
rampStep = 0x100 / rampColors
|
|
)
|
|
|
|
func makeColorRamp(c color.Color) color.Palette {
|
|
p := make(color.Palette, rampColors)
|
|
p[0] = color.RGBA{}
|
|
|
|
r, g, b, _ := c.RGBA()
|
|
r &= 0xff
|
|
g &= 0xff
|
|
b &= 0xff
|
|
for i := uint32(0); i < rampColors; i++ {
|
|
p[i] = color.RGBA{
|
|
R: uint8((r * (i + 1) * rampStep) >> 8),
|
|
G: uint8((g * (i + 1) * rampStep) >> 8),
|
|
B: uint8((b * (i + 1) * rampStep) >> 8),
|
|
A: 0xff,
|
|
}
|
|
}
|
|
return p
|
|
}
|
|
|
|
func toPaletted(i image.Image) *image.Paletted {
|
|
switch i := i.(type) {
|
|
case *image.Paletted:
|
|
if len(i.Palette) == 0 {
|
|
return &image.Paletted{
|
|
Pix: i.Pix,
|
|
Stride: i.Stride,
|
|
Rect: i.Rect,
|
|
Palette: GrayPalette,
|
|
}
|
|
}
|
|
return i
|
|
|
|
case *bitmap.MaskedImage:
|
|
if !i.HasMaskedPixels() {
|
|
return toPaletted(&i.Paletted)
|
|
}
|
|
b := i.Bounds()
|
|
o := image.NewPaletted(b, append(i.Palette, color.Transparent))
|
|
draw.Draw(o, b, i, image.Point{}, draw.Src)
|
|
return o
|
|
|
|
default:
|
|
b := i.Bounds()
|
|
o := image.NewPaletted(b, getPalette(i))
|
|
draw.Draw(o, b, i, image.Point{}, draw.Src)
|
|
return o
|
|
}
|
|
}
|
|
|
|
func getPalette(i image.Image) (p color.Palette) {
|
|
var (
|
|
seen = make(map[uint32]bool)
|
|
b = i.Bounds()
|
|
)
|
|
for y := 0; y < b.Dy(); y++ {
|
|
for x := 0; x < b.Dx(); x++ {
|
|
c := i.At(x, y)
|
|
r, g, b, _ := c.RGBA()
|
|
v := (r & 0xff) << 16
|
|
v |= (g & 0xff) << 8
|
|
b |= (b & 0xff) << 0
|
|
if !seen[v] {
|
|
seen[v] = true
|
|
p = append(p, c)
|
|
}
|
|
}
|
|
}
|
|
return p
|
|
}
|