|
|
@ -7,6 +7,7 @@ import ( |
|
|
|
"image" |
|
|
|
"io" |
|
|
|
"io/ioutil" |
|
|
|
"log" |
|
|
|
"time" |
|
|
|
|
|
|
|
"maze.io/x/dmd/bitmap/color" |
|
|
@ -14,7 +15,7 @@ import ( |
|
|
|
) |
|
|
|
|
|
|
|
// DecodeVPIN calls DecodeVPINWithColoring.
|
|
|
|
func DecodeVPIN(r io.Reader) (Animations, error) { |
|
|
|
func DecodeVPIN(r io.ReadSeeker) (Animations, error) { |
|
|
|
return DecodeVPINWithColoring(r, nil) |
|
|
|
} |
|
|
|
|
|
|
@ -23,7 +24,7 @@ func DecodeVPIN(r io.Reader) (Animations, error) { |
|
|
|
// Typically, these animation file are called "pin2dmd.vni" or "pin2dmd.ani". If you
|
|
|
|
// have a machine palette file ("pin2dmd.pal"), you can load the coloring definitions
|
|
|
|
// with LoadPin2DMDColoring / DecodePin2DMDColoring.
|
|
|
|
func DecodeVPINWithColoring(r io.Reader, c *Pin2DMDColoring) (Animations, error) { |
|
|
|
func DecodeVPINWithColoring(r io.ReadSeeker, c *Pin2DMDColoring) (Animations, error) { |
|
|
|
var ( |
|
|
|
magic [4]byte |
|
|
|
err error |
|
|
@ -58,7 +59,20 @@ func DecodeVPINWithColoring(r io.Reader, c *Pin2DMDColoring) (Animations, error) |
|
|
|
|
|
|
|
all := make([]Animation, animations) |
|
|
|
for i := 0; i < int(animations); i++ { |
|
|
|
if all[i], err = decodeVPNAnimation(r, version, c); err != nil { |
|
|
|
//if os.Getenv("DECODE") != "" {
|
|
|
|
if all[i], err = decodeVPNAnimation(i, r, version, c); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
//} else {
|
|
|
|
// all[i], err = parseVPINAnimation(i, r, version, c)
|
|
|
|
//}
|
|
|
|
/* |
|
|
|
if all[i], err = parseVPINAnimation(r, version, c); err != nil { |
|
|
|
if all[i], err = decodeVPNAnimation(r, version, c); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
*/ |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
@ -79,7 +93,247 @@ type vpinAnimationHeader struct { |
|
|
|
Frames int16 |
|
|
|
} |
|
|
|
|
|
|
|
func decodeVPNAnimation(r io.Reader, version int16, c *Pin2DMDColoring) (Animation, error) { |
|
|
|
type vpinAnimation struct { |
|
|
|
n int |
|
|
|
r io.ReadSeeker |
|
|
|
width, height int |
|
|
|
name string |
|
|
|
header vpinAnimationHeader |
|
|
|
frame *image.Paletted |
|
|
|
frames int |
|
|
|
pos int |
|
|
|
info []*vpinFrameInfo |
|
|
|
palette color.Palette |
|
|
|
} |
|
|
|
|
|
|
|
type vpinFrameInfo struct { |
|
|
|
header vpinFrameHeader |
|
|
|
offset int64 |
|
|
|
compressedSize int32 |
|
|
|
bits uint8 |
|
|
|
} |
|
|
|
|
|
|
|
func (a vpinAnimation) Name() string { return a.name } |
|
|
|
func (a vpinAnimation) Len() int { return a.frames } |
|
|
|
func (a vpinAnimation) Size() (width, height int) { return a.width, a.height } |
|
|
|
func (a vpinAnimation) IsMask() bool { return a.palette == nil || isMask(a.palette) } |
|
|
|
|
|
|
|
func (a vpinAnimation) ClockInFront() bool { return a.header.ClockInFront } |
|
|
|
|
|
|
|
func (a vpinAnimation) ClockOffset() image.Point { |
|
|
|
return image.Pt(int(a.header.ClockOffsetX), int(a.header.ClockOffsetY)) |
|
|
|
} |
|
|
|
|
|
|
|
func (a *vpinAnimation) Duration() (total time.Duration) { |
|
|
|
log.Printf("vpin %s: %+v", a.name, a.info) |
|
|
|
for _, info := range a.info { |
|
|
|
total += time.Duration(info.header.Delay) |
|
|
|
} |
|
|
|
log.Printf("vpin: duration %d -> %s", int64(total), total*time.Millisecond) |
|
|
|
return total * time.Millisecond |
|
|
|
} |
|
|
|
|
|
|
|
func (a *vpinAnimation) NextFrame() (frame image.Image, delay time.Duration, err error) { |
|
|
|
if a.pos+1 > a.frames { |
|
|
|
log.Printf("dmd: VPIN animation %d next %d is beyond %d: EOF on %#+v", a.n, a.pos+1, a.frames, a) |
|
|
|
return nil, 0, io.EOF |
|
|
|
} |
|
|
|
|
|
|
|
info := a.info[a.pos] |
|
|
|
log.Printf("vpin: next frame, pos:%d frames:%d info@%p:%+v", a.pos, a.frames, info, info) |
|
|
|
a.pos++ |
|
|
|
log.Printf("vpin: animation %d seek to %d", a.n, info.offset) |
|
|
|
if _, err = a.r.Seek(info.offset, io.SeekStart); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
//var header vpinFrameHeader
|
|
|
|
//if err = binary.Read(a.r, binary.BigEndian, &header); err != nil {
|
|
|
|
// return
|
|
|
|
//}
|
|
|
|
|
|
|
|
if info.compressedSize > 0 { |
|
|
|
var ( |
|
|
|
comp = make([]byte, info.compressedSize) |
|
|
|
data []byte |
|
|
|
) |
|
|
|
if _, err = io.ReadFull(a.r, comp); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
if data, err = heatshrink.Decompress(10, 0, comp); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
if err = decodeVPINFramePlanes(bytes.NewReader(data), a.frame, info.bits, info.header.PlaneSize, nil); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
} else if err = decodeVPINFramePlanes(a.r, a.frame, info.bits, info.header.PlaneSize, nil); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
a.frame.Palette = a.palette |
|
|
|
return a.frame, time.Duration(info.header.Delay) * time.Millisecond, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (a *vpinAnimation) SeekFrame(pos int) error { |
|
|
|
if pos < 0 || pos >= a.frames { |
|
|
|
return ErrSeek |
|
|
|
} |
|
|
|
a.pos = pos |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
type vpinFrameHeader struct { |
|
|
|
PlaneSize int16 |
|
|
|
Delay uint16 |
|
|
|
} |
|
|
|
|
|
|
|
func parseVPINAnimation(n int, r io.ReadSeeker, version int16, c *Pin2DMDColoring) (Animation, error) { |
|
|
|
var ( |
|
|
|
a = &vpinAnimation{ |
|
|
|
n: n, |
|
|
|
r: r, |
|
|
|
} |
|
|
|
nameLen int16 |
|
|
|
name []byte |
|
|
|
offset int64 |
|
|
|
err error |
|
|
|
) |
|
|
|
if err = binary.Read(r, binary.BigEndian, &nameLen); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
if nameLen > 0 { |
|
|
|
if name, err = ioutil.ReadAll(io.LimitReader(r, int64(nameLen))); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
a.name = string(name) |
|
|
|
} |
|
|
|
if err = binary.Read(r, binary.BigEndian, &a.header); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
offset, _ = r.Seek(0, io.SeekCurrent) |
|
|
|
log.Printf("position after header of animation %d: %d", n, offset) |
|
|
|
|
|
|
|
a.frames = int(a.header.Frames) |
|
|
|
if a.frames < 0 { |
|
|
|
a.frames += 65336 |
|
|
|
} |
|
|
|
a.info = make([]*vpinFrameInfo, a.frames) |
|
|
|
|
|
|
|
if version >= 2 { |
|
|
|
var index int |
|
|
|
if index, err = decodeVPINPalettesAndColors(r, &a.palette); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
if index > 0 && c != nil { |
|
|
|
// log.Printf("using palette index %d", index)
|
|
|
|
a.palette = c.Palettes[index].Colors |
|
|
|
} |
|
|
|
} |
|
|
|
if a.palette == nil { |
|
|
|
a.palette = Mask |
|
|
|
} |
|
|
|
|
|
|
|
if version >= 3 { |
|
|
|
var b [1]byte |
|
|
|
if _, err = io.ReadFull(r, b[:]); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if version >= 4 { |
|
|
|
var size struct { |
|
|
|
Width, Height int16 |
|
|
|
} |
|
|
|
if err = binary.Read(r, binary.BigEndian, &size); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
a.width = int(size.Width) |
|
|
|
a.height = int(size.Height) |
|
|
|
} else { |
|
|
|
a.width = 128 |
|
|
|
a.height = 32 |
|
|
|
} |
|
|
|
a.frame = image.NewPaletted(image.Rectangle{Max: image.Point{X: a.width, Y: a.height}}, nil) |
|
|
|
|
|
|
|
if version >= 5 { |
|
|
|
var skip int16 |
|
|
|
if err = binary.Read(r, binary.BigEndian, &skip); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
if _, err = io.Copy(ioutil.Discard, io.LimitReader(r, int64(skip)*3)); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if offset, err = r.Seek(0, io.SeekCurrent); err != nil { |
|
|
|
return nil, fmt.Errorf("dmd: error getting current VPiN position: %w", err) |
|
|
|
} |
|
|
|
log.Println("position before frames:", offset) |
|
|
|
for i := 0; i < a.frames; i++ { |
|
|
|
var ( |
|
|
|
header vpinFrameHeader |
|
|
|
bits uint8 |
|
|
|
frameStart int64 |
|
|
|
compressedSize int32 |
|
|
|
) |
|
|
|
if _, err = r.Seek(offset, io.SeekStart); err != nil { |
|
|
|
return nil, fmt.Errorf("dmd: error skipping to VPIN animation %d: %w", i+1, err) |
|
|
|
} |
|
|
|
if err = binary.Read(r, binary.BigEndian, &header); err != nil { |
|
|
|
return nil, fmt.Errorf("dmd: error reading VPIN animation %d header: %w", i+1, err) |
|
|
|
} |
|
|
|
|
|
|
|
offset += int64(binary.Size(header)) |
|
|
|
if version >= 4 { |
|
|
|
if _, err = io.Copy(ioutil.Discard, io.LimitReader(r, 4)); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
offset += 4 // checksum
|
|
|
|
} |
|
|
|
|
|
|
|
if err = binary.Read(r, binary.BigEndian, &bits); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
offset++ |
|
|
|
|
|
|
|
var compressed bool |
|
|
|
if version >= 3 { |
|
|
|
if err = binary.Read(r, binary.BigEndian, &compressed); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
offset++ // compressed flag
|
|
|
|
} |
|
|
|
frameStart = offset |
|
|
|
log.Printf("frame header: %+v, bits %d, offset %d", header, bits, frameStart) |
|
|
|
|
|
|
|
if compressed { |
|
|
|
if err = binary.Read(r, binary.BigEndian, &compressedSize); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
offset += 4 |
|
|
|
log.Println("position before compressed frame", i+1, ":", offset) |
|
|
|
offset += int64(compressedSize) |
|
|
|
} else { |
|
|
|
log.Println("position before frame", i+1, ":", offset) |
|
|
|
offset += int64(bits) * int64(header.PlaneSize+1) |
|
|
|
} |
|
|
|
log.Printf("delay of frame %d: %s", i+1, time.Duration(header.Delay)*time.Millisecond) |
|
|
|
log.Println("position after frame", i+1, ":", offset) |
|
|
|
a.info[i] = &vpinFrameInfo{ |
|
|
|
header: header, |
|
|
|
offset: frameStart, |
|
|
|
compressedSize: compressedSize, |
|
|
|
bits: bits, |
|
|
|
} |
|
|
|
log.Printf("info at %d/%d (%p): %+v", i, a.frames, a.info[i], a.info[i]) |
|
|
|
} |
|
|
|
if _, err = r.Seek(offset, io.SeekStart); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
return a, nil |
|
|
|
} |
|
|
|
|
|
|
|
func decodeVPNAnimation(n int, r io.ReadSeeker, version int16, c *Pin2DMDColoring) (Animation, error) { |
|
|
|
var ( |
|
|
|
a = &imageAnimation{ |
|
|
|
width: 128, |
|
|
@ -109,6 +363,8 @@ func decodeVPNAnimation(r io.Reader, version int16, c *Pin2DMDColoring) (Animati |
|
|
|
X: int(header.ClockOffsetX), |
|
|
|
Y: int(header.ClockOffsetY), |
|
|
|
} |
|
|
|
//offset, _ := r.Seek(0, io.SeekCurrent)
|
|
|
|
//log.Printf("position after header of animation %d: %d", n, offset)
|
|
|
|
/* |
|
|
|
if a.clockInFront || a.clockOffset.X+a.clockOffset.Y != 0 { |
|
|
|
log.Printf("vpin: version %d animation %q header: %+v", version, a.name, header) |
|
|
@ -122,7 +378,7 @@ func decodeVPNAnimation(r io.Reader, version int16, c *Pin2DMDColoring) (Animati |
|
|
|
|
|
|
|
if version >= 2 { |
|
|
|
var index int |
|
|
|
if index, err = decodeVPINPalettesAndColors(r, a); err != nil { |
|
|
|
if index, err = decodeVPINPalettesAndColors(r, &a.palette); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
if index > 0 && c != nil { |
|
|
@ -152,23 +408,31 @@ func decodeVPNAnimation(r io.Reader, version int16, c *Pin2DMDColoring) (Animati |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//offset, _ = r.Seek(0, io.SeekCurrent)
|
|
|
|
//log.Println("position before frames:", offset)
|
|
|
|
|
|
|
|
a.delay = make([]time.Duration, frames) |
|
|
|
a.frame = make([]image.Image, frames) |
|
|
|
for i := 0; i < frames; i++ { |
|
|
|
//offset, _ := r.(io.Seeker).Seek(0, io.SeekCurrent)
|
|
|
|
//log.Printf("position before frame %d: %d", i+1, offset)
|
|
|
|
if a.frame[i], a.delay[i], err = decodeVPINFrame(r, a.width, a.height, version, c); err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
//log.Printf("delay of frame %d: %s", i+1, a.delay[i])
|
|
|
|
if a.frame[i].(*image.Paletted).Palette == nil { |
|
|
|
a.frame[i].(*image.Paletted).Palette = a.palette |
|
|
|
} else { |
|
|
|
// Key frame switched to custom palette.
|
|
|
|
a.palette = a.frame[i].(*image.Paletted).Palette |
|
|
|
} |
|
|
|
//offset, _ = r.(io.Seeker).Seek(0, io.SeekCurrent)
|
|
|
|
//log.Printf("position before frame %d: %d", i+1, offset)
|
|
|
|
} |
|
|
|
return a, nil |
|
|
|
} |
|
|
|
|
|
|
|
func decodeVPINPalettesAndColors(r io.Reader, a *imageAnimation) (index int, err error) { |
|
|
|
func decodeVPINPalettesAndColors(r io.Reader, p *color.Palette) (index int, err error) { |
|
|
|
var header struct { |
|
|
|
Index int16 |
|
|
|
Colors int16 |
|
|
@ -182,9 +446,9 @@ func decodeVPINPalettesAndColors(r io.Reader, a *imageAnimation) (index int, err |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
a.palette = make(color.Palette, header.Colors) |
|
|
|
*p = make(color.Palette, header.Colors) |
|
|
|
for i := 0; i < int(header.Colors); i++ { |
|
|
|
if a.palette[i], err = color.DecodeRGB24(r); err != nil { |
|
|
|
if (*p)[i], err = color.DecodeRGB24(r); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
@ -213,11 +477,8 @@ func decodeVPINMasks(r io.Reader, a *imageAnimation) (err error) { |
|
|
|
|
|
|
|
func decodeVPINFrame(r io.Reader, width, height int, version int16, c *Pin2DMDColoring) (frame *image.Paletted, delay time.Duration, err error) { |
|
|
|
var ( |
|
|
|
header struct { |
|
|
|
PlaneSize int16 |
|
|
|
Delay uint16 |
|
|
|
} |
|
|
|
bits uint8 |
|
|
|
header vpinFrameHeader |
|
|
|
bits uint8 |
|
|
|
) |
|
|
|
if err = binary.Read(r, binary.BigEndian, &header); err != nil { |
|
|
|
return |
|
|
@ -234,6 +495,8 @@ func decodeVPINFrame(r io.Reader, width, height int, version int16, c *Pin2DMDCo |
|
|
|
if err = binary.Read(r, binary.BigEndian, &bits); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
//offset, _ := r.(io.Seeker).Seek(0, io.SeekCurrent)
|
|
|
|
//log.Printf("frame header: %+v, bits %d, offset %d", header, bits, offset)
|
|
|
|
|
|
|
|
//frame := NewRGBA16Image(image.Rect(0, 0, width, height))
|
|
|
|
frame = image.NewPaletted(image.Rect(0, 0, width, height), nil) |
|
|
@ -254,6 +517,8 @@ func decodeVPINFrame(r io.Reader, width, height int, version int16, c *Pin2DMDCo |
|
|
|
if err = binary.Read(r, binary.BigEndian, &size); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
//offset, _ := r.(io.Seeker).Seek(0, io.SeekCurrent)
|
|
|
|
//log.Printf("position before frame: %d", offset)
|
|
|
|
comp = make([]byte, size) |
|
|
|
if _, err = io.ReadFull(r, comp); err != nil { |
|
|
|
return |
|
|
@ -264,31 +529,33 @@ func decodeVPINFrame(r io.Reader, width, height int, version int16, c *Pin2DMDCo |
|
|
|
err = decodeVPINFramePlanes(bytes.NewReader(data), frame, bits, header.PlaneSize, c) |
|
|
|
return |
|
|
|
} |
|
|
|
//offset, _ = r.(io.Seeker).Seek(0, io.SeekCurrent)
|
|
|
|
//log.Printf("position before frame: %d", offset)
|
|
|
|
err = decodeVPINFramePlanes(r, frame, bits, header.PlaneSize, c) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
func decodeVPINFramePlanes(r io.Reader, frame *image.Paletted, bits uint8, size int16, c *Pin2DMDColoring) (err error) { |
|
|
|
var ( |
|
|
|
plane = make([]byte, size) |
|
|
|
mask = make([]byte, size) |
|
|
|
buffer = new(bytes.Buffer) |
|
|
|
br = io.TeeReader(r, buffer) |
|
|
|
plane = make([]byte, size) |
|
|
|
mask = make([]byte, size) |
|
|
|
) |
|
|
|
for i := range mask { |
|
|
|
mask[i] = 0xff |
|
|
|
} |
|
|
|
for i := 0; i < int(bits); i++ { |
|
|
|
var marker byte |
|
|
|
if err = binary.Read(br, binary.BigEndian, &marker); err != nil { |
|
|
|
if err = binary.Read(r, binary.BigEndian, &marker); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
// log.Printf("dmd: VPIN plane bit %d marker %#02x", i, marker)
|
|
|
|
switch marker { |
|
|
|
case 0x00, 0x01, 0x02, 0x03: |
|
|
|
if _, err = io.ReadFull(br, plane); err != nil { |
|
|
|
if _, err = io.ReadFull(r, plane); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
// offset, _ := r.(io.Seeker).Seek(0, io.SeekCurrent)
|
|
|
|
// fmt.Printf("plane %d@%d:\n%s", marker, offset, hex.Dump(plane))
|
|
|
|
if c != nil { |
|
|
|
// Colorize keyframe based on checksum.
|
|
|
|
if checksum := calcChecksumWithMask(plane, mask, true); c.Mappings[checksum] != nil && c.Mappings[checksum].PaletteIndex <= uint16(len(c.Palettes)) { |
|
|
@ -310,7 +577,7 @@ func decodeVPINFramePlanes(r io.Reader, frame *image.Paletted, bits uint8, size |
|
|
|
default: |
|
|
|
return fmt.Errorf("dmd: unexpected VPIN frame marker %#02x", marker) |
|
|
|
case 0x6d: |
|
|
|
if _, err = io.ReadFull(br, mask); err != nil { |
|
|
|
if _, err = io.ReadFull(r, mask); err != nil { |
|
|
|
return |
|
|
|
} |
|
|
|
// fmt.Print(hex.Dump(mask))
|
|
|
|