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.

201 lines
4.3 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
  1. package dmd
  2. import (
  3. "image"
  4. "image/color"
  5. "image/draw"
  6. "image/gif"
  7. "io"
  8. "time"
  9. "maze.io/x/dmd/bitmap"
  10. )
  11. const gifDelay = 10 * time.Millisecond
  12. type gifAnimation struct {
  13. name string
  14. gif *gif.GIF
  15. pos int
  16. }
  17. func (g *gifAnimation) Name() string { return g.name }
  18. func (g *gifAnimation) IsMask() bool { return false }
  19. func (g *gifAnimation) Size() (width, height int) { return g.gif.Config.Width, g.gif.Config.Height }
  20. func (g *gifAnimation) ClockInFront() bool { return false }
  21. func (g *gifAnimation) ClockOffset() image.Point { return image.Point{} }
  22. func (g *gifAnimation) Len() int { return len(g.gif.Image) }
  23. func (g *gifAnimation) Duration() (total time.Duration) {
  24. for _, delay := range g.gif.Delay {
  25. total += time.Duration(delay) * gifDelay
  26. }
  27. return
  28. }
  29. func (g *gifAnimation) NextFrame() (image.Image, time.Duration, error) {
  30. if g.pos+1 > len(g.gif.Image) {
  31. return nil, 0, io.EOF
  32. }
  33. pos := g.pos
  34. g.pos++
  35. return g.gif.Image[pos], time.Duration(g.gif.Delay[pos]) * gifDelay, nil
  36. }
  37. func (g *gifAnimation) SeekFrame(pos int) error {
  38. if pos < 0 || pos >= len(g.gif.Image) {
  39. return ErrSeek
  40. }
  41. g.pos = pos
  42. return nil
  43. }
  44. func DecodeGIF(r io.Reader) (Animation, error) {
  45. g, err := gif.DecodeAll(r)
  46. if err != nil {
  47. return nil, err
  48. }
  49. var name string
  50. if n, ok := r.(namer); ok {
  51. name = n.Name()
  52. }
  53. return &gifAnimation{
  54. name: name,
  55. gif: g,
  56. }, nil
  57. }
  58. func EncodeToGIF(w io.Writer, a Animation, c color.Color) error {
  59. var (
  60. out = &gif.GIF{
  61. Config: image.Config{},
  62. Image: make([]*image.Paletted, 0, a.Len()),
  63. }
  64. mask image.Image
  65. palette color.Palette
  66. prev color.Palette
  67. )
  68. if a.IsMask() {
  69. mask = image.NewUniform(c)
  70. palette = makeColorRamp(c)
  71. }
  72. for i, l := 0, a.Len(); i < l; i++ {
  73. frame, delay, err := a.NextFrame()
  74. if err == io.EOF {
  75. break
  76. } else if err != nil {
  77. return err
  78. }
  79. if mask != nil {
  80. // log.Printf("colorize %T: %+v", frame, frame)
  81. p := image.NewPaletted(frame.Bounds(), palette)
  82. draw.DrawMask(p, frame.Bounds(), mask, image.Point{}, frame, image.Point{}, draw.Src)
  83. out.Image = append(out.Image, p)
  84. } else {
  85. // log.Printf("gif frame %d: %T %s", i, frame, frame.Bounds())
  86. switch frame := frame.(type) {
  87. case *image.Paletted:
  88. p := image.NewPaletted(frame.Bounds(), nil)
  89. copy(p.Pix, frame.Pix)
  90. if len(frame.Palette) == 0 {
  91. if len(prev) == 0 {
  92. p.Palette = Mask
  93. } else {
  94. p.Palette = prev
  95. }
  96. } else {
  97. p.Palette = make(color.Palette, len(frame.Palette))
  98. copy(p.Palette, frame.Palette)
  99. prev = p.Palette
  100. }
  101. // log.Printf("gif frame %d save: %T %d->%d", i, p, len(frame.Palette), len(p.Palette))
  102. out.Image = append(out.Image, p)
  103. default:
  104. out.Image = append(out.Image, toPaletted(frame))
  105. }
  106. }
  107. out.Delay = append(out.Delay, int((delay+5*time.Millisecond)/(10*time.Millisecond)))
  108. }
  109. return gif.EncodeAll(w, out)
  110. }
  111. const (
  112. rampColors = 16
  113. rampStep = 0x100 / rampColors
  114. )
  115. func makeColorRamp(c color.Color) color.Palette {
  116. p := make(color.Palette, rampColors)
  117. p[0] = color.RGBA{}
  118. r, g, b, _ := c.RGBA()
  119. r &= 0xff
  120. g &= 0xff
  121. b &= 0xff
  122. for i := uint32(0); i < rampColors; i++ {
  123. p[i] = color.RGBA{
  124. R: uint8((r * (i + 1) * rampStep) >> 8),
  125. G: uint8((g * (i + 1) * rampStep) >> 8),
  126. B: uint8((b * (i + 1) * rampStep) >> 8),
  127. A: 0xff,
  128. }
  129. }
  130. return p
  131. }
  132. func toPaletted(i image.Image) *image.Paletted {
  133. switch i := i.(type) {
  134. case *image.Paletted:
  135. if len(i.Palette) == 0 {
  136. return &image.Paletted{
  137. Pix: i.Pix,
  138. Stride: i.Stride,
  139. Rect: i.Rect,
  140. Palette: GrayPalette,
  141. }
  142. }
  143. return i
  144. case *bitmap.MaskedImage:
  145. if !i.HasMaskedPixels() {
  146. return toPaletted(&i.Paletted)
  147. }
  148. b := i.Bounds()
  149. o := image.NewPaletted(b, append(i.Palette, color.Transparent))
  150. draw.Draw(o, b, i, image.Point{}, draw.Src)
  151. return o
  152. default:
  153. b := i.Bounds()
  154. o := image.NewPaletted(b, getPalette(i))
  155. draw.Draw(o, b, i, image.Point{}, draw.Src)
  156. return o
  157. }
  158. }
  159. func getPalette(i image.Image) (p color.Palette) {
  160. var (
  161. seen = make(map[uint32]bool)
  162. b = i.Bounds()
  163. )
  164. for y := 0; y < b.Dy(); y++ {
  165. for x := 0; x < b.Dx(); x++ {
  166. c := i.At(x, y)
  167. r, g, b, _ := c.RGBA()
  168. v := (r & 0xff) << 16
  169. v |= (g & 0xff) << 8
  170. b |= (b & 0xff) << 0
  171. if !seen[v] {
  172. seen[v] = true
  173. p = append(p, c)
  174. }
  175. }
  176. }
  177. return p
  178. }