commit
e4b3fd5943
23 changed files with 2109 additions and 0 deletions
@ -0,0 +1,159 @@ |
|||
package dmd |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"image" |
|||
"image/color" |
|||
"image/gif" |
|||
"io" |
|||
"log" |
|||
"strings" |
|||
"time" |
|||
) |
|||
|
|||
var ErrHeatShrink = errors.New("dmd: heat shrink compression is not supported") |
|||
|
|||
type Animation struct { |
|||
// Name of the animation.
|
|||
Name string |
|||
|
|||
// Width of the animation in pixels.
|
|||
Width int |
|||
|
|||
// Height of the animation in pixels.
|
|||
Height int |
|||
|
|||
// Palette is a custom palette.
|
|||
Palette color.Palette |
|||
|
|||
// PaletteIndex is the index of the custom palette.
|
|||
PaletteIndex int |
|||
|
|||
// Index of each frame, used in sequenced animations. If set to nil,
|
|||
// it is assumed this is a linear animation.
|
|||
Index []int |
|||
|
|||
// Frame data.
|
|||
Frame []*Frame |
|||
|
|||
// Palettes data.
|
|||
Palettes map[uint8]color.Palette // Frame palette (if any).
|
|||
|
|||
// Clock location data.
|
|||
Clock []*image.Point |
|||
|
|||
// ClockSmall displays a small clock for a given index.
|
|||
ClockSmall []bool |
|||
} |
|||
|
|||
func (a Animation) Duration() time.Duration { |
|||
var sum time.Duration |
|||
if a.Index == nil { |
|||
// Linear.
|
|||
for _, f := range a.Frame { |
|||
sum += f.Delay |
|||
} |
|||
} else { |
|||
// Sequenced.
|
|||
for _, i := range a.Index { |
|||
sum += a.Frame[i].Delay |
|||
} |
|||
} |
|||
return sum |
|||
} |
|||
|
|||
func (a Animation) GIF(coloring *Coloring, scale uint) *gif.GIF { |
|||
if scale <= 0 { |
|||
scale = 1 |
|||
} |
|||
|
|||
size := len(a.Index) |
|||
if size == 0 { |
|||
size = len(a.Frame) |
|||
} |
|||
|
|||
g := &gif.GIF{ |
|||
Image: make([]*image.Paletted, size), |
|||
Delay: make([]int, size), |
|||
} |
|||
|
|||
var palette color.Palette |
|||
if coloring != nil && coloring.Palette != nil { |
|||
palette = coloring.Palette.Colors |
|||
} else { |
|||
palette = Amber |
|||
} |
|||
|
|||
if len(a.Index) == 0 { |
|||
// Linear scan.
|
|||
for i, f := range a.Frame { |
|||
if coloring != nil { |
|||
if m, ok := coloring.Mappings[f.Checksum]; ok { |
|||
palette = coloring.Palettes[m.PaletteIndex].Colors |
|||
log.Printf("%s: frame %#08x using custom palette %v", a.Name, f.Checksum, palette) |
|||
} else { |
|||
log.Printf("%s: frame %#08x has no palette mapping", a.Name, f.Checksum) |
|||
} |
|||
} |
|||
g.Delay[i] = int(f.Delay / time.Millisecond / 10) |
|||
g.Image[i] = toPaletted(Panel{ |
|||
PalettedImage: &FrameImage{ |
|||
Frame: f, |
|||
Palette: palette, |
|||
}, |
|||
Dot: int(scale), |
|||
}, palette) |
|||
} |
|||
} else { |
|||
for j, i := range a.Index { |
|||
f := a.Frame[i] |
|||
if coloring != nil { |
|||
if m, ok := coloring.Mappings[f.Checksum]; ok { |
|||
palette = coloring.Palettes[m.PaletteIndex].Colors |
|||
log.Printf("%s: frame %#08x using custom palette %v", a.Name, f.Checksum, palette) |
|||
} else { |
|||
log.Printf("frame %#08x has no palette mapping", f.Checksum) |
|||
} |
|||
} |
|||
g.Delay[j] = int(f.Delay / time.Millisecond / 10) |
|||
g.Image[i] = toPaletted(Panel{ |
|||
PalettedImage: &FrameImage{ |
|||
Frame: f, |
|||
Palette: palette, |
|||
}, |
|||
Dot: int(scale), |
|||
}, palette) |
|||
} |
|||
} |
|||
|
|||
return g |
|||
} |
|||
|
|||
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 |
|||
} |
|||
|
|||
id := cString(magic[:]) |
|||
switch { |
|||
case id == "ANIM": |
|||
return readPin2DMDAnimations(reader) |
|||
|
|||
case id == "VPIN": |
|||
return readVPINAnimations(reader) |
|||
|
|||
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[:])) |
|||
} |
|||
} |
@ -0,0 +1,342 @@ |
|||
package dmd |
|||
|
|||
import ( |
|||
"bufio" |
|||
"bytes" |
|||
"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[uint8]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[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 = readBEString(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] = &Frame{ |
|||
Width: a.Width, |
|||
Height: 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 { |
|||
f.Hash = make([]byte, 4) |
|||
if _, err = io.ReadFull(r, f.Hash); err != nil { |
|||
return |
|||
} |
|||
} |
|||
|
|||
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 |
|||
} |
@ -0,0 +1,114 @@ |
|||
package dmd |
|||
|
|||
import ( |
|||
"io" |
|||
"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) |
|||
|
|||
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 |
|||
} |
|||
|
|||
// Read interleaved (4-bits) palette indexes.
|
|||
if _, err = io.ReadFull(rs, b); err != nil { |
|||
return |
|||
} |
|||
|
|||
// Deinterleave.
|
|||
p := &Plane{ |
|||
Data: make([]byte, a.Width*a.Height), |
|||
} |
|||
for j, v := range b { |
|||
q := j << 1 |
|||
p.Data[q+0] = v >> 4 |
|||
p.Data[q+1] = v & 0x0f |
|||
} |
|||
|
|||
a.Frame = append(a.Frame, &Frame{ |
|||
Delay: delays[i], |
|||
Bits: 4, |
|||
Plane: []*Plane{p}, |
|||
}) |
|||
} |
|||
|
|||
return |
|||
} |
@ -0,0 +1,206 @@ |
|||
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, 1)); 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 TestReadVNIAnimations(t *testing.T) { |
|||
tests := []struct { |
|||
Name string |
|||
Palette string |
|||
}{ |
|||
{ |
|||
Name: "ID4_1.00.vni", |
|||
Palette: "ID4_1.00.pal", |
|||
}, |
|||
{ |
|||
Name: "sprk_103.vni", |
|||
Palette: "sprk_103.pal", |
|||
}, |
|||
} |
|||
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)) |
|||
|
|||
var c *Coloring |
|||
if test.Palette != "" { |
|||
if c, err = LoadColoring(filepath.Join("testdata", test.Palette)); err != nil { |
|||
it.Fatal(err) |
|||
} |
|||
it.Logf("%s: coloring: %d palettes, %d mappings", test.Name, len(c.Palettes), len(c.Mappings)) |
|||
} |
|||
|
|||
for i, a := range all { |
|||
it.Logf("%s: animation %d: %d frames, duration %s", test.Name, i, len(a.Frame), a.Duration()) |
|||
if len(a.Frame) > 1 { |
|||
/* |
|||
for _, f := range a.Frame { |
|||
for _, p := range f.Plane { |
|||
dumpPlane(p.Data, f.Width/8) |
|||
} |
|||
} |
|||
*/ |
|||
testSaveGIF(t, test.Name, a, c) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
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() |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,238 @@ |
|||
package dmd |
|||
|
|||
import ( |
|||
"bufio" |
|||
"encoding/binary" |
|||
"image/color" |
|||
"io" |
|||
"io/ioutil" |
|||
"log" |
|||
"time" |
|||
) |
|||
|
|||
type vniAnimationHeader struct { |
|||
Cycles int16 |
|||
Hold int16 |
|||
ClockFrom int16 |
|||
ClockSmall uint8 |
|||
ClockInFront uint8 |
|||
ClockOffsetX int16 |
|||
ClockOffsetY int16 |
|||
RefreshDelay uint16 |
|||
Type uint8 |
|||
Fsk uint8 |
|||
Frames uint16 |
|||
} |
|||
|
|||
func readVPINAnimations(reader io.Reader) (all []*Animation, err error) { |
|||
var ( |
|||
r = bufferedReader(reader) |
|||
version uint16 |
|||
nAnimations int16 |
|||
) |
|||
if err = binary.Read(r, binary.BigEndian, &version); err != nil { |
|||
return nil, err |
|||
} |
|||
if err = binary.Read(r, binary.BigEndian, &nAnimations); err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
if version >= 2 { |
|||
// Skip animation indexes (why?)
|
|||
log.Printf("skipping %d bytes of animation indexes", int(nAnimations)*4) |
|||
if _, err = io.Copy(ioutil.Discard, io.LimitReader(r, int64(nAnimations)*4)); err != nil { |
|||
return |
|||
} |
|||
} |
|||
|
|||
log.Printf("VPI file version %d, %d animations", version, nAnimations) |
|||
|
|||
for i := 0; i < int(nAnimations); i++ { |
|||
a := new(Animation) |
|||
if err = a.readVPIData(r, int(version)); err != nil { |
|||
return |
|||
} |
|||
all = append(all, a) |
|||
} |
|||
return all, nil |
|||
} |
|||
|
|||
func (a *Animation) readVPIData(r *bufio.Reader, version int) (err error) { |
|||
var ( |
|||
nameLength uint16 |
|||
nameBytes []byte |
|||
header vniAnimationHeader |
|||
) |
|||
if err = binary.Read(r, binary.BigEndian, &nameLength); err != nil { |
|||
return |
|||
} |
|||
if nameLength > 0 { |
|||
nameBytes = make([]byte, nameLength) |
|||
if _, err = io.ReadFull(r, nameBytes); err != nil { |
|||
return |
|||
} |
|||
a.Name = cString(nameBytes) |
|||
} |
|||
if err = binary.Read(r, binary.BigEndian, &header); err != nil { |
|||
return |
|||
} |
|||
|
|||
log.Printf("reading VNI animation %q version %d: %#+v", a.Name, version, header) |
|||
|
|||
if version >= 2 { |
|||
// Read palette data.
|
|||
if a.PaletteIndex, a.Palette, err = readVPIPalette(r); err != nil { |
|||
return |
|||
} |
|||
log.Printf("%s: palette index %d, custom palette: %t (%d)", a.Name, a.PaletteIndex, a.Palette != nil, len(a.Palette)) |
|||
} |
|||
if version >= 3 { |
|||
// Edit mode.
|
|||
if _, err = r.ReadByte(); err != nil { |
|||
return |
|||
} |
|||
} |
|||
if version >= 4 { |
|||
if a.Width, a.Height, err = readVPIDimensions(r); err != nil { |
|||
return |
|||
} |
|||
} |
|||
if err = readVPIFrames(r, a, int(header.Frames), version); err != nil { |
|||
return |
|||
} |
|||
|
|||
// linear indexes
|
|||
a.Index = make([]int, len(a.Frame)) |
|||
for i := range a.Frame { |
|||
a.Index[i] = i |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
func readVPIFrames(r *bufio.Reader, a *Animation, n int, version int) (err error) { |
|||
for i := 0; i < n; i++ { |
|||
f := &Frame{ |
|||
Width: a.Width, |
|||
Height: a.Height, |
|||
} |
|||
if err = f.readVNIData(r, version); err != nil { |
|||
return |
|||
} |
|||
a.Frame = append(a.Frame, f) |
|||
} |
|||
return |
|||
} |
|||
|
|||
func readVPIDimensions(r *bufio.Reader) (int, int, error) { |
|||
var w, h uint16 |
|||
if err := binary.Read(r, binary.BigEndian, &w); err != nil { |
|||
return 0, 0, err |
|||
} |
|||
if err := binary.Read(r, binary.BigEndian, &h); err != nil { |
|||
return 0, 0, err |
|||
} |
|||
return int(w), int(h), nil |
|||
} |
|||
|
|||
func readVPIPalette(r *bufio.Reader) (int, color.Palette, error) { |
|||
var i, n int16 |
|||
if err := binary.Read(r, binary.BigEndian, &i); err != nil { |
|||
return -1, nil, err |
|||
} |
|||
if err := binary.Read(r, binary.BigEndian, &n); err != nil { |
|||
return -1, nil, err |
|||
} |
|||
if n <= 0 { |
|||
return int(i), nil, nil |
|||
} |
|||
|
|||
palette := make(color.Palette, n) |
|||
triplets := make([]byte, n*3) |
|||
if _, err := io.ReadFull(r, triplets); err != nil { |
|||
return int(i), nil, err |
|||
} |
|||
for j := 0; j < int(n); j++ { |
|||
o := j * 3 |
|||
palette[j] = color.RGBA{ |
|||
R: triplets[o+0], |
|||
G: triplets[o+1], |
|||
B: triplets[o+2], |
|||
A: 0xff, |
|||
} |
|||
} |
|||
return int(i), palette, nil |
|||
} |
|||
|
|||
func (f *Frame) readVNIData(r *bufio.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 |
|||
if version >= 4 { |
|||
f.Hash = make([]byte, 4) |
|||
if _, err = io.ReadFull(r, f.Hash); err != nil { |
|||
return |
|||
} |
|||
f.Checksum = binary.BigEndian.Uint32(f.Hash) |
|||
log.Printf("frame checksum: %#08x", f.Checksum) |
|||
} |
|||
if f.Bits, err = r.ReadByte(); err != nil { |
|||
return |
|||
} |
|||
|
|||
if version < 3 { |
|||
return f.readVNIPlanes(r, int(planeSize)) |
|||
} |
|||
return f.readVNIPlanesCompressed(r, int(planeSize)) |
|||
} |
|||
|
|||
func (f *Frame) readVNIPlanes(r *bufio.Reader, n int) (err error) { |
|||
for i := 0; i < int(f.Bits); i++ { |
|||
var marker byte |
|||
if marker, err = r.ReadByte(); err != nil { |
|||
return |
|||
} |
|||
if marker == 0x6d { |
|||
f.Mask = make([]byte, n) |
|||
if _, err = io.ReadFull(r, f.Mask); err != nil { |
|||
return |
|||
} |
|||
} else { |
|||
p := &Plane{ |
|||
Marker: marker, |
|||
} |
|||
if err = p.readVNIData(r, n, marker); err != nil { |
|||
return |
|||
} |
|||
f.Plane = append(f.Plane, p) |
|||
} |
|||
} |
|||
return |
|||
} |
|||
|
|||
func (f *Frame) readVNIPlanesCompressed(r *bufio.Reader, n int) (err error) { |
|||
var compressed byte |
|||
if compressed, err = r.ReadByte(); err != nil { |
|||
return |
|||
} else if compressed == 0 { |
|||
return f.readVNIPlanes(r, n) |
|||
} |
|||
return ErrHeatShrink |
|||
} |
|||
|
|||
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 |
|||
} |
@ -0,0 +1,65 @@ |
|||
package dmd |
|||
|
|||
import ( |
|||
"image" |
|||
"image/color" |
|||
) |
|||
|
|||
// Panel emulates a dot-matrix display panel.
|
|||
type Panel struct { |
|||
image.PalettedImage |
|||
Dot int |
|||
Square bool |
|||
} |
|||
|
|||
func MakePanel(p image.PalettedImage, size int) Panel { |
|||
return Panel{ |
|||
PalettedImage: p, |
|||
Dot: size, |
|||
} |
|||
} |
|||
|
|||
func (p Panel) At(x, y int) color.Color { |
|||
if p.Dot <= 1 { |
|||
return p.PalettedImage.At(x, y) |
|||
} |
|||
if !p.Square { |
|||
r1 := p.Dot / 2 |
|||
xx := (x % p.Dot) - r1 |
|||
x2 := xx * xx |
|||
yy := (y % p.Dot) - r1 |
|||
y2 := yy * yy |
|||
r2 := r1 * r1 |
|||
if x2+y2-r2 >= 0 { |
|||
return color.Transparent |
|||
} |
|||
} |
|||
x /= p.Dot |
|||
y /= p.Dot |
|||
return p.PalettedImage.At(x, y) |
|||
} |
|||
|
|||
func (p Panel) ColorIndexAt(x, y int) uint8 { |
|||
if p.Dot <= 1 { |
|||
return p.PalettedImage.ColorIndexAt(x, y) |
|||
} |
|||
if !p.Square { |
|||
xx := x % p.Dot |
|||
yy := y % p.Dot |
|||
if xx*xx+yy*yy >= p.Dot*p.Dot { |
|||
return 0 |
|||
} |
|||
} |
|||
x /= p.Dot |
|||
y /= p.Dot |
|||
return p.PalettedImage.ColorIndexAt(x, y) |
|||
} |
|||
|
|||
func (p Panel) Bounds() image.Rectangle { |
|||
b := p.PalettedImage.Bounds() |
|||
b.Min.X *= p.Dot |
|||
b.Min.Y *= p.Dot |
|||
b.Max.X *= p.Dot |
|||
b.Max.Y *= p.Dot |
|||
return b |
|||
} |
@ -0,0 +1,7 @@ |
|||
/* |
|||
Package dmd implements well-known formats for dot-matrix display (DMD) art. |
|||
|
|||
This dot-matrix display (DMD) image formats are most often used in arcade |
|||
machines, most notable pinball machines. |
|||
*/ |
|||
package dmd |
@ -0,0 +1,97 @@ |
|||
package dmd |
|||
|
|||
import ( |
|||
"image" |
|||
"image/color" |
|||
"time" |
|||
) |
|||
|
|||
type Frame struct { |
|||
Width, Height int |
|||
Delay time.Duration |
|||
Bits uint8 // Bit depth of the plane(s).
|
|||
Plane []*Plane |
|||
PlaneData [][]byte |
|||
Mask []byte |
|||
Hash []byte |
|||
Checksum uint32 |
|||
} |
|||
|
|||
func (f *Frame) isOutOfBounds(x, y int) bool { |
|||
return x < 0 || y < 0 || x >= f.Width || y >= f.Height |
|||
} |
|||
|
|||
func (f *Frame) isMaskRelevant() bool { |
|||
// (this.drawMask & 0x1) != 0x0 && this.frame.hasMask()
|
|||
return len(f.Mask) > 0 |
|||
} |
|||
|
|||
const drawMask = 0x7fffff |
|||
|
|||
func (f *Frame) ColorIndexAt(x, y int) uint8 { |
|||
if f.isOutOfBounds(x, y) { |
|||
return 0 |
|||
} |
|||
|
|||
var ( |
|||
mask = uint8(128 >> (x % 8)) |
|||
index uint8 |
|||
bytesPerRow = f.Width >> 3 |
|||
) |
|||
//log.Printf("x:%d y:%d mask:%#08b bytesPerRow:%d", x, y, mask, bytesPerRow)
|
|||
if f.isMaskRelevant() { |
|||
if f.Mask[(x/8)+y*bytesPerRow]&mask != 0 { |
|||
index |= 1 |
|||
} |
|||
} |
|||
var ( |
|||
draw = drawMask >> 1 |
|||
) |
|||
for i, plane := range f.Plane { |
|||
if ((1 << i) & draw) != 0 { |
|||
if plane.Data[(x/8)+y*bytesPerRow]&mask != 0 { |
|||
index |= 1 << i |
|||
} |
|||
} |
|||
} |
|||
return index |
|||
} |
|||
|
|||
type FrameImage struct { |
|||
*Frame |
|||
color.Palette |
|||
} |
|||
|
|||
func (i *FrameImage) At(x, y int) color.Color { |
|||
c := i.ColorIndexAt(x, y) |
|||
if i.Palette == nil { |
|||
return defaultPalettes[0][c] |
|||
} |
|||
return i.Palette[c] |
|||
} |
|||
|
|||
func (i *FrameImage) Bounds() image.Rectangle { |
|||
switch len(i.Frame.Plane[0].Data) { |
|||
case 1568: |
|||
return image.Rect(0, 0, 196, 64) |
|||
case 512: |
|||
fallthrough |
|||
default: |
|||
return image.Rect(0, 0, 128, 32) |
|||
} |
|||
} |
|||
|
|||
func (i *FrameImage) ColorIndexAt(x, y int) uint8 { |
|||
return i.Frame.ColorIndexAt(x, y) |
|||
} |
|||
|
|||
func (i *FrameImage) ColorModel() color.Model { |
|||
return i.Palette |
|||
} |
|||
|
|||
type Plane struct { |
|||
Marker byte |
|||
Data []byte |
|||
} |
|||
|
|||
var _ image.PalettedImage = (*FrameImage)(nil) |
@ -0,0 +1,3 @@ |
|||
module maze.io/x/dmd |
|||
|
|||
go 1.14 |
@ -0,0 +1,363 @@ |
|||
package heatshrink |
|||
|
|||
import ( |
|||
"bytes" |
|||
"errors" |
|||
) |
|||
|
|||
const ( |
|||
minWindowBits = 4 |
|||
maxWindowBits = 15 |
|||
minLookaheadBits = 3 |
|||
) |
|||
|
|||
/* States for the polling state machine. */ |
|||
const ( |
|||
stateTagBit = iota /* tag bit */ |
|||
stateYieldLiteral /* ready to yield literal byte */ |
|||
stateBackrefIndexMSB /* most significant byte of index */ |
|||
stateBackrefIndexLSB /* least significant byte of index */ |
|||
stateBackrefCountMSB /* most significant byte of count */ |
|||
stateBackrefCountLSB /* least significant byte of count */ |
|||
stateYieldBackref /* ready to yield back-reference */ |
|||
) |
|||
|
|||
const ( |
|||
sinkOK = 0 /* data sunk, ready to poll */ |
|||
sinkFull = 1 /* out of space in internal buffer */ |
|||
|
|||
pollEmpty = 0 /* input exhausted */ |
|||
pollMore = 1 /* more data remaining, call again w/ fresh output buffer */ |
|||
pollErrorUnknown = -2 |
|||
|
|||
finishDone = 0 /* output is done */ |
|||
finishMore = 1 /* more output remains */ |
|||
) |
|||
|
|||
const ( |
|||
noBits = uint16(0xffff) |
|||
) |
|||
|
|||
type decoder struct { |
|||
inputSize uint16 /* bytes in input buffer */ |
|||
inputIndex uint16 /* offset to next unprocessed input byte */ |
|||
outputCount uint16 /* how many bytes to output */ |
|||
outputIndex uint16 /* index for bytes to output */ |
|||
headIndex uint16 /* head of window buffer */ |
|||
state uint8 /* current state machine node */ |
|||
currentByte uint8 /* current byte of input */ |
|||
bitIndex uint8 /* current bit index */ |
|||
|
|||
/* Fields that are only used if dynamically allocated. */ |
|||
windowSz2 uint8 /* window buffer bits */ |
|||
lookaheadSz2 uint8 /* lookahead bits */ |
|||
|
|||
/* Input buffer, then expansion window buffer */ |
|||
decbuf []byte |
|||
inbuf []byte |
|||
outbuf bytes.Buffer |
|||
} |
|||
|
|||
func Decompress(window, lookahead uint8, data []byte) ([]byte, error) { |
|||
var ( |
|||
hsd = newDecoder(window, lookahead) |
|||
size = len(data) |
|||
read int |
|||
) |
|||
for { |
|||
_, tmp := hsd.sink(data[read:]) |
|||
read += int(tmp) |
|||
if _, err := hsd.poll(); err != nil { |
|||
return nil, err |
|||
} |
|||
if read == size { |
|||
if hsd.finish() == finishDone { |
|||
break |
|||
} |
|||
} |
|||
} |
|||
return hsd.outbuf.Bytes(), nil |
|||
} |
|||
|
|||
func newDecoder(windowSz2, lookaheadSz2 uint8) *decoder { |
|||
if (windowSz2 < minWindowBits) || |
|||
(windowSz2 > maxWindowBits) || |
|||
(lookaheadSz2 < minLookaheadBits) || |
|||
(lookaheadSz2 >= windowSz2) { |
|||
return nil |
|||
} |
|||
hsd := &decoder{ |
|||
windowSz2: windowSz2, |
|||
lookaheadSz2: lookaheadSz2, |
|||
decbuf: make([]byte, 1<<windowSz2), |
|||
inbuf: make([]byte, 65535), |
|||
} |
|||
hsd.reset() |
|||
return hsd |
|||
} |
|||
|
|||
func (hsd *decoder) reset() { |
|||
hsd.state = stateTagBit |
|||
hsd.inputSize = 0 |
|||
hsd.inputIndex = 0 |
|||
hsd.bitIndex = 0x00 |
|||
hsd.currentByte = 0x00 |
|||
hsd.outputCount = 0 |
|||
hsd.outputIndex = 0 |
|||
hsd.headIndex = 0 |
|||
hsd.outbuf.Reset() |
|||
} |
|||
|
|||
/* Copy SIZE bytes into the decoder's input buffer, if it will fit. */ |
|||
func (hsd *decoder) sink(data []byte) (result int, inputSize uint16) { |
|||
rem := uint16(len(hsd.inbuf)) - hsd.inputSize |
|||
if rem == 0 { |
|||
return sinkFull, 0 |
|||
} |
|||
|
|||
size := rem |
|||
if len(data) < int(size) { |
|||
size = uint16(len(data)) |
|||
} |
|||
//log.Printf("-- sinking %v bytes\n", size)
|
|||
/* copy into input buffer (at head of buffers) */ |
|||
copy(hsd.inbuf[hsd.inputSize:], data[:size]) |
|||
hsd.inputSize += size |
|||
return sinkOK, size |
|||
} |
|||
|
|||
func (hsd *decoder) poll() (int, error) { |
|||
for { |
|||
//log.Printf("-- poll, state is %v, inputSize %v\n", hsd.state, hsd.inputSize)
|
|||
var ( |
|||
inState = hsd.state |
|||
err error |
|||
) |
|||
switch inState { |
|||
case stateTagBit: |
|||
hsd.state = hsd.dstTagBit() |
|||
case stateYieldLiteral: |
|||
hsd.state = hsd.dstYieldLiteral() |
|||
case stateBackrefIndexMSB: |
|||
hsd.state, err = hsd.dstBackrefIndexMSB() |
|||
case stateBackrefIndexLSB: |
|||
hsd.state = hsd.dstBackrefIndexLSB() |
|||
case stateBackrefCountMSB: |
|||
hsd.state, err = hsd.dstBackrefCountMSB() |
|||
case stateBackrefCountLSB: |
|||
hsd.state = hsd.dstBackrefCountLSB() |
|||
case stateYieldBackref: |
|||
hsd.state = hsd.dstYieldBackref() |
|||
default: |
|||
return pollErrorUnknown, nil |
|||
} |
|||
if err != nil { |
|||
return 0, err |
|||
} |
|||
|
|||
/* If the current state cannot advance, check if input or output |
|||
* buffer are exhausted. */ |
|||
if hsd.state == inState { |
|||
return pollEmpty, nil |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (hsd *decoder) finish() int { |
|||
switch hsd.state { |
|||
case stateTagBit, |
|||
/* If we want to finish with no input, but are in these states, it's |
|||
* because the 0-bit padding to the last byte looks like a backref |
|||
* marker bit followed by all 0s for index and count bits. */ |
|||
stateBackrefIndexLSB, |
|||
stateBackrefIndexMSB, |
|||
stateBackrefCountLSB, |
|||
stateBackrefCountMSB, |
|||
|
|||
/* If the output stream is padded with 0xFFs (possibly due to being in |
|||
* flash memory), also explicitly check the input size rather than |
|||
* uselessly returning MORE but yielding 0 bytes when polling. */ |
|||
stateYieldLiteral: |
|||
if hsd.inputSize == 0 { |
|||
return finishDone |
|||
} else { |
|||
return finishMore |
|||
} |
|||
} |
|||
return finishMore |
|||
} |
|||
|
|||
func (hsd *decoder) dstTagBit() uint8 { |
|||
bits := hsd.getBits(1) // get tag bit
|
|||
if bits == noBits { |
|||
return stateTagBit |
|||
} else if bits > 0 { |
|||
return stateYieldLiteral |
|||
} else if hsd.windowSz2 > 8 { |
|||
return stateBackrefIndexMSB |
|||
} else { |
|||
hsd.outputIndex = 0 |
|||
return stateBackrefIndexLSB |
|||
} |
|||
} |
|||
|
|||
func (hsd *decoder) dstYieldLiteral() uint8 { |
|||
/* Emit a repeated section from the window buffer, and add it (again) |
|||
* to the window buffer. (Note that the repetition can include |
|||
* itself.)*/ |
|||
bits := hsd.getBits(8) |
|||
if bits == noBits { |
|||
return stateYieldLiteral |
|||
} /* out of input */ |
|||
mask := uint16(1<<hsd.windowSz2) - 1 |
|||
c := uint8(bits & 0xFF) |
|||
// log.Printf("-- emitting literal byte 0x%02x\n", c)
|
|||
hsd.decbuf[hsd.headIndex&mask] = c |
|||
hsd.headIndex++ |
|||
hsd.pushByte(c) |
|||
return stateTagBit |
|||
} |
|||
|
|||
func (hsd *decoder) dstBackrefIndexMSB() (uint8, error) { |
|||
bitCt := hsd.windowSz2 |
|||
if bitCt <= 8 { |
|||
return 0, errors.New("heatshrink: bit count failed") |
|||
} |
|||
bits := hsd.getBits(bitCt - 8) |
|||
// log.Printf("-- backref index (msb), got 0x%04x (+1)\n", bits)
|
|||
if bits == noBits { |
|||
return stateBackrefIndexMSB, nil |
|||
} |
|||
hsd.outputIndex = bits << 8 |
|||
return stateBackrefIndexLSB, nil |
|||
} |
|||
|
|||
func (hsd *decoder) dstBackrefIndexLSB() uint8 { |
|||
bitCt := hsd.windowSz2 |
|||
if bitCt > 8 { |
|||
bitCt = 8 |
|||
} |
|||
bits := hsd.getBits(bitCt) |
|||
// log.Printf("-- backref index (lsb), got 0x%04x (+1)\n", bits)
|
|||
if bits == noBits { |
|||
return stateBackrefIndexLSB |
|||
} |
|||
hsd.outputIndex |= bits |
|||
hsd.outputIndex++ |
|||
brBitCt := hsd.lookaheadSz2 |
|||
hsd.outputCount = 0 |
|||
if brBitCt > 8 { |
|||
return stateBackrefCountMSB |
|||
} else { |
|||
return stateBackrefCountLSB |
|||
} |
|||
} |
|||
|
|||
func (hsd *decoder) dstBackrefCountMSB() (uint8, error) { |
|||
brBitCt := hsd.lookaheadSz2 |
|||
if brBitCt <= 8 { |
|||
return 0, errors.New("heatshrink: bit count failed") |
|||
} |
|||
bits := hsd.getBits(brBitCt - 8) |
|||
// log.Printf("-- backref count (msb), got 0x%04x (+1)\n", bits)
|
|||
if bits == noBits { |
|||
return stateBackrefCountMSB, nil |
|||
} |
|||
hsd.outputCount = bits << 8 |
|||
return stateBackrefCountLSB, nil |
|||
} |
|||
|
|||
func (hsd *decoder) dstBackrefCountLSB() uint8 { |
|||
brBitCt := hsd.lookaheadSz2 |
|||
if brBitCt > 8 { |
|||
brBitCt = 8 |
|||
} |
|||
bits := hsd.getBits(brBitCt) |
|||
// log.Printf("-- backref count (lsb), got 0x%04x (+1)\n", bits)
|
|||
if bits == noBits { |
|||
return stateBackrefCountLSB |
|||
} |
|||
hsd.outputCount |= bits |
|||
hsd.outputCount++ |
|||
return stateYieldBackref |
|||
} |
|||
|
|||
func (hsd *decoder) dstYieldBackref() uint8 { |
|||
count := hsd.outputCount |
|||
mask := uint16(1<<hsd.windowSz2) - 1 |
|||
negOffset := hsd.outputIndex |
|||
// log.Printf("-- emitting %v bytes from -%v bytes back\n", count, negOffset)
|
|||
if negOffset > mask+1 { |
|||
// log.Fatal("neg_offset assert failed.")
|
|||
return 0 |
|||
} |
|||
if count > (1 << hsd.lookaheadSz2) { |
|||
// log.Fatal("count assert failed.")
|
|||
return 0 |
|||
} |
|||
|
|||
for i := uint16(0); i < count; i++ { |
|||
c := hsd.decbuf[(hsd.headIndex-negOffset)&mask] |
|||
hsd.pushByte(c) |
|||
hsd.decbuf[hsd.headIndex&mask] = c |
|||
hsd.headIndex++ |
|||
// log.Printf(" -- ++ 0x%02x\n", c)
|
|||
} |
|||
hsd.outputCount -= count |
|||
if hsd.outputCount == 0 { |
|||
return stateTagBit |
|||
} |
|||
return stateYieldBackref |
|||
} |
|||
|
|||
/* Get the next COUNT bits from the input buffer, saving incremental progress. |
|||
* Returns noBits on end of input, or if more than 15 bits are requested. */ |
|||
func (hsd *decoder) getBits(count uint8) uint16 { |
|||
accumulator := uint16(0) |
|||
if count > 15 { |
|||
return noBits |
|||
} |
|||
// log.Printf("-- popping %v bit(s)\n", count)
|
|||
|
|||
/* If we aren't able to get COUNT bits, suspend immediately, because we |
|||
* don't track how many bits of COUNT we've accumulated before suspend. */ |
|||
if hsd.inputSize == 0 { |
|||
if hsd.bitIndex < (1 << (count - 1)) { |
|||
return noBits |
|||
} |
|||
} |
|||
|
|||
for i := uint8(0); i < count; i++ { |
|||
if hsd.bitIndex == 0x00 { |
|||
if hsd.inputSize == 0 { |
|||
// log.Printf(" -- out of bits, suspending w/ accumulator of %v (0x%02x)\n", accumulator, accumulator)
|
|||
return noBits |
|||
} |
|||
hsd.currentByte = hsd.inbuf[hsd.inputIndex] |
|||
hsd.inputIndex++ |
|||
// log.Printf(" -- pulled byte 0x%02x\n", hsd.currentByte)
|
|||
if hsd.inputIndex == hsd.inputSize { |
|||
hsd.inputIndex = 0 /* input is exhausted */ |
|||
hsd.inputSize = 0 |
|||
} |
|||
hsd.bitIndex = 0x80 |
|||
} |
|||
accumulator <<= 1 |
|||
if hsd.currentByte&hsd.bitIndex > 0 { |
|||
accumulator |= 0x01 |
|||
} |
|||
hsd.bitIndex >>= 1 |
|||
} |
|||
|
|||
/* |
|||
if count > 1 { |
|||
log.Printf(" -- accumulated %08x\n", accumulator) |
|||
} |
|||
*/ |
|||
return accumulator |
|||
} |
|||
|
|||
func (hsd *decoder) pushByte(c uint8) { |
|||
// log.Printf(" -- pushing c: 0x%02x\n", c)
|
|||
hsd.outbuf.WriteByte(c) |
|||
} |
@ -0,0 +1,252 @@ |
|||
package dmd |
|||
|
|||
import ( |
|||
"bufio" |
|||
"encoding/binary" |
|||
"fmt" |
|||
"image" |
|||
"image/color" |
|||
"image/draw" |
|||
"io" |
|||
"io/ioutil" |
|||