Browse Source

Probably broke a lot of things

master v1.0.1
maze 1 year ago
parent
commit
1599abc320
7 changed files with 548 additions and 78 deletions
  1. +29
    -3
      animation.go
  2. +1
    -0
      dmd1.go
  3. +84
    -21
      image.go
  4. +130
    -19
      rundmd.go
  5. +2
    -2
      rundmd_test.go
  6. +287
    -20
      vpin.go
  7. +15
    -13
      vpin_test.go

+ 29
- 3
animation.go View File

@ -56,6 +56,7 @@ type imageAnimation struct {
width, height int
isMask bool
clockInFront bool
clockIsSmall bool
clockOffset image.Point
delay []time.Duration
index []int
@ -72,6 +73,8 @@ func (a *imageAnimation) Size() (width, height int) {
func (a *imageAnimation) ClockInFront() bool { return a.clockInFront }
func (a *imageAnimation) ClockIsSmall() bool { return a.clockIsSmall }
func (a *imageAnimation) ClockOffset() image.Point { return a.clockOffset }
func (a *imageAnimation) IsMask() bool { return a.isMask }
@ -184,7 +187,18 @@ func Colorize(a Animation, p color.Palette) Animation {
}
}
// Default palettes.
// Default colors.
var (
Red = color.RGBA{R: 0xff, A: 0xff}
Green = color.RGBA{G: 0xff, A: 0xff}
Blue = color.RGBA{B: 0xff, A: 0xff}
Yellow = color.RGBA{R: 0xff, G: 0xff, A: 0xff}
Magenta = color.RGBA{R: 0xff, B: 0xff, A: 0xff}
Cyan = color.RGBA{G: 0xff, B: 0xff, A: 0xff}
Amber = color.RGBA{R: 0xff, G: 0x7f, A: 0xff}
)
// Default palettes.
var (
Mask = color.Palette{
color.Alpha{A: 0x00}, color.Alpha{A: 0x11}, color.Alpha{A: 0x22}, color.Alpha{A: 0x33},
@ -192,13 +206,13 @@ var (
color.Alpha{A: 0x88}, color.Alpha{A: 0x99}, color.Alpha{A: 0xaa}, color.Alpha{A: 0xbb},
color.Alpha{A: 0xcc}, color.Alpha{A: 0xdd}, color.Alpha{A: 0xee}, color.Alpha{A: 0xff},
}
Gray = color.Palette{
GrayPalette = color.Palette{
color.Gray{Y: 0x00}, color.Gray{Y: 0x11}, color.Gray{Y: 0x22}, color.Gray{Y: 0x33},
color.Gray{Y: 0x44}, color.Gray{Y: 0x55}, color.Gray{Y: 0x66}, color.Gray{Y: 0x77},
color.Gray{Y: 0x88}, color.Gray{Y: 0x99}, color.Gray{Y: 0xaa}, color.Gray{Y: 0xbb},
color.Gray{Y: 0xcc}, color.Gray{Y: 0xdd}, color.Gray{Y: 0xee}, color.Gray{Y: 0xff},
}
Amber = color.Palette{
AmberPalette = color.Palette{
color.RGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff}, color.RGBA{R: 0x11, G: 0x08, B: 0x00, A: 0xff},
color.RGBA{R: 0x22, G: 0x11, B: 0x00, A: 0xff}, color.RGBA{R: 0x33, G: 0x19, B: 0x00, A: 0xff},
color.RGBA{R: 0x44, G: 0x22, B: 0x00, A: 0xff}, color.RGBA{R: 0x55, G: 0x2a, B: 0x00, A: 0xff},
@ -209,3 +223,15 @@ var (
color.RGBA{R: 0xee, G: 0x77, B: 0x00, A: 0xff}, color.RGBA{R: 0xff, G: 0x7f, B: 0x00, A: 0xff},
}
)
func isMask(palette color.Palette) bool {
if len(palette) != len(Mask) {
return false
}
for i, c := range palette {
if c != Mask[i] {
return false
}
}
return true
}

+ 1
- 0
dmd1.go View File

@ -23,6 +23,7 @@ const (
dmd1FrameChunk = "FRAM"
dmd1FrameTimeChunk = "TIME"
dmd1FrameDataChunk = "DATA"
dmd1LoopChunk = "LOOP"
)
// DecodeDMD1 decodes one DMD1 animation (as used in RPI2DMD).


+ 84
- 21
image.go View File

@ -11,33 +11,35 @@ import (
"maze.io/x/dmd/bitmap"
)
const gifDelay = 10 * time.Millisecond
type gifAnimation struct {
name string
gif *gif.GIF
pos int
}
func (g gifAnimation) Name() string { return g.name }
func (g gifAnimation) IsMask() bool { return false }
func (g gifAnimation) Size() (width, height int) { return g.gif.Config.Width, g.gif.Config.Height }
func (g gifAnimation) ClockInFront() bool { return false }
func (g gifAnimation) ClockOffset() image.Point { return image.Point{} }
func (g gifAnimation) Len() int { return len(g.gif.Image) }
func (g *gifAnimation) Name() string { return g.name }
func (g *gifAnimation) IsMask() bool { return false }
func (g *gifAnimation) Size() (width, height int) { return g.gif.Config.Width, g.gif.Config.Height }
func (g *gifAnimation) ClockInFront() bool { return false }
func (g *gifAnimation) ClockOffset() image.Point { return image.Point{} }
func (g *gifAnimation) Len() int { return len(g.gif.Image) }
func (g gifAnimation) Duration() (total time.Duration) {
func (g *gifAnimation) Duration() (total time.Duration) {
for _, delay := range g.gif.Delay {
total += time.Duration(delay) * 10 * time.Millisecond
total += time.Duration(delay) * gifDelay
}
return
}
func (g gifAnimation) NextFrame() (image.Image, time.Duration, error) {
if g.pos+1 >= len(g.gif.Image) {
func (g *gifAnimation) NextFrame() (image.Image, time.Duration, error) {
if g.pos+1 > len(g.gif.Image) {
return nil, 0, io.EOF
}
pos := g.pos
g.pos++
return g.gif.Image[pos], time.Duration(g.gif.Delay[pos]) * 10 * time.Millisecond, nil
return g.gif.Image[pos], time.Duration(g.gif.Delay[pos]) * gifDelay, nil
}
func (g *gifAnimation) SeekFrame(pos int) error {
@ -65,25 +67,86 @@ func DecodeGIF(r io.Reader) (Animation, error) {
}, nil
}
func EncodeToGIF(w io.Writer, a Animation) error {
g := &gif.GIF{
Config: image.Config{},
func EncodeToGIF(w io.Writer, a Animation, c color.Color) error {
var (
out = &gif.GIF{
Config: image.Config{},
Image: make([]*image.Paletted, 0, a.Len()),
}
mask image.Image
palette color.Palette
prev color.Palette
)
if a.IsMask() {
mask = image.NewUniform(c)
palette = makeColorRamp(c)
}
for i, l := 0, a.Len(); i < l; i++ {
img, delay, err := a.NextFrame()
frame, delay, err := a.NextFrame()
if err == io.EOF {
break
} else if err != nil {
return err
}
// fmt.Printf("%s -> %g -> %d\n", delay, float64(delay)/float64(time.Second), int(delay/(10*time.Millisecond)))
g.Delay = append(g.Delay, int((delay+5*time.Millisecond)/(10*time.Millisecond)))
g.Image = append(g.Image, toPaletted(img))
// log.Printf("frame %d: %s for %d (%s)", i, g.Image[i].Bounds(), g.Delay[i], delay)
if mask != nil {
// log.Printf("colorize %T: %+v", frame, frame)
p := image.NewPaletted(frame.Bounds(), palette)
draw.DrawMask(p, frame.Bounds(), mask, image.Point{}, frame, image.Point{}, draw.Src)
out.Image = append(out.Image, p)
} else {
// log.Printf("gif frame %d: %T %s", i, frame, frame.Bounds())
switch frame := frame.(type) {
case *image.Paletted:
p := image.NewPaletted(frame.Bounds(), nil)
copy(p.Pix, frame.Pix)
if len(frame.Palette) == 0 {
if len(prev) == 0 {
p.Palette = Mask
} else {
p.Palette = prev
}
} else {
p.Palette = make(color.Palette, len(frame.Palette))
copy(p.Palette, frame.Palette)
prev = p.Palette
}
// log.Printf("gif frame %d save: %T %d->%d", i, p, len(frame.Palette), len(p.Palette))
out.Image = append(out.Image, p)
default:
out.Image = append(out.Image, toPaletted(frame))
}
}
out.Delay = append(out.Delay, int((delay+5*time.Millisecond)/(10*time.Millisecond)))
}
return gif.EncodeAll(w, g)
return gif.EncodeAll(w, out)
}
const (
rampColors = 16
rampStep = 0x100 / rampColors
)
func makeColorRamp(c color.Color) color.Palette {
p := make(color.Palette, rampColors)
p[0] = color.RGBA{}
r, g, b, _ := c.RGBA()
r &= 0xff
g &= 0xff
b &= 0xff
for i := uint32(0); i < rampColors; i++ {
p[i] = color.RGBA{
R: uint8((r * (i + 1) * rampStep) >> 8),
G: uint8((g * (i + 1) * rampStep) >> 8),
B: uint8((b * (i + 1) * rampStep) >> 8),
A: 0xff,
}
}
return p
}
func toPaletted(i image.Image) *image.Paletted {
@ -94,7 +157,7 @@ func toPaletted(i image.Image) *image.Paletted {
Pix: i.Pix,
Stride: i.Stride,
Rect: i.Rect,
Palette: Gray,
Palette: GrayPalette,
}
}
return i


+ 130
- 19
rundmd.go View File

@ -2,11 +2,9 @@ package dmd
import (
"encoding/binary"
"encoding/hex"
"fmt"
"image"
"io"
"log"
"time"
)
@ -17,6 +15,7 @@ const (
runDMDHeaderOffset = 0xc800
runDMDHeaderBuildOffset = 0x01ef
runDMDHeaderSize = 0x0200
runDMDBitmapSize = 128 * 32 / 2
)
// DecodeRunDMD can decode animations in a (raw) Run-DMD SD card image.
@ -46,7 +45,7 @@ func DecodeRunDMD(r io.ReadSeeker) (Animations, error) {
return nil, err
}
log.Printf("dmd: expecting %d RunDMD animations, build %s, number %d (%#04x)", size, cString(build[:]), next, next)
// log.Printf("dmd: expecting %d RunDMD animations, build %s, number %d (%#04x)", size, cString(build[:]), next, next)
var (
all = make(Animations, size)
err error
@ -59,26 +58,142 @@ func DecodeRunDMD(r io.ReadSeeker) (Animations, error) {
return all, nil
}
type runDMDAnimation struct {
r io.ReadSeeker
name string
width, height int
pos int
delay []time.Duration
index []int
frames int
offset int64
pixels [runDMDBitmapSize]byte
frame *image.Paletted
}
func (r *runDMDAnimation) Name() string { return r.name }
func (r *runDMDAnimation) IsMask() bool { return true }
func (r *runDMDAnimation) Size() (width, height int) { return 128, 32 }
func (r *runDMDAnimation) ClockInFront() bool { return false }
func (r *runDMDAnimation) ClockIsSmall() bool { return false }
func (r *runDMDAnimation) ClockOffset() image.Point { return image.Point{} }
func (r *runDMDAnimation) Duration() (total time.Duration) {
for _, i := range r.index {
if i >= 0 {
total += r.delay[i]
}
}
return
}
func (r *runDMDAnimation) Len() int {
var valid int
for _, index := range r.index {
if index >= 0 {
valid++
}
}
return valid
}
func (r *runDMDAnimation) NextFrame() (image.Image, time.Duration, error) {
index := -1
for index < 0 {
if r.pos+1 > len(r.index) {
return nil, 0, io.EOF
}
index = r.index[r.pos]
r.pos++
}
// Skip to frame
stride := r.width * r.height / 2
//if _, err := r.r.Seek(runDMDHeaderOffset+r.offset+int64(index)*runDMDBitmapSize, io.SeekStart); err != nil {
if _, err := r.r.Seek(runDMDHeaderSize+r.offset*runDMDHeaderSize+int64(index)*int64(stride), io.SeekStart); err != nil {
return nil, 0, fmt.Errorf("dmd: error seeking to animation %d frame data: %w", r.pos-1, err)
}
// Read frame
if _, err := io.ReadFull(r.r, r.pixels[:]); err != nil {
return nil, 0, fmt.Errorf("dmd: error reading animation %d frame pixels: %w", r.pos-1, err)
}
for j, v := range r.pixels[:] {
o := j << 1
r.frame.Pix[o+0] = v >> 4
r.frame.Pix[o+1] = v & 0xf
}
return r.frame, r.delay[index], nil
}
func (r *runDMDAnimation) SeekFrame(pos int) error {
if pos < 0 || pos >= len(r.index) {
return ErrSeek
}
r.pos = pos
return nil
}
type runDMDHeader struct {
ID uint16
_ uint8
Frames uint8
ByteOffset uint32
Entries uint8
Width uint8
Height uint8
Unknown [9]byte
Name [32]byte
}
type runDMDFrameHeader struct {
ID uint8
Delay uint8
}
func decodeRunDMDAnimation(r io.ReadSeeker, i int) (Animation, error) {
if _, err := r.Seek(runDMDHeaderOffset+runDMDHeaderSize*int64(i), io.SeekStart); err != nil {
return nil, err
}
var header struct {
ID uint16
_ uint8
Frames uint8
ByteOffset uint32
Entries uint8
Width uint8
Height uint8
Unknown [9]byte
Name [32]byte
var header runDMDHeader
if err := binary.Read(r, binary.BigEndian, &header); err != nil {
return nil, fmt.Errorf("dmd: error seeking to animation %d: %w", i+1, err)
}
// log.Printf("animation %d: %q: %s", i, cString(header.Name[:]), hex.Dump(header.Unknown[:]))
a := &runDMDAnimation{
r: r,
name: cString(header.Name[:]),
width: int(header.Width),
height: int(header.Height),
frames: int(header.Frames),
offset: int64(header.ByteOffset), // * runDMDHeaderSize,
frame: image.NewPaletted(image.Rectangle{Max: image.Point{X: 128, Y: 32}}, Mask),
}
a.width = 128 // fixed
// Read indexes and delays.
if _, err := r.Seek(int64(header.ByteOffset)*runDMDHeaderSize, io.SeekStart); err != nil {
return nil, fmt.Errorf("dmd: error seeking to animation %d delays: %w", i+1, err)
}
for i := 0; i < int(header.Frames); i++ {
var frameHeader runDMDFrameHeader
if err := binary.Read(r, binary.BigEndian, &frameHeader); err != nil {
return nil, err
}
a.delay = append(a.delay, time.Duration(frameHeader.Delay)*time.Millisecond)
a.index = append(a.index, int(frameHeader.ID)-1)
}
return a, nil
}
func decodeRunDMDAnimationOld(r io.ReadSeeker, i int) (Animation, error) {
var header runDMDHeader
if err := binary.Read(r, binary.BigEndian, &header); err != nil {
return nil, fmt.Errorf("dmd: error seeking to animation %d: %w", i+1, err)
}
log.Printf("animation %d: %q: %s", i, cString(header.Name[:]), hex.Dump(header.Unknown[:]))
// log.Printf("animation %d: %q: %s", i, cString(header.Name[:]), hex.Dump(header.Unknown[:]))
a := &imageAnimation{
name: cString(header.Name[:]),
isMask: true,
@ -92,10 +207,7 @@ func decodeRunDMDAnimation(r io.ReadSeeker, i int) (Animation, error) {
return nil, fmt.Errorf("dmd: error seeking to animation %d delays: %w", i+1, err)
}
for i := 0; i < int(header.Frames); i++ {
var frameHeader struct {
ID uint8
Delay uint8
}
var frameHeader runDMDFrameHeader
if err := binary.Read(r, binary.BigEndian, &frameHeader); err != nil {
return nil, err
}
@ -128,6 +240,5 @@ func decodeRunDMDAnimation(r io.ReadSeeker, i int) (Animation, error) {
}
a.frame = append(a.frame, f)
}
return a, nil
}

+ 2
- 2
rundmd_test.go View File

@ -52,9 +52,9 @@ func TestDecodeRunDMD(t *testing.T) {
} else {
t.Logf("encoding %s to %s", a.Name(), o.Name())
if a.IsMask() {
err = EncodeToGIF(o, Colorize(a, Amber))
err = EncodeToGIF(o, a, Amber)
} else {
err = EncodeToGIF(o, a)
err = EncodeToGIF(o, a, Red)
}
if err != nil {
_ = o.Close()


+ 287
- 20
vpin.go View File

@ -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))


+ 15
- 13
vpin_test.go View File

@ -15,18 +15,20 @@ func TestDecodeVPIN(t *testing.T) {
{
Name: "mb_106b.vni",
},
{
Name: "sprk_103.vni",
Coloring: "sprk_103.pal",
},
{
Name: "smb.ani",
Coloring: "smb.pal",
},
{
Name: "sttng.vni",
Coloring: "sttng.pal",
},
/*
{
Name: "sprk_103.vni",
Coloring: "sprk_103.pal",
},
{
Name: "smb.ani",
Coloring: "smb.pal",
},
{
Name: "sttng.vni",
Coloring: "sttng.pal",
},
*/
}
for _, test := range tests {
t.Run(test.Name, func(it *testing.T) {
@ -85,7 +87,7 @@ func testEncodeGIF(t *testing.T, set, base string, a Animation, n int) {
w, h := a.Size()
t.Logf("saving animation %d: %s, %dx%d, %s, %d frames to %s", n+1, a.Name(), w, h, a.Duration(), a.Len(), o.Name())
if err = EncodeToGIF(o, a); err != nil {
if err = EncodeToGIF(o, a, Red); err != nil {
t.Fatal(err)
}
}

Loading…
Cancel
Save