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

package dmd
import (
"errors"
"image"
"image/color"
"io"
"time"
)
var (
ErrHeaderMagic = errors.New("dmd: unexpected header magic")
ErrSeek = errors.New("dmd: illegal seek offset")
)
type Animation interface {
// Name of the animation.
Name() string
// IsMask indicates the animation contains masks.
IsMask() bool
// size of the animation.
Size() (width, height int)
// ClockInFront checks if the clock should be over the image.
ClockInFront() bool
// ClockOffset is the offset of the clock.
ClockOffset() image.Point
// Duration of the animation.
Duration() time.Duration
// Len is the length of the animation in number of frames.
Len() int
// Next frame, will return EOF when end of animation is reached.
NextFrame() (image.Image, time.Duration, error)
// Seek to frame.
SeekFrame(pos int) error
}
// Animations is 0 or more Animation.
type Animations []Animation
// Namer can provide a name.
type namer interface {
Name() string
}
// ImageAnimation consists of (bitmap) images.
type imageAnimation struct {
name string
width, height int
isMask bool
clockInFront bool
clockIsSmall bool
clockOffset image.Point
delay []time.Duration
index []int
frame []image.Image
pos int
palette color.Palette
}
func (a *imageAnimation) Name() string { return a.name }
func (a *imageAnimation) Size() (width, height int) {
return a.width, a.height
}
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 }
func (a *imageAnimation) Duration() (total time.Duration) {
if len(a.index) == 0 {
// Linear.
for _, delay := range a.delay {
total += delay
}
} else {
// Sequenced.
for _, index := range a.index {
if index >= 0 {
total += a.delay[index]
}
}
}
return
}
func (a *imageAnimation) Len() int {
if l := len(a.index); l > 0 {
// Sequenced.
return l
}
// Linear.
return len(a.frame)
}
// Next frame, will return EOF when end of animation is reached.
func (a *imageAnimation) NextFrame() (image.Image, time.Duration, error) {
pos := a.pos
if len(a.index) > 0 {
if pos >= len(a.index) || pos >= len(a.delay) {
return nil, 0, io.EOF
}
a.pos++
// Sequenced.
index := a.index[pos]
if index < 0 {
return newTransparentFrame(a.width, a.height), a.delay[pos], nil
}
return a.frame[index], a.delay[pos], nil
}
// Linear.
if pos >= len(a.frame) || pos >= len(a.delay) {
return nil, 0, io.EOF
}
a.pos++
return a.frame[pos], a.delay[pos], nil
}
func newTransparentFrame(width, height int) image.Image {
return &image.Paletted{
Pix: make([]byte, width*height),
Stride: width,
Rect: image.Rect(0, 0, width, height),
Palette: color.Palette{color.Transparent},
}
}
// Seek to frame.
func (a *imageAnimation) SeekFrame(pos int) error {
var l int
if l = len(a.index); l == 0 {
l = len(a.frame)
}
if pos < 0 || pos >= l {
return ErrSeek
}
a.pos = pos
return nil
}
type colorizedAnimation struct {
Animation
palette color.Palette
}
func (a colorizedAnimation) NextFrame() (image.Image, time.Duration, error) {
i, t, err := a.Animation.NextFrame()
i = a.colorize(i)
return i, t, err
}
func (a colorizedAnimation) colorize(i image.Image) image.Image {
if i == nil {
return nil
}
if p, ok := i.(*image.Paletted); ok {
// log.Printf("colorize %T with %d colors", i, len(p.Pin2DMDPalette))
return &image.Paletted{
Pix: p.Pix,
Stride: p.Stride,
Rect: p.Rect,
Palette: a.palette,
}
}
return i
}
func Colorize(a Animation, p color.Palette) Animation {
return colorizedAnimation{
Animation: a,
palette: p,
}
}
// 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},
color.Alpha{A: 0x44}, color.Alpha{A: 0x55}, color.Alpha{A: 0x66}, color.Alpha{A: 0x77},
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},
}
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},
}
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},
color.RGBA{R: 0x66, G: 0x33, B: 0x00, A: 0xff}, color.RGBA{R: 0x77, G: 0x3b, B: 0x00, A: 0xff},
color.RGBA{R: 0x88, G: 0x44, B: 0x00, A: 0xff}, color.RGBA{R: 0x99, G: 0x4c, B: 0x00, A: 0xff},
color.RGBA{R: 0xaa, G: 0x55, B: 0x00, A: 0xff}, color.RGBA{R: 0xbb, G: 0x5d, B: 0x00, A: 0xff},
color.RGBA{R: 0xcc, G: 0x66, B: 0x00, A: 0xff}, color.RGBA{R: 0xdd, G: 0x6e, B: 0x00, A: 0xff},
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
}