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