@ -1,341 +0,0 @@ | |||
package dmd | |||
import ( | |||
"bufio" | |||
"bytes" | |||
"encoding/binary" | |||
"fmt" | |||
"image/color" | |||
"io" | |||
"io/ioutil" | |||
"log" | |||
"time" | |||
"maze.io/x/dmd/internal/heatshrink" | |||
) | |||
const ( | |||
pin2DMDPanelWidth = 128 | |||
pin2DMDPanelHeight = 32 | |||
) | |||
/* | |||
4 Byte Header: ANIM | |||
2 Byte Version: 00 01 | |||
2 Byte Anzahl der Animationen: 00 02 | |||
N Byte Name der Animation in modified UTF-8 | |||
first 2 Byte gives length of string incl 2 byte length header | |||
2 Byte: cycles | |||
2 Byte: hold | |||
2 Byte: clockFrom | |||
1 Byte: clock small | |||
1 Byte: clock in front | |||
2 Byte: xoffset for small clock | |||
2 Byte: yoffset for small clock | |||
2 Byte: refresh delay in millis | |||
1 Byte: Animation Type (not relevant) | |||
1 Byte: fsk tag | |||
2 Byte: frame set count | |||
each frame consists of 2 or 3 planes and optionally a mask plane | |||
each plane is tagged with its type 0,1,2 for normal bitplanes in | |||
grayscale animations or M ( for mask, the order is always m,0,1,2 | |||
where m and 2 are optional | |||
foreach frame: | |||
2 Byte: plane size in Byte. normally 512 byte | |||
2 Byte: frame duration in ms | |||
1 Byte: number of planes | |||
foreach plane: | |||
1 Byte: type of plane, m,0,1,2 see above | |||
plane size Byte frame data: 1 Bit = 1 Pixel | |||
Bit 7 is pixel on the left (BIG_ENDIAN) | |||
end | |||
end | |||
*/ | |||
type pin2DMDHeader struct { | |||
Version uint16 | |||
Animations uint16 | |||
} | |||
func readPin2DMDAnimations(rs io.ReadSeeker) (all []*Animation, err error) { | |||
var h pin2DMDHeader | |||
if err = readBE(rs, &h); err != nil { | |||
return | |||
} | |||
if h.Version > 6 { | |||
return nil, fmt.Errorf("dmd: pin2dmd animation version %d is not supported", h.Version) | |||
} | |||
if h.Version >= 2 { | |||
// Skip over indexes. | |||
if _, err = ioutil.ReadAll(io.LimitReader(rs, 4*int64(h.Animations))); err != nil { | |||
return | |||
} | |||
} | |||
r := bufferedReader(rs) | |||
for i := 0; i < int(h.Animations); i++ { | |||
a := &Animation{ | |||
Palettes: make(map[int]color.Palette), | |||
} | |||
if err = a.readPin2DMDData(r, int(h.Version)); err != nil { | |||
return | |||
} | |||
all = append(all, a) | |||
} | |||
return | |||
} | |||
type pin2DMDAnimationHeader struct { | |||
Cycles uint16 | |||
Hold uint16 | |||
ClockFrom uint16 | |||
ClockSmall uint8 | |||
ClockFront uint8 | |||
ClockXOffset uint16 | |||
ClockYOffset uint16 | |||
Delay uint16 | |||
_ uint8 | |||
FSK uint8 | |||
Frames int16 | |||
} | |||
func (a *Animation) readPin2DMDData(r *bufio.Reader, version int) (err error) { | |||
var nameLength int16 | |||
if err = readBE(r, &nameLength); err != nil { | |||
return | |||
} | |||
if nameLength > 0 { | |||
name := make([]byte, nameLength) | |||
if _, err = io.ReadFull(r, name); err != nil { | |||
return nil | |||
} | |||
a.Name = cString(name) | |||
} | |||
var h pin2DMDAnimationHeader | |||
if err = readBE(r, &h); err != nil { | |||
return | |||
} | |||
// log.Printf("pin2dmd header for %q: %#+v", a.Name, h) | |||
frames := int(h.Frames) | |||
if frames < 0 { | |||
frames += 65536 | |||
} | |||
if version >= 2 { | |||
// Custom palette. | |||
var ( | |||
index uint8 | |||
palette color.Palette | |||
) | |||
if index, palette, err = readPin2DMDPalette(r); err != nil { | |||
return fmt.Errorf("error decoding palette: %w", err) | |||
} | |||
//if a.Name == "Tilt" { | |||
log.Printf("custom palette %d in %q: %d colors: %+v", index, a.Name, len(palette), palette) | |||
//} | |||
a.Palettes[int(index)] = palette | |||
} | |||
if version >= 3 { | |||
// Edit mode. | |||
if _, err = r.ReadByte(); err != nil { | |||
return | |||
} | |||
} | |||
if version >= 4 { | |||
// Size of the panel. | |||
var w, h int16 | |||
if err = readBE(r, &w); err != nil { | |||
return | |||
} | |||
if err = readBE(r, &h); err != nil { | |||
return | |||
} | |||
a.Width = int(w) | |||
a.Height = int(h) | |||
} else { | |||
a.Width = pin2DMDPanelWidth | |||
a.Height = pin2DMDPanelHeight | |||
} | |||
if version >= 5 { | |||
// Masks. | |||
var n int16 | |||
if err = readBE(r, &n); err != nil { | |||
return | |||
} | |||
} | |||
if version >= 6 { | |||
var recorded uint8 | |||
if recorded, err = r.ReadByte(); err != nil { | |||
return | |||
} | |||
if recorded != 0 { | |||
// Link to the recording (ignored). | |||
if _, err = readString(r); err != nil { | |||
return | |||
} | |||
// Start frame (ignored) | |||
var n int16 | |||
if err = readBE(r, &n); err != nil { | |||
return | |||
} | |||
} | |||
} | |||
return a.readPin2DMDFrames(r, int(h.Frames), version) | |||
} | |||
type pin2DMDPaletteHeader struct { | |||
Index int16 | |||
Colors int16 | |||
} | |||
func readPin2DMDPalette(r io.Reader) (index uint8, palette color.Palette, err error) { | |||
var h pin2DMDPaletteHeader | |||
if err = readBE(r, &h); err != nil { | |||
return | |||
} | |||
index = uint8(h.Index) | |||
log.Printf("read %d color palette for index %d", h.Colors, h.Index) | |||
if h.Colors == 0 && index < 16 { | |||
return index, defaultPalettes[index], nil | |||
} | |||
triplets := make([]byte, h.Colors*3) | |||
if _, err = io.ReadFull(r, triplets); err != nil { | |||
return | |||
} | |||
palette = make(color.Palette, h.Colors) | |||
for i := 0; i < int(h.Colors); i++ { | |||
o := i * 3 | |||
palette[i] = color.RGBA{ | |||
R: triplets[o+0], | |||
G: triplets[o+1], | |||
B: triplets[o+2], | |||
A: 0xff, | |||
} | |||
} | |||
return | |||
} | |||
func (a *Animation) readPin2DMDFrames(r *bufio.Reader, n, version int) (err error) { | |||
a.Index = make([]int, n) | |||
a.Frame = make([]*Frame, n) | |||
for i := 0; i < n; i++ { | |||
// log.Printf("pin2dmd: frame %d/%d", i+1, n) | |||
a.Frame[i] = NewFrame(a.Width, a.Height) | |||
a.Index[i] = i | |||
if err = a.Frame[i].readPin2DMDData(r, version); err != nil { | |||
return fmt.Errorf("error decoding frame: %w", err) | |||
} | |||
} | |||
return | |||
} | |||
type pin2DMDFrameHeader struct { | |||
Size uint16 | |||
Delay uint16 | |||
} | |||
func (f *Frame) readPin2DMDData(r *bufio.Reader, version int) (err error) { | |||
var h pin2DMDFrameHeader | |||
if err = readBE(r, &h); err != nil { | |||
return | |||
} | |||
/* | |||
if h.Size%32 != 0 { | |||
return fmt.Errorf("dmd: odd pin2dmd frame size %d", h.Size) | |||
} | |||
*/ | |||
f.Delay = time.Duration(h.Delay) * time.Millisecond | |||
// log.Println(f.Delay) | |||
if version >= 4 { | |||
var hash [4]byte | |||
if _, err = io.ReadFull(r, hash[:]); err != nil { | |||
return | |||
} | |||
f.Checksum = binary.BigEndian.Uint32(hash[:]) | |||
} | |||
var nPlanes uint8 | |||
if nPlanes, err = r.ReadByte(); err != nil { | |||
return | |||
} | |||
// log.Printf(" frame: version %d %#+v", version, h) | |||
// log.Printf(" frame: %d planes of %db %#+v", nPlanes, h.Size, f) | |||
pr := r | |||
if version >= 3 { | |||
var isCompressed bool | |||
if isCompressed, err = readBool(r); err != nil { | |||
return | |||
} else if isCompressed { | |||
var compressedSize int16 | |||
if err = readBE(r, &compressedSize); err != nil { | |||
return | |||
} | |||
var compressed []byte | |||
if compressed, err = ioutil.ReadAll(io.LimitReader(r, int64(compressedSize))); err != nil { | |||
return | |||
} | |||
var decompressed []byte | |||
if decompressed, err = heatshrink.Decompress(10, 5, compressed); err != nil { | |||
return | |||
} | |||
log.Printf("decompressed %d bytes to %d", compressedSize, len(decompressed)) | |||
pr = bufio.NewReader(bytes.NewReader(decompressed)) | |||
} | |||
} | |||
if err = f.readPin2DMDPlanes(pr, int(nPlanes), int(h.Size)); err != nil { | |||
return fmt.Errorf("error reading plane: %w", err) | |||
} | |||
return | |||
} | |||
func (f *Frame) readPin2DMDPlanes(r *bufio.Reader, nPlanes, size int) (err error) { | |||
for i := 0; i < nPlanes; i++ { | |||
var ( | |||
data = make([]byte, size) | |||
marker byte | |||
) | |||
if marker, err = r.ReadByte(); err != nil { | |||
return | |||
} | |||
if _, err = io.ReadFull(r, data); err != nil { | |||
return fmt.Errorf("error reading plane %d/%d (%d bytes): %w", i+1, nPlanes, size, err) | |||
} | |||
if int(marker) < nPlanes { | |||
f.Plane = append(f.Plane, &Plane{ | |||
Marker: marker, | |||
Data: data, | |||
}) | |||
} else { | |||
f.Mask = data | |||
} | |||
} | |||
return | |||
} | |||
func (p *Plane) readPin2DMDData(r io.Reader, size int) (err error) { | |||
p.Data = make([]byte, size) | |||
if _, err = io.ReadFull(r, p.Data); err != nil { | |||
return | |||
} | |||
reverseBytes(p.Data, p.Data) | |||
return | |||
} |
@ -1,156 +0,0 @@ | |||
package dmd | |||
import ( | |||
"io" | |||
"log" | |||
"time" | |||
) | |||
const ( | |||
runDMDPanelWidth = 128 | |||
runDMDPanelHeight = 32 | |||
runDMDHeaderMagic = "DGD" | |||
runDMDHeaderOffset = 0xc800 | |||
runDMDHeaderBuildOffset = 0x01ef | |||
runDMDHeaderSize = 0x0200 | |||
) | |||
func readRunDMDAnimations(rs io.ReadSeeker) (all []*Animation, err error) { | |||
if _, err = rs.Seek(3, io.SeekStart); err != nil { | |||
return | |||
} | |||
var n uint16 | |||
if err = readBE(rs, &n); err != nil { | |||
return | |||
} | |||
for i := 0; i < int(n); i++ { | |||
a := new(Animation) | |||
if err = a.readRunDMDData(rs, i); err != nil { | |||
return | |||
} | |||
all = append(all, a) | |||
} | |||
return | |||
} | |||
type runDMDAnimationHeader struct { | |||
ID uint16 | |||
_ uint8 | |||
Frames uint8 | |||
ByteOffset uint32 | |||
Entries uint8 | |||
Width uint8 | |||
Height uint8 | |||
_ [9]byte | |||
Name [32]byte | |||
} | |||
type runDMDAnimationFrameHeader struct { | |||
ID uint8 | |||
Delay uint8 | |||
} | |||
func (a *Animation) readRunDMDData(rs io.ReadSeeker, n int) (err error) { | |||
if _, err = rs.Seek(runDMDHeaderOffset+runDMDHeaderSize*int64(n), io.SeekStart); err != nil { | |||
return | |||
} | |||
var h runDMDAnimationHeader | |||
if err = readBE(rs, &h); err != nil { | |||
return | |||
} | |||
a.Name = cString(h.Name[:]) | |||
a.Width = int(h.Width) | |||
a.Height = int(h.Height) | |||
log.Printf("rundmd: animation %q: %#+v", a.Name, h) | |||
if _, err = rs.Seek(int64(h.ByteOffset)*runDMDHeaderSize, io.SeekStart); err != nil { | |||
return | |||
} | |||
var delays []time.Duration | |||
for i := 0; i < int(h.Frames); i++ { | |||
var fh runDMDAnimationFrameHeader | |||
if err = readBE(rs, &fh); err != nil { | |||
return | |||
} | |||
delays = append(delays, time.Duration(fh.Delay)*time.Millisecond) | |||
a.Index = append(a.Index, int(fh.ID)-1) | |||
} | |||
if _, err = rs.Seek(runDMDHeaderOffset+int64(h.ByteOffset), io.SeekStart); err != nil { | |||
return | |||
} | |||
b := make([]byte, a.Width*a.Height/2) | |||
for i := 0; i < int(h.Frames); i++ { | |||
o := runDMDHeaderSize + int64(h.ByteOffset)*runDMDHeaderSize | |||
o += int64(a.Width) * int64(a.Height) * int64(i) / 2 | |||
if _, err = rs.Seek(o, io.SeekStart); err != nil { | |||
return | |||
} | |||
f := NewFrame(a.Width, a.Height) | |||
f.Delay = delays[i] | |||
// Read interleaved (4-bits) planes. | |||
if _, err = io.ReadFull(rs, b); err != nil { | |||
return | |||
} | |||
// Samples are packed plane values (one nibble contains bits for each plane): | |||
// 0x2a -> 0010 (upper) 1010 (lower) | |||
// planes: 0123 0123 | |||
// | |||
// 0x87654321 -> 1000 0111 0110 0101 0100 0011 0010 0001 | |||
// plane0 10000000 | |||
// plane1 01111000 | |||
// plane2 01100110 | |||
// plane3 01010101 | |||
f.Plane = make([]*Plane, 4) | |||
for j := 0; j < 4; j++ { | |||
f.Plane[j] = &Plane{ | |||
Marker: uint8(j), | |||
Data: make([]byte, (a.Width*a.Height)>>2), | |||
} | |||
} | |||
for j, v := range b { | |||
var ( | |||
index = j >> 2 | |||
bit0 = (3 - (j & 3)) << 1 // bits 6, 4, 2, 0 | |||
bit1 = bit0 | 1 // bits 7, 5, 3, 1 | |||
) | |||
f.Plane[0].Data[index] |= ((v & 0x01) >> 0) << bit0 | |||
f.Plane[1].Data[index] |= ((v & 0x02) >> 1) << bit0 | |||
f.Plane[2].Data[index] |= ((v & 0x04) >> 2) << bit0 | |||
f.Plane[3].Data[index] |= ((v & 0x08) >> 3) << bit0 | |||
f.Plane[0].Data[index] |= ((v & 0x10) >> 4) << bit1 | |||
f.Plane[1].Data[index] |= ((v & 0x20) >> 5) << bit1 | |||
f.Plane[2].Data[index] |= ((v & 0x40) >> 6) << bit1 | |||
f.Plane[3].Data[index] |= ((v & 0x80) >> 7) << bit1 | |||
} | |||
// Deinterleave. | |||
/* | |||
data := make([]byte, a.Width*a.Height) | |||
for j, v := range b { | |||
q := j << 1 | |||
data[q+0] = v >> 4 | |||
data[q+1] = v & 0x0f | |||
} | |||
for j := 0; j < 4; j++ { | |||
p := &Plane{ | |||
Marker: byte(j), | |||
Data: make([]byte, a.Width*a.Height/8), | |||
} | |||
f.Plane = append(f.Plane, p) | |||
} | |||
*/ | |||
a.Frame = append(a.Frame, f) | |||
} | |||
return | |||
} |
@ -1,153 +0,0 @@ | |||
package dmd | |||
import ( | |||
"fmt" | |||
"image/gif" | |||
"os" | |||
"path/filepath" | |||
"strings" | |||
"testing" | |||
) | |||
func TestReadPin2DMDAnimations(t *testing.T) { | |||
tests := []struct { | |||
Name string | |||
}{ | |||
{ | |||
Name: "smb.ani", | |||
}, | |||
} | |||
for _, test := range tests { | |||
t.Run(test.Name, func(it *testing.T) { | |||
f, err := os.Open(filepath.Join("testdata", test.Name)) | |||
if err != nil { | |||
it.Skip(err) | |||
} | |||
defer f.Close() | |||
all, err := ReadAnimations(f) | |||
if err != nil { | |||
it.Fatal(err) | |||
} | |||
it.Logf("%s: %d animations", test.Name, len(all)) | |||
/* | |||
for i, a := range all { | |||
it.Logf("%s: animation %d %q: %d frames, %d palettes, duration %s", | |||
test.Name, i, a.Name, len(a.Frame), len(a.Palettes), a.Duration()) | |||
} | |||
*/ | |||
for i, a := range all { | |||
it.Logf("animation %d: %s", i, a.Name) | |||
} | |||
a := all[41] | |||
it.Logf("animation %s", a.Name) | |||
for i, f := range a.Frame { | |||
it.Logf("frame %d:", i) | |||
for j, p := range f.Plane { | |||
it.Logf("plane %d: %#02x", j, p.Marker) | |||
} | |||
} | |||
return | |||
for _, a := range all { | |||
it.Logf("%d palettes", len(a.Palettes)) | |||
for j, colors := range a.Palettes { | |||
it.Logf("palette %d: %+v", j, colors) | |||
} | |||
if err := testSaveGIF(it, test.Name, a, nil); err != nil { | |||
it.Errorf("%s: %v", a.Name, err) | |||
} | |||
/* | |||
for i, frame := range a.Frame { | |||
o, err := os.Create(filepath.Join(os.TempDir(), fmt.Sprintf("%s-%d.png", a.Name, i))) | |||
if err != nil { | |||
it.Fatal(err) | |||
} | |||
defer o.Close() | |||
it.Logf("saving to %s", o.Name()) | |||
if err = png.Encode(o, Panel{ | |||
PalettedImage: &FrameImage{ | |||
Frame: frame, | |||
Palette: Amber, // a.Palettes[3], //a.Palettes[uint8(i)], | |||
}, | |||
Dot: 5, | |||
}); err != nil { | |||
it.Error(err) | |||
} | |||
it.Logf("frame %d [%02x]: %#+v", i, frame.Hash, frame) | |||
for j, plane := range frame.Plane { | |||
it.Logf("plane %d: %d (%#02x)", j, len(plane.Data)/128, plane.Marker) | |||
// dumpPlane(plane.Data, 128/8) | |||
} | |||
} | |||
*/ | |||
} | |||
}) | |||
} | |||
} | |||
func testSaveGIF(t *testing.T, name string, a *Animation, c *Coloring) error { | |||
t.Helper() | |||
name = filepath.Base(name) | |||
if ext := filepath.Ext(name); ext != "" { | |||
name = name[:len(name)-len(ext)] | |||
} | |||
full := filepath.Join(os.TempDir(), fmt.Sprintf("dmd-%s-%s.gif", name, a.Name)) | |||
t.Logf("saving to %s", full) | |||
o, err := os.Create(full) | |||
if err != nil { | |||
return err | |||
} | |||
if err := gif.EncodeAll(o, a.GIF(c, 5)); err != nil { | |||
_ = o.Close() | |||
_ = os.Remove(name) | |||
return err | |||
} | |||
return o.Close() | |||
} | |||
func TestReadRunDMDAnimations(t *testing.T) { | |||
name := os.Getenv("RUNDMD_IMAGE") | |||
if name == "" { | |||
t.Skip("environment variable RUNDMD_IMAGE not set") | |||
} | |||
f, err := os.Open(name) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
defer f.Close() | |||
all, err := ReadAnimations(f) | |||
if err != nil { | |||
t.Fatal(err) | |||
} | |||
t.Logf("%s: %d animations", name, len(all)) | |||
} | |||
func dumpPlane(plane []byte, bytesPerLine int) { | |||
var sb strings.Builder | |||
for i, l := 0, len(plane); i < l; i++ { | |||
v := plane[i] | |||
for j := 0; j < 8; j++ { | |||
if ((128 >> j) & v) != 0x00 { | |||
sb.WriteByte('#') | |||
} else { | |||
sb.WriteByte('.') | |||
} | |||
} | |||
if i%bytesPerLine == bytesPerLine-1 { | |||
fmt.Println(sb.String()) | |||
sb.Reset() | |||
} | |||
} | |||
} |
@ -1,260 +0,0 @@ | |||
package dmd | |||
/* | |||
VNI animation file | |||
The VNI format contains 0 or more animations, consisting of frames with a delay | |||
and multiple planes. Each animation has markers for clock locations and sizes. | |||
*/ | |||
/* | |||
type vniHeader struct { | |||
Version int16 | |||
Animations int16 | |||
} | |||
type vniAnimationHeader struct { | |||
Cycles int16 | |||
Hold int16 | |||
ClockFrom int16 | |||
ClockSmall bool | |||
ClockInFront bool | |||
ClockOffsetX int16 | |||
ClockOffsetY int16 | |||
RefreshDelay int16 | |||
Type uint8 | |||
Fsk uint8 | |||
Frames int16 | |||
} | |||
func readVPINAnimations(r io.Reader) (all []*Animation, err error) { | |||
r = newReadDumper(r) | |||
var header vniHeader | |||
if err = readBE(r, &header); err != nil { | |||
return | |||
} | |||
log.Printf("VPI file version %d, %d animations", header.Version, header.Animations) | |||
if header.Version >= 2 { | |||
// Skip animation indexes, we'll get to them as we parse the .VNI file.ร | |||
log.Printf("skipping %d bytes of animation indexes", header.Animations*4) | |||
for i := 0; i < int(header.Animations); i++ { | |||
if _, err = readUint32(r); err != nil { | |||
return | |||
} | |||
} | |||
} | |||
all = make([]*Animation, header.Animations) | |||
for i := 0; i < int(header.Animations); i++ { | |||
a := new(Animation) | |||
if err = a.readVPIData(r, int(header.Version)); err != nil { | |||
return | |||
} | |||
all = append(all, a) | |||
} | |||
return all, nil | |||
} | |||
func (a *Animation) readVPIData(r io.Reader, version int) (err error) { | |||
if a.Name, err = readString(r); err != nil { | |||
return | |||
} | |||
var header vniAnimationHeader | |||
if err = binary.Read(r, binary.BigEndian, &header); err != nil { | |||
return | |||
} | |||
frames := int(header.Frames) | |||
if frames < 0 { | |||
frames += 1 << 16 | |||
} | |||
log.Printf("reading VNI animation %q version %d with %d frames: %#+v", a.Name, version, frames, header) | |||
a.ClockOffset = image.Pt(int(header.ClockOffsetX), int(header.ClockOffsetY)) | |||
a.ClockInFront = header.ClockInFront | |||
a.ClockSmall = header.ClockSmall | |||
if version >= 2 { | |||
// Read palette data. | |||
if a.PaletteIndex, a.Palette, err = readVPIPalette(r); err != nil { | |||
return | |||
} | |||
if a.PaletteIndex > -1 { | |||
log.Printf("%s: palette index %d, custom palette: %t (%d)", a.Name, a.PaletteIndex, a.Palette != nil, len(a.Palette)) | |||
} | |||
} else { | |||
a.PaletteIndex = -1 | |||
} | |||
if version >= 3 { | |||
// Edit mode. | |||
if _, err = readBool(r); err != nil { | |||
return | |||
} | |||
} | |||
if version >= 4 { | |||
var size [2]int16 | |||
if err = readBE(r, &size); err != nil { | |||
return | |||
} | |||
a.Width = int(size[0]) | |||
a.Height = int(size[1]) | |||
log.Printf("%s: frame size %dx%d", a.Name, a.Width, a.Height) | |||
} | |||
if version >= 5 { | |||
// TODO: read masks | |||
} | |||
if version >= 6 { | |||
// TODO: read links | |||
} | |||
for i := 0; i < frames; i++ { | |||
f := NewFrame(a.Width, a.Height) | |||
if err = f.readVNIData(r, version); err != nil { | |||
return | |||
} | |||
a.Frame = append(a.Frame, f) | |||
a.Index = append(a.Index, i) | |||
} | |||
return | |||
} | |||
type vpiPaletteHeader struct { | |||
Index int16 | |||
Colors int16 | |||
} | |||
func readVPIPalette(r io.Reader) (index int, palette color.Palette, err error) { | |||
var header vpiPaletteHeader | |||
if err = readBE(r, &header); err != nil { | |||
return | |||
} | |||
if header.Colors <= 0 { | |||
return -1, nil, nil | |||
} | |||
index = int(header.Index) | |||
palette = make(color.Palette, header.Colors) | |||
for i := 0; i < int(header.Colors); i++ { | |||
if palette[i], err = readRGB24(r); err != nil { | |||
return | |||
} | |||
} | |||
return | |||
} | |||
func (f *Frame) readVNIData(r io.Reader, version int) (err error) { | |||
var ( | |||
planeSize int16 | |||
delay uint16 | |||
) | |||
if err = readBE(r, &planeSize); err != nil { | |||
return | |||
} | |||
if err = readBE(r, &delay); err != nil { | |||
return | |||
} | |||
f.Delay = time.Duration(delay) * time.Millisecond | |||
log.Printf("frame: %d planes, delay %s", planeSize, f.Delay) | |||
if version >= 4 { | |||
if f.Checksum, err = readUint32(r); err != nil { | |||
return | |||
} | |||
log.Printf("frame checksum: %#08x", f.Checksum) | |||
} | |||
var bits uint8 | |||
if bits, err = readByte(r); err != nil { | |||
return | |||
} | |||
var isCompressed bool | |||
if version < 3 { | |||
log.Printf("frame: %dx%d byte plane data", bits, planeSize) | |||
return f.readVNIPlanes(r, int(planeSize), int(bits)) | |||
} else if isCompressed, err = readBool(r); err != nil { | |||
return | |||
} else if !isCompressed { | |||
log.Printf("frame: %dx%d byte plane data, not compressed", bits, planeSize) | |||
return f.readVNIPlanes(r, int(planeSize), int(bits)) | |||
} | |||
log.Printf("frame: %dx%d byte plane data, compressed", bits, planeSize) | |||
var compressedSize int32 | |||
if err = readBE(r, &compressedSize); err != nil { | |||
return | |||
} | |||
var compressedPlanes = make([]byte, compressedSize) | |||
if _, err = io.ReadFull(r, compressedPlanes); err != nil { | |||
return | |||
} | |||
var decompressedPlanes []byte | |||
if decompressedPlanes, err = heatshrink.Decompress(10, 0, compressedPlanes); err != nil { | |||
return | |||
} | |||
log.Printf("decompressedPlanes %d bytes to %d", compressedSize, len(decompressedPlanes)) | |||
return f.readVNIPlanes(bufio.NewReader(bytes.NewReader(decompressedPlanes)), int(planeSize), int(bits)) | |||
} | |||
func (f *Frame) readVNIPlanes(r io.Reader, planeSize, bitLength int) (err error) { | |||
h := bytes.NewBuffer(nil) | |||
b := bufio.NewReader(io.TeeReader(r, h)) | |||
for i := 0; i < bitLength; i++ { | |||
var marker byte | |||
if marker, err = b.ReadByte(); err != nil { | |||
return | |||
} | |||
if marker == 0x6d { | |||
f.Mask = make([]byte, planeSize) | |||
if _, err = io.ReadFull(b, f.Mask); err != nil { | |||
return | |||
} | |||
} else { | |||
p := &Plane{ | |||
Marker: marker, | |||
} | |||
if err = p.readVNIData(b, planeSize, marker); err != nil { | |||
return | |||
} | |||
f.Plane = append(f.Plane, p) | |||
} | |||
} | |||
// Correct checksum. | |||
if f.Checksum == 0 { | |||
f.Checksum = checkSum(h.Bytes()) | |||
} | |||
// Decode planes to raw bitmap data. | |||
for i, p := range f.Plane { | |||
for j, c := range p.Data { | |||
c = reverseBits(c) | |||
for k := 0; k < 8; k++ { | |||
if c&1<<k != 0 { | |||
f.Pix[j*8+k] |= 1 << i | |||
} | |||
} | |||
} | |||
} | |||
return | |||
} | |||
func (p *Plane) readVNIData(r *bufio.Reader, n int, marker byte) (err error) { | |||
p.Marker = marker | |||
p.Data = make([]byte, n) | |||
if _, err = io.ReadFull(r, p.Data); err != nil { | |||
return | |||
} | |||
return | |||
} | |||
*/ |
@ -1,52 +0,0 @@ | |||
package dmd | |||
import ( | |||
"errors" | |||
"fmt" | |||
"io" | |||
"strings" | |||
) | |||
var ( | |||
ErrHeaderMagic = errors.New("dmd: invalid header magic") | |||
) | |||
type animationSet interface { | |||
DecodeAnimationSet(r io.Reader) ([]*Animation, error) | |||
} | |||
func ReadAnimations(reader io.ReadSeeker) ([]*Animation, error) { | |||
if err := rewind(reader); err != nil { | |||
return nil, err | |||
} | |||
var magic [4]byte | |||
if _, err := io.ReadFull(reader, magic[:]); err != nil { | |||
return nil, err | |||
} | |||
var ( | |||
id = cString(magic[:]) | |||
set animationSet | |||
) | |||
switch { | |||
case id == "ANIM": | |||
set = vniAnimationSet{} // pin2DMDAnimationSet{} | |||
case id == "VPIN": | |||
set = vniAnimationSet{} | |||
case strings.HasPrefix(id, runDMDHeaderMagic): | |||
return readRunDMDAnimations(reader) | |||
case id == "\x11HDD": | |||
return nil, errors.New("dmd: please unpack the HDD Raw Copy image to raw (dd) format") | |||
default: | |||
return nil, fmt.Errorf("dmd: magic %q does not denote a supported format", cString(magic[:])) | |||
} | |||
if _, err := reader.Seek(0, io.SeekStart); err != nil { | |||
return nil, err | |||
} | |||
return set.DecodeAnimationSet(reader) | |||
} |
@ -1,148 +0,0 @@ | |||
package dmd | |||
import ( | |||
"fmt" | |||
"image/color" | |||
"io" | |||
"log" | |||
) | |||
type pin2DMDAnimationSet struct{} | |||
func (s pin2DMDAnimationSet) DecodeAnimationSet(r io.Reader) (set []*Animation, err error) { | |||
var header [4]byte | |||
if _, err = io.ReadFull(r, header[:]); err != nil { | |||
return | |||
} else if string(header[:]) != "ANIM" { | |||
return nil, ErrHeaderMagic | |||
} | |||
var version uint16 | |||
if version, err = readUint16(r); err != nil { | |||
return | |||
} | |||
var animations uint16 | |||
if animations, err = readUint16(r); err != nil { | |||
return | |||
} | |||
if version > 6 { | |||
return nil, fmt.Errorf("dmd: pin2dmd animation version %d is not supported", version) | |||
} | |||
if version >= 4 { | |||
log.Printf("pin2dmd: skipping %d bytes of animation indexes", 4*int(animations)) | |||
for i := 0; i < int(animations); i++ { | |||
if _, err = readUint32(r); err != nil { | |||
return | |||
} | |||
} | |||
} | |||
set = make([]*Animation, animations) | |||
log.Printf("vni: reading %d animations from %s v%d", animations, string(header[:]), version) | |||
for i := 0; i < int(animations); i++ { | |||
if set[i], err = s.decodeAnimation(r, version); err != nil { | |||
return | |||
} | |||
} | |||
return set, nil | |||
} | |||
func (s pin2DMDAnimationSet) decodeAnimation(r io.Reader, version uint16) (a *Animation, err error) { | |||
a = &Animation{ | |||
Width: 128, | |||
Height: 32, | |||
PaletteIndex: -1, | |||
} | |||
if a.Name, err = readString(r); err != nil { | |||
return | |||
} | |||
var header struct { | |||
Cycles uint16 | |||
Hold uint16 | |||
ClockFrom uint16 | |||
ClockSmall uint8 | |||
ClockFront uint8 | |||
ClockXOffset uint16 | |||
ClockYOffset uint16 | |||
Delay uint16 | |||
_ uint8 | |||
FSK uint8 | |||
Frames int16 | |||
} | |||
if err = readBE(r, &header); err != nil { | |||
return | |||
} | |||
frames := int(header.Frames) | |||
if frames < 0 { | |||
frames += 65536 | |||
} | |||
if version >= 2 { | |||
// Custom palette. | |||
var ( | |||
index uint8 | |||
palette color.Palette | |||
) | |||
if index, palette, err = readPin2DMDPalette(r); err != nil { | |||
return nil, fmt.Errorf("error decoding palette: %w", err) | |||
} | |||
//if a.Name == "Tilt" { | |||
log.Printf("custom palette %d in %q: %d colors: %+v", index, a.Name, len(palette), palette) | |||
//} | |||
a.Palettes[int(index)] = palette | |||
} | |||
if version >= 3 { | |||
// Edit mode. | |||
if _, err = readByte(r); err != nil { | |||
return | |||
} | |||
} | |||
if version >= 4 { | |||
// Size of the panel. | |||
if a.Width, err = readInt16Int(r); err != nil { | |||
return | |||
} | |||
if a.Height, err = readInt16Int(r); err != nil { | |||
return | |||
} | |||
} else { | |||
a.Width = pin2DMDPanelWidth | |||
a.Height = pin2DMDPanelHeight | |||
} | |||
if version >= 5 { | |||
// Masks. | |||
var n int16 | |||
if err = readBE(r, &n); err != nil { | |||
return | |||
} | |||
} | |||
if version >= 6 { | |||
var isRecorded bool | |||
if isRecorded, err = readBool(r); err != nil { | |||
return | |||
} | |||
if isRecorded { | |||
// Link to the recording (ignored). | |||
if _, err = readString(r); err != nil { | |||
return | |||
} | |||
// Start frame (ignored) | |||
var n int16 | |||
if err = readBE(r, &n); err != nil { | |||
return | |||
} | |||
} | |||
} | |||
return | |||
} |
@ -1,281 +0,0 @@ | |||
package dmd | |||
import ( | |||
"bytes" | |||
"image" | |||
"image/color" | |||
"io" | |||
"log" | |||
"time" | |||
"maze.io/x/dmd/internal/heatshrink" | |||
) | |||
const vniDelayStep = time.Millisecond | |||
type vniAnimationSet struct{} | |||
func (s vniAnimationSet) DecodeAnimationSet(r io.Reader) (set []*Animation, err error) { | |||
var header [4]byte | |||
if _, err = io.ReadFull(r, header[:]); err != nil { | |||
return | |||
} else if string(header[:]) != "VPIN" && string(header[:]) != "ANIM" { | |||
return nil, ErrHeaderMagic | |||
} | |||
var version int16 | |||
if version, err = readInt16(r); err != nil { | |||
return | |||
} | |||
var animations int16 | |||
if animations, err = readInt16(r); err != nil { | |||
return | |||
} | |||
if version >= 2 { | |||
log.Printf("vni: skipping %d bytes of animation indexes", 4*int(animations)) | |||
for i := 0; i < int(animations); i++ { | |||
if _, err = readUint32(r); err != nil { | |||
return | |||
} | |||
} | |||
} | |||
set = make([]*Animation, animations) | |||
log.Printf("vni: reading %d animations from %s v%d", animations, string(header[:]), version) | |||
for i := 0; i < int(animations); i++ { | |||
if set[i], err = s.decodeAnimation(r, version); err != nil { | |||
return | |||
} | |||
} | |||
return set, nil | |||
} | |||
func (s vniAnimationSet) decodeAnimation(r io.Reader, version int16) (a *Animation, err error) { | |||
a = &Animation{ | |||
Width: 128, | |||
Height: 32, | |||
PaletteIndex: -1, | |||
} | |||
if a.Name, err = readString(r); err != nil { | |||
return | |||
} | |||
var ( | |||
clockOffsetX int16 | |||
clockOffsetY int16 | |||
frameCount int16 | |||
) | |||
if _, err = readInt16(r); err != nil { // cycles | |||
return | |||
} | |||
if _, err = readInt16(r); err != nil { // hold | |||
return | |||
} | |||
if _, err = readInt16(r); err != nil { // clock from | |||
return | |||
} | |||
if a.ClockSmall, err = readBool(r); err != nil { | |||
return | |||
} | |||
if a.ClockInFront, err = readBool(r); err != nil { | |||
return | |||
} | |||
if clockOffsetX, err = readInt16(r); err != nil { | |||
return | |||
} | |||
if clockOffsetY, err = readInt16(r); err != nil { | |||
return | |||
} | |||
a.ClockOffset = image.Pt(int(clockOffsetX), int(clockOffsetY)) | |||
if _, err = readInt16(r); err != nil { // refresh delay | |||
return | |||
} | |||
if _, err = readByte(r); err != nil { // type | |||
return | |||
} | |||
if _, err = readByte(r); err != nil { // fsk | |||
return | |||
} | |||
if frameCount, err = readInt16(r); err != nil { | |||
return | |||
} | |||
frames := int(frameCount) | |||
if frames < 0 { | |||
frames += 65536 | |||
} | |||
if version >= 2 { | |||
if err = s.readPalettesAndColors(r, a); err != nil { | |||
return | |||
} | |||
} | |||
if version >= 3 { | |||
if _, err = readByte(r); err != nil { // edit mode | |||
return | |||
} | |||
} | |||
if version >= 4 { | |||
if a.Width, err = readInt16Int(r); err != nil { | |||
return | |||
} | |||
if a.Height, err = readInt16Int(r); err != nil { | |||
return | |||
} | |||
} | |||
if version >= 5 { | |||
if err = s.readMasks(r, a); err != nil { | |||
return | |||
} | |||
} | |||
log.Printf("vni: reading %d frames for animation %q", frames, a.Name) | |||
a.Index = make([]int, frames) | |||
a.Frame = make([]*Frame, frames) | |||
for i := 0; i < frames; i++ { | |||
a.Index[i] = i | |||
if a.Frame[i], err = s.readFrame(r, a.Width, a.Height, version); err != nil { | |||
return | |||
} | |||
} | |||
return | |||
} | |||
func (s vniAnimationSet) readPalettesAndColors(r io.Reader, a *Animation) (err error) { | |||
if a.PaletteIndex, err = readInt16Int(r); err != nil { | |||
a.PaletteIndex = -1 | |||
return err | |||
} | |||
var colors int | |||
if colors, err = readInt16Int(r); err != nil { | |||
return | |||
} | |||
if colors <= 0 { | |||
log.Printf("vni: no colors defined, palette index %d", a.PaletteIndex) | |||
return | |||
} | |||
a.Palette = make(color.Palette, colors) | |||
for i := 0; i < colors; i++ { | |||
if a.Palette[i], err = readRGB24(r); err != nil { | |||
return | |||
} | |||
} | |||
log.Printf("vni: found %d colors for palette %d", colors, a.PaletteIndex) | |||
return | |||
} | |||
func (s vniAnimationSet) readMasks(r io.Reader, a *Animation) (err error) { | |||
var masks int | |||
if masks, err = readInt16Int(r); err != nil { | |||
return | |||
} | |||
a.Masks = make([][]byte, masks) | |||
for i := 0; i < masks; i++ { | |||
if _, err = readByte(r); err != nil { // locked | |||
return | |||
} | |||
var size int16 | |||
if size, err = readInt16(r); err |