Package dmd implements well-known formats for dot-matrix display (DMD) art.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
4.3 KiB

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
}