Package dmd implements well-known formats for dot-matrix display (DMD) art.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

158 lines
4.1 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. package dmd
  2. import (
  3. "bytes"
  4. "encoding/binary"
  5. "fmt"
  6. "image"
  7. "io"
  8. "os"
  9. "path/filepath"
  10. "time"
  11. "maze.io/x/dmd/container"
  12. "maze.io/x/dmd/bitmap"
  13. )
  14. const (
  15. dmd1Magic = "DMD1"
  16. dmd1VersionChunk = "VERS"
  17. dmd1AnimationChunk = "ANIM"
  18. dmd1InfoChunk = "INFO"
  19. dmd1FrameChunk = "FRAM"
  20. dmd1FrameTimeChunk = "TIME"
  21. dmd1FrameDataChunk = "DATA"
  22. dmd1LoopChunk = "LOOP"
  23. )
  24. // DecodeDMD1 decodes one DMD1 animation (as used in RPI2DMD).
  25. func DecodeDMD1(r io.Reader) (Animation, error) {
  26. var chunk container.DMD1Chunk
  27. if _, err := chunk.ReadFrom(r); err != nil {
  28. return nil, err
  29. } else if name := string(chunk.Name[:]); name != dmd1Magic {
  30. return nil, ErrHeaderMagic
  31. }
  32. a := new(imageAnimation)
  33. if f, ok := r.(*os.File); ok {
  34. a.name = filepath.Base(f.Name())
  35. a.name = a.name[:len(a.name)-len(filepath.Ext(a.name))]
  36. } else if n, ok := r.(namer); ok {
  37. a.name = n.Name()
  38. }
  39. r = bytes.NewReader(chunk.Data)
  40. for {
  41. if _, err := chunk.ReadFrom(r); err == io.EOF {
  42. break
  43. } else if err != nil {
  44. return nil, err
  45. }
  46. name := string(chunk.Name[:])
  47. // log.Printf("dmd: DMD1 chunk %q of size %d", name, chunk.size)
  48. switch name {
  49. case dmd1VersionChunk:
  50. if chunk.Size != 4 {
  51. return nil, fmt.Errorf("dmd1: unexpected %q chunk size", name)
  52. }
  53. var version uint32
  54. if version = binary.LittleEndian.Uint32(chunk.Data); version != 0x00010000 {
  55. return nil, fmt.Errorf("dmd1: unsupported version %#08x", version)
  56. }
  57. case dmd1AnimationChunk:
  58. if err := decodeDMD1Animation(bytes.NewReader(chunk.Data), a); err != nil {
  59. return nil, err
  60. }
  61. default:
  62. return nil, fmt.Errorf("dmd: unsupported DMD1 chunk %q", name)
  63. }
  64. }
  65. return a, nil
  66. }
  67. func decodeDMD1Animation(r io.Reader, a *imageAnimation) error {
  68. var chunk container.DMD1Chunk
  69. for {
  70. if _, err := chunk.ReadFrom(r); err == io.EOF {
  71. return nil
  72. } else if err != nil {
  73. return err
  74. }
  75. name := string(chunk.Name[:])
  76. // log.Printf("dmd: DMD1 animation chunk %q of size %d", name, chunk.size)
  77. switch name {
  78. case dmd1InfoChunk:
  79. var info struct {
  80. Width uint32
  81. Height uint32
  82. _ [3]uint32
  83. Frames uint32
  84. }
  85. if err := binary.Read(bytes.NewReader(chunk.Data), binary.LittleEndian, &info); err != nil {
  86. return err
  87. }
  88. a.width = int(info.Width)
  89. a.height = int(info.Height)
  90. a.delay = make([]time.Duration, 0, info.Frames)
  91. a.frame = make([]image.Image, 0, info.Frames)
  92. case dmd1FrameChunk:
  93. var (
  94. i image.Image
  95. d time.Duration
  96. err error
  97. )
  98. if i, d, err = decodeDMD1Frame(bytes.NewReader(chunk.Data), a.width, a.height); err == io.EOF {
  99. return nil
  100. } else if err != nil {
  101. return err
  102. }
  103. a.delay = append(a.delay, d)
  104. a.frame = append(a.frame, i)
  105. default:
  106. return fmt.Errorf("dmd: unsupported DMD1 animation chunk %q", name)
  107. }
  108. }
  109. }
  110. func decodeDMD1Frame(r io.Reader, width, height int) (i image.Image, d time.Duration, err error) {
  111. var chunk container.DMD1Chunk
  112. // TIME chunk.
  113. if _, err = chunk.ReadFrom(r); err != nil {
  114. return
  115. } else if name := string(chunk.Name[:]); name != dmd1FrameTimeChunk {
  116. return nil, 0, fmt.Errorf("dmd: expected DMD1 %q chunk, got %q", dmd1FrameTimeChunk, name)
  117. } else if chunk.Size != 4 {
  118. return nil, 0, fmt.Errorf("dmd: unexpected DMD1 %q chunk size", name)
  119. }
  120. d = time.Duration(binary.LittleEndian.Uint32(chunk.Data)) * time.Millisecond
  121. // DATA chunk.
  122. if _, err = chunk.ReadFrom(r); err != nil {
  123. return
  124. } else if name := string(chunk.Name[:]); name != dmd1FrameDataChunk {
  125. return nil, 0, fmt.Errorf("dmd: expected DMD1 %q chunk, got %q", dmd1FrameDataChunk, name)
  126. } else if need := uint32(width*height*4) / 2; chunk.Size < need {
  127. return nil, 0, fmt.Errorf("dmd: expected DMD1 %q chunk size of %d, got %d", dmd1FrameDataChunk, need, chunk.Size)
  128. }
  129. img := bitmap.NewRGBA16Image(image.Rect(0, 0, width, height))
  130. copy(img.Pix, chunk.Data)
  131. // DMD1 sets the alpha to 0 if no alpha, or 1 if alpha channel.
  132. for i, l := 0, len(img.Pix); i < l; i += 2 {
  133. // RGBA is stored as BARG (little endian)
  134. if img.Pix[i]&1 == 1 {
  135. img.Pix[i] |= 0x0f
  136. }
  137. // FIXME: Removes alpha completely.
  138. img.Pix[i] |= 0x0f
  139. }
  140. return img, d, nil
  141. }