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.

158 lines
4.1 KiB

package dmd
import (
"bytes"
"encoding/binary"
"fmt"
"image"
"io"
"os"
"path/filepath"
"time"
"maze.io/x/dmd/container"
"maze.io/x/dmd/bitmap"
)
const (
dmd1Magic = "DMD1"
dmd1VersionChunk = "VERS"
dmd1AnimationChunk = "ANIM"
dmd1InfoChunk = "INFO"
dmd1FrameChunk = "FRAM"
dmd1FrameTimeChunk = "TIME"
dmd1FrameDataChunk = "DATA"
dmd1LoopChunk = "LOOP"
)
// DecodeDMD1 decodes one DMD1 animation (as used in RPI2DMD).
func DecodeDMD1(r io.Reader) (Animation, error) {
var chunk container.DMD1Chunk
if _, err := chunk.ReadFrom(r); err != nil {
return nil, err
} else if name := string(chunk.Name[:]); name != dmd1Magic {
return nil, ErrHeaderMagic
}
a := new(imageAnimation)
if f, ok := r.(*os.File); ok {
a.name = filepath.Base(f.Name())
a.name = a.name[:len(a.name)-len(filepath.Ext(a.name))]
} else if n, ok := r.(namer); ok {
a.name = n.Name()
}
r = bytes.NewReader(chunk.Data)
for {
if _, err := chunk.ReadFrom(r); err == io.EOF {
break
} else if err != nil {
return nil, err
}
name := string(chunk.Name[:])
// log.Printf("dmd: DMD1 chunk %q of size %d", name, chunk.size)
switch name {
case dmd1VersionChunk:
if chunk.Size != 4 {
return nil, fmt.Errorf("dmd1: unexpected %q chunk size", name)
}
var version uint32
if version = binary.LittleEndian.Uint32(chunk.Data); version != 0x00010000 {
return nil, fmt.Errorf("dmd1: unsupported version %#08x", version)
}
case dmd1AnimationChunk:
if err := decodeDMD1Animation(bytes.NewReader(chunk.Data), a); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("dmd: unsupported DMD1 chunk %q", name)
}
}
return a, nil
}
func decodeDMD1Animation(r io.Reader, a *imageAnimation) error {
var chunk container.DMD1Chunk
for {
if _, err := chunk.ReadFrom(r); err == io.EOF {
return nil
} else if err != nil {
return err
}
name := string(chunk.Name[:])
// log.Printf("dmd: DMD1 animation chunk %q of size %d", name, chunk.size)
switch name {
case dmd1InfoChunk:
var info struct {
Width uint32
Height uint32
_ [3]uint32
Frames uint32
}
if err := binary.Read(bytes.NewReader(chunk.Data), binary.LittleEndian, &info); err != nil {
return err
}
a.width = int(info.Width)
a.height = int(info.Height)
a.delay = make([]time.Duration, 0, info.Frames)
a.frame = make([]image.Image, 0, info.Frames)
case dmd1FrameChunk:
var (
i image.Image
d time.Duration
err error
)
if i, d, err = decodeDMD1Frame(bytes.NewReader(chunk.Data), a.width, a.height); err == io.EOF {
return nil
} else if err != nil {
return err
}
a.delay = append(a.delay, d)
a.frame = append(a.frame, i)
default:
return fmt.Errorf("dmd: unsupported DMD1 animation chunk %q", name)
}
}
}
func decodeDMD1Frame(r io.Reader, width, height int) (i image.Image, d time.Duration, err error) {
var chunk container.DMD1Chunk
// TIME chunk.
if _, err = chunk.ReadFrom(r); err != nil {
return
} else if name := string(chunk.Name[:]); name != dmd1FrameTimeChunk {
return nil, 0, fmt.Errorf("dmd: expected DMD1 %q chunk, got %q", dmd1FrameTimeChunk, name)
} else if chunk.Size != 4 {
return nil, 0, fmt.Errorf("dmd: unexpected DMD1 %q chunk size", name)
}
d = time.Duration(binary.LittleEndian.Uint32(chunk.Data)) * time.Millisecond
// DATA chunk.
if _, err = chunk.ReadFrom(r); err != nil {
return
} else if name := string(chunk.Name[:]); name != dmd1FrameDataChunk {
return nil, 0, fmt.Errorf("dmd: expected DMD1 %q chunk, got %q", dmd1FrameDataChunk, name)
} else if need := uint32(width*height*4) / 2; chunk.Size < need {
return nil, 0, fmt.Errorf("dmd: expected DMD1 %q chunk size of %d, got %d", dmd1FrameDataChunk, need, chunk.Size)
}
img := bitmap.NewRGBA16Image(image.Rect(0, 0, width, height))
copy(img.Pix, chunk.Data)
// DMD1 sets the alpha to 0 if no alpha, or 1 if alpha channel.
for i, l := 0, len(img.Pix); i < l; i += 2 {
// RGBA is stored as BARG (little endian)
if img.Pix[i]&1 == 1 {
img.Pix[i] |= 0x0f
}
// FIXME: Removes alpha completely.
img.Pix[i] |= 0x0f
}
return img, d, nil
}