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.

237 lines
5.9 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
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
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
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
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
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
2 years ago
2 years ago
2 years ago
  1. package dmd
  2. import (
  3. "errors"
  4. "image"
  5. "image/color"
  6. "io"
  7. "time"
  8. )
  9. var (
  10. ErrHeaderMagic = errors.New("dmd: unexpected header magic")
  11. ErrSeek = errors.New("dmd: illegal seek offset")
  12. )
  13. type Animation interface {
  14. // Name of the animation.
  15. Name() string
  16. // IsMask indicates the animation contains masks.
  17. IsMask() bool
  18. // size of the animation.
  19. Size() (width, height int)
  20. // ClockInFront checks if the clock should be over the image.
  21. ClockInFront() bool
  22. // ClockOffset is the offset of the clock.
  23. ClockOffset() image.Point
  24. // Duration of the animation.
  25. Duration() time.Duration
  26. // Len is the length of the animation in number of frames.
  27. Len() int
  28. // Next frame, will return EOF when end of animation is reached.
  29. NextFrame() (image.Image, time.Duration, error)
  30. // Seek to frame.
  31. SeekFrame(pos int) error
  32. }
  33. // Animations is 0 or more Animation.
  34. type Animations []Animation
  35. // Namer can provide a name.
  36. type namer interface {
  37. Name() string
  38. }
  39. // ImageAnimation consists of (bitmap) images.
  40. type imageAnimation struct {
  41. name string
  42. width, height int
  43. isMask bool
  44. clockInFront bool
  45. clockIsSmall bool
  46. clockOffset image.Point
  47. delay []time.Duration
  48. index []int
  49. frame []image.Image
  50. pos int
  51. palette color.Palette
  52. }
  53. func (a *imageAnimation) Name() string { return a.name }
  54. func (a *imageAnimation) Size() (width, height int) {
  55. return a.width, a.height
  56. }
  57. func (a *imageAnimation) ClockInFront() bool { return a.clockInFront }
  58. func (a *imageAnimation) ClockIsSmall() bool { return a.clockIsSmall }
  59. func (a *imageAnimation) ClockOffset() image.Point { return a.clockOffset }
  60. func (a *imageAnimation) IsMask() bool { return a.isMask }
  61. func (a *imageAnimation) Duration() (total time.Duration) {
  62. if len(a.index) == 0 {
  63. // Linear.
  64. for _, delay := range a.delay {
  65. total += delay
  66. }
  67. } else {
  68. // Sequenced.
  69. for _, index := range a.index {
  70. if index >= 0 {
  71. total += a.delay[index]
  72. }
  73. }
  74. }
  75. return
  76. }
  77. func (a *imageAnimation) Len() int {
  78. if l := len(a.index); l > 0 {
  79. // Sequenced.
  80. return l
  81. }
  82. // Linear.
  83. return len(a.frame)
  84. }
  85. // Next frame, will return EOF when end of animation is reached.
  86. func (a *imageAnimation) NextFrame() (image.Image, time.Duration, error) {
  87. pos := a.pos
  88. if len(a.index) > 0 {
  89. if pos >= len(a.index) || pos >= len(a.delay) {
  90. return nil, 0, io.EOF
  91. }
  92. a.pos++
  93. // Sequenced.
  94. index := a.index[pos]
  95. if index < 0 {
  96. return newTransparentFrame(a.width, a.height), a.delay[pos], nil
  97. }
  98. return a.frame[index], a.delay[pos], nil
  99. }
  100. // Linear.
  101. if pos >= len(a.frame) || pos >= len(a.delay) {
  102. return nil, 0, io.EOF
  103. }
  104. a.pos++
  105. return a.frame[pos], a.delay[pos], nil
  106. }
  107. func newTransparentFrame(width, height int) image.Image {
  108. return &image.Paletted{
  109. Pix: make([]byte, width*height),
  110. Stride: width,
  111. Rect: image.Rect(0, 0, width, height),
  112. Palette: color.Palette{color.Transparent},
  113. }
  114. }
  115. // Seek to frame.
  116. func (a *imageAnimation) SeekFrame(pos int) error {
  117. var l int
  118. if l = len(a.index); l == 0 {
  119. l = len(a.frame)
  120. }
  121. if pos < 0 || pos >= l {
  122. return ErrSeek
  123. }
  124. a.pos = pos
  125. return nil
  126. }
  127. type colorizedAnimation struct {
  128. Animation
  129. palette color.Palette
  130. }
  131. func (a colorizedAnimation) NextFrame() (image.Image, time.Duration, error) {
  132. i, t, err := a.Animation.NextFrame()
  133. i = a.colorize(i)
  134. return i, t, err
  135. }
  136. func (a colorizedAnimation) colorize(i image.Image) image.Image {
  137. if i == nil {
  138. return nil
  139. }
  140. if p, ok := i.(*image.Paletted); ok {
  141. // log.Printf("colorize %T with %d colors", i, len(p.Pin2DMDPalette))
  142. return &image.Paletted{
  143. Pix: p.Pix,
  144. Stride: p.Stride,
  145. Rect: p.Rect,
  146. Palette: a.palette,
  147. }
  148. }
  149. return i
  150. }
  151. func Colorize(a Animation, p color.Palette) Animation {
  152. return colorizedAnimation{
  153. Animation: a,
  154. palette: p,
  155. }
  156. }
  157. // Default colors.
  158. var (
  159. Red = color.RGBA{R: 0xff, A: 0xff}
  160. Green = color.RGBA{G: 0xff, A: 0xff}
  161. Blue = color.RGBA{B: 0xff, A: 0xff}
  162. Yellow = color.RGBA{R: 0xff, G: 0xff, A: 0xff}
  163. Magenta = color.RGBA{R: 0xff, B: 0xff, A: 0xff}
  164. Cyan = color.RGBA{G: 0xff, B: 0xff, A: 0xff}
  165. Amber = color.RGBA{R: 0xff, G: 0x7f, A: 0xff}
  166. )
  167. // Default palettes.
  168. var (
  169. Mask = color.Palette{
  170. color.Alpha{A: 0x00}, color.Alpha{A: 0x11}, color.Alpha{A: 0x22}, color.Alpha{A: 0x33},
  171. color.Alpha{A: 0x44}, color.Alpha{A: 0x55}, color.Alpha{A: 0x66}, color.Alpha{A: 0x77},
  172. color.Alpha{A: 0x88}, color.Alpha{A: 0x99}, color.Alpha{A: 0xaa}, color.Alpha{A: 0xbb},
  173. color.Alpha{A: 0xcc}, color.Alpha{A: 0xdd}, color.Alpha{A: 0xee}, color.Alpha{A: 0xff},
  174. }
  175. GrayPalette = color.Palette{
  176. color.Gray{Y: 0x00}, color.Gray{Y: 0x11}, color.Gray{Y: 0x22}, color.Gray{Y: 0x33},
  177. color.Gray{Y: 0x44}, color.Gray{Y: 0x55}, color.Gray{Y: 0x66}, color.Gray{Y: 0x77},
  178. color.Gray{Y: 0x88}, color.Gray{Y: 0x99}, color.Gray{Y: 0xaa}, color.Gray{Y: 0xbb},
  179. color.Gray{Y: 0xcc}, color.Gray{Y: 0xdd}, color.Gray{Y: 0xee}, color.Gray{Y: 0xff},
  180. }
  181. AmberPalette = color.Palette{
  182. color.RGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff}, color.RGBA{R: 0x11, G: 0x08, B: 0x00, A: 0xff},
  183. color.RGBA{R: 0x22, G: 0x11, B: 0x00, A: 0xff}, color.RGBA{R: 0x33, G: 0x19, B: 0x00, A: 0xff},
  184. color.RGBA{R: 0x44, G: 0x22, B: 0x00, A: 0xff}, color.RGBA{R: 0x55, G: 0x2a, B: 0x00, A: 0xff},
  185. color.RGBA{R: 0x66, G: 0x33, B: 0x00, A: 0xff}, color.RGBA{R: 0x77, G: 0x3b, B: 0x00, A: 0xff},
  186. color.RGBA{R: 0x88, G: 0x44, B: 0x00, A: 0xff}, color.RGBA{R: 0x99, G: 0x4c, B: 0x00, A: 0xff},
  187. color.RGBA{R: 0xaa, G: 0x55, B: 0x00, A: 0xff}, color.RGBA{R: 0xbb, G: 0x5d, B: 0x00, A: 0xff},
  188. color.RGBA{R: 0xcc, G: 0x66, B: 0x00, A: 0xff}, color.RGBA{R: 0xdd, G: 0x6e, B: 0x00, A: 0xff},
  189. color.RGBA{R: 0xee, G: 0x77, B: 0x00, A: 0xff}, color.RGBA{R: 0xff, G: 0x7f, B: 0x00, A: 0xff},
  190. }
  191. )
  192. func isMask(palette color.Palette) bool {
  193. if len(palette) != len(Mask) {
  194. return false
  195. }
  196. for i, c := range palette {
  197. if c != Mask[i] {
  198. return false
  199. }
  200. }
  201. return true
  202. }