|
|
@ -1,6 +1,7 @@ |
|
|
|
package matrix |
|
|
|
|
|
|
|
import ( |
|
|
|
"fmt" |
|
|
|
"image" |
|
|
|
"image/color" |
|
|
|
"image/draw" |
|
|
@ -9,15 +10,14 @@ import ( |
|
|
|
"math/rand" |
|
|
|
"time" |
|
|
|
|
|
|
|
"maze.io/x/pixel/pixeleffect" |
|
|
|
|
|
|
|
"maze.io/x/pixel" |
|
|
|
|
|
|
|
"golang.org/x/image/colornames" |
|
|
|
|
|
|
|
"maze.io/x/dmd" |
|
|
|
"maze.io/x/pixel" |
|
|
|
"maze.io/x/pixel/pixeleffect" |
|
|
|
|
|
|
|
"maze.io/matrix/driver" |
|
|
|
"maze.io/matrix/driver/text" |
|
|
|
_ "maze.io/matrix/driver/text" // Text driver
|
|
|
|
) |
|
|
|
|
|
|
@ -34,21 +34,16 @@ const ( |
|
|
|
) |
|
|
|
|
|
|
|
var ( |
|
|
|
emptyPix []byte |
|
|
|
empty *image.RGBA |
|
|
|
) |
|
|
|
|
|
|
|
func init() { |
|
|
|
emptyPix = make([]byte, 128*32*4) |
|
|
|
for i := 0; i < 128*32*4; i += 4 { |
|
|
|
emptyPix[i+3] = 0xff |
|
|
|
monoEffects = []effectFunc{ |
|
|
|
pixeleffect.FadeOutHorizontal, |
|
|
|
pixeleffect.FadeOutVertical, |
|
|
|
pixeleffect.FadeOutDiagonal, |
|
|
|
pixeleffect.FadeOutDither, |
|
|
|
} |
|
|
|
empty = &image.RGBA{ |
|
|
|
Pix: emptyPix, |
|
|
|
Stride: 128, |
|
|
|
Rect: image.Rectangle{Max: image.Point{X: 128, Y: 32}}, |
|
|
|
} |
|
|
|
} |
|
|
|
colorEffects = append(monoEffects, |
|
|
|
pixeleffect.FadeOutShift, |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
type DisplayConfig struct { |
|
|
|
Type string `toml:"type"` |
|
|
@ -64,10 +59,10 @@ type Display struct { |
|
|
|
driver.Matrix |
|
|
|
config *DisplayConfig |
|
|
|
bounds image.Rectangle |
|
|
|
buffer *image.RGBA |
|
|
|
layers [4]*image.RGBA |
|
|
|
buffer draw.Image |
|
|
|
layers [4]draw.Image |
|
|
|
animationLayer int |
|
|
|
animationFrame *image.RGBA |
|
|
|
animationFrame draw.Image |
|
|
|
clockLayer int |
|
|
|
color color.RGBA |
|
|
|
mask image.Image |
|
|
@ -100,22 +95,25 @@ func New(config *DisplayConfig, driverConfig *driver.Config) (*Display, error) { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
|
|
|
|
size := matrix.Bounds().Size() |
|
|
|
log.Printf("display: using %dx%d driver %s", size.X, size.Y, matrix.Name()) |
|
|
|
var ( |
|
|
|
bits = matrix.ColorDepth() |
|
|
|
size = matrix.Bounds().Size() |
|
|
|
) |
|
|
|
// log.Printf("display: using %dx%d driver %s", size.X, size.Y, matrix.Name())
|
|
|
|
|
|
|
|
d := &Display{ |
|
|
|
Matrix: matrix, |
|
|
|
config: config, |
|
|
|
bounds: matrix.Bounds(), |
|
|
|
buffer: newLayer(size), |
|
|
|
layers: [4]*image.RGBA{ |
|
|
|
newLayer(size), // Background
|
|
|
|
newLayer(size), // Clock background
|
|
|
|
newLayer(size), // Animation
|
|
|
|
newLayer(size), // Clock foreground
|
|
|
|
bounds: image.Rectangle{Max: size}, |
|
|
|
buffer: newLayer(size, bits), |
|
|
|
layers: [4]draw.Image{ |
|
|
|
newLayer(size, bits), // Background
|
|
|
|
newLayer(size, bits), // Clock background
|
|
|
|
newLayer(size, bits), // Animation
|
|
|
|
newLayer(size, bits), // Clock foreground
|
|
|
|
}, |
|
|
|
animationLayer: layerAnimation, |
|
|
|
animationFrame: newLayer(size), |
|
|
|
animationFrame: newLayer(size, bits), |
|
|
|
clockLayer: layerClockForeground, |
|
|
|
color: colornames.Green, |
|
|
|
mask: image.NewUniform(colornames.Green), |
|
|
@ -129,32 +127,72 @@ func New(config *DisplayConfig, driverConfig *driver.Config) (*Display, error) { |
|
|
|
return d, nil |
|
|
|
} |
|
|
|
|
|
|
|
func newLayer(size image.Point) *image.RGBA { |
|
|
|
i := image.NewRGBA(image.Rectangle{Max: size}) |
|
|
|
clearLayer(i) |
|
|
|
return i |
|
|
|
func newLayer(size image.Point, bpp int) draw.Image { |
|
|
|
switch bpp { |
|
|
|
case 2: |
|
|
|
return pixel.NewBitmap(size.X, size.Y) |
|
|
|
default: |
|
|
|
return image.NewRGBA(image.Rectangle{Max: size}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
var zeros = make([]byte, 128*32*4) |
|
|
|
|
|
|
|
func clearLayer(i draw.Image) { |
|
|
|
switch i := i.(type) { |
|
|
|
case *image.RGBA: |
|
|
|
copy(i.Pix, zeros) |
|
|
|
case *pixel.Bitmap: |
|
|
|
copy(i.Pix, zeros) |
|
|
|
default: |
|
|
|
draw.Draw(i, i.Bounds(), image.Transparent, image.Point{}, draw.Src) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func clearLayer(i *image.RGBA) { |
|
|
|
//draw.Draw(i, i.Bounds(), image.NewUniform(color.Transparent), image.Point{}, draw.Src)
|
|
|
|
copy(i.Pix, emptyPix) |
|
|
|
func fillLayer(i draw.Image, c color.Color) { |
|
|
|
switch i := i.(type) { |
|
|
|
case *image.RGBA: |
|
|
|
var ( |
|
|
|
rm, gm, bm, am = c.RGBA() |
|
|
|
r = uint8(rm) |
|
|
|
g = uint8(gm) |
|
|
|
b = uint8(bm) |
|
|
|
a = uint8(am) |
|
|
|
) |
|
|
|
for n := 0; n < len(i.Pix); n += 4 { |
|
|
|
i.Pix[n+0] = r |
|
|
|
i.Pix[n+1] = g |
|
|
|
i.Pix[n+2] = b |
|
|
|
i.Pix[n+3] = a |
|
|
|
} |
|
|
|
|
|
|
|
default: |
|
|
|
draw.Draw(i, i.Bounds(), &image.Uniform{C: c}, image.Point{}, draw.Src) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func fillLayer(i *image.RGBA, c color.Color) { |
|
|
|
var ( |
|
|
|
rm, gm, bm, am = c.RGBA() |
|
|
|
r = uint8(rm) |
|
|
|
g = uint8(gm) |
|
|
|
b = uint8(bm) |
|
|
|
a = uint8(am) |
|
|
|
) |
|
|
|
for n := 0; n < len(i.Pix); n += 4 { |
|
|
|
i.Pix[n+0] = r |
|
|
|
i.Pix[n+1] = g |
|
|
|
i.Pix[n+2] = b |
|
|
|
i.Pix[n+3] = a |
|
|
|
func drawLayer(dst draw.Image, src, mask image.Image) { |
|
|
|
switch dst := dst.(type) { |
|
|
|
case *image.RGBA: |
|
|
|
switch src := src.(type) { |
|
|
|
case *image.RGBA: |
|
|
|
copy(dst.Pix, src.Pix) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
case *pixel.Bitmap: |
|
|
|
switch src := src.(type) { |
|
|
|
case *pixel.Bitmap: |
|
|
|
copy(dst.Pix, src.Pix) |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if mask != nil { |
|
|
|
draw.DrawMask(dst, dst.Bounds(), mask, image.Point{}, src, image.Point{}, draw.Src) |
|
|
|
} else { |
|
|
|
draw.Draw(dst, dst.Bounds(), src, image.Point{}, draw.Src) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func convertPalette(p color.Palette) []driver.Color { |
|
|
@ -176,11 +214,6 @@ func (d *Display) renderLayer(layer int, frame, mask image.Image) { |
|
|
|
return |
|
|
|
} |
|
|
|
// log.Printf("update: %T %s", frame, frame.Bounds())
|
|
|
|
if i, ok := frame.(*image.RGBA); ok { |
|
|
|
copy(d.layers[layer].Pix, i.Pix) |
|
|
|
d.doRender() |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
defer func() { |
|
|
|
if err := recover(); err != nil { |
|
|
@ -188,12 +221,7 @@ func (d *Display) renderLayer(layer int, frame, mask image.Image) { |
|
|
|
} |
|
|
|
}() |
|
|
|
|
|
|
|
if mask != nil { |
|
|
|
draw.DrawMask(d.layers[layer], d.bounds, mask, image.Point{}, frame, image.Point{}, draw.Src) |
|
|
|
} else { |
|
|
|
draw.Draw(d.layers[layer], d.bounds, frame, image.Point{}, draw.Src) |
|
|
|
} |
|
|
|
|
|
|
|
drawLayer(d.layers[layer], frame, mask) |
|
|
|
d.doRender() |
|
|
|
} |
|
|
|
|
|
|
@ -207,7 +235,8 @@ func (d *Display) Render() error { |
|
|
|
|
|
|
|
func (d *Display) doRender() { |
|
|
|
// log.Println("display: rendering")
|
|
|
|
copy(d.buffer.Pix, d.layers[0].Pix) |
|
|
|
//copy(d.buffer.Pix, d.layers[0].Pix)
|
|
|
|
clearLayer(d.buffer) |
|
|
|
if d.clockLayer == layerClockBackground { |
|
|
|
draw.Draw(d.buffer, d.bounds, d.layers[d.clockLayer], image.Point{}, draw.Over) |
|
|
|
} |
|
|
@ -216,11 +245,12 @@ func (d *Display) doRender() { |
|
|
|
draw.Draw(d.buffer, d.bounds, d.layers[d.clockLayer], image.Point{}, draw.Over) |
|
|
|
} |
|
|
|
draw.Draw(d.Matrix, d.bounds, d.buffer, image.Point{}, draw.Src) |
|
|
|
//draw.Draw(d.Matrix, d.Bounds(), d.layers[layerAnimation], image.Point{}, draw.Src)
|
|
|
|
d.Matrix.Render() |
|
|
|
} |
|
|
|
|
|
|
|
func (d *Display) RenderBackground(back *image.RGBA) { |
|
|
|
copy(d.layers[layerBackground].Pix, back.Pix) |
|
|
|
drawLayer(d.layers[layerBackground], back, nil) |
|
|
|
} |
|
|
|
|
|
|
|
func (d *Display) RenderClock(frames <-chan *image.RGBA) { |
|
|
@ -234,22 +264,27 @@ func (d *Display) RenderClock(frames <-chan *image.RGBA) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
type effectFunc func(time.Duration, pixel.Image) <-chan time.Time |
|
|
|
|
|
|
|
func (d *Display) RenderAnimation(a dmd.Animation) error { |
|
|
|
log.Printf("display: render animation %q for %s", a.Name(), a.Duration()) |
|
|
|
//log.Printf("display: render animation %q for %s", a.Name(), a.Duration())
|
|
|
|
if m, ok := d.Matrix.(*text.Matrix); ok { |
|
|
|
m.Title = fmt.Sprintf("%s (%s)", a.Name(), a.Duration()) |
|
|
|
} |
|
|
|
|
|
|
|
repeat := 1 |
|
|
|
if a.Duration() <= animationShort { |
|
|
|
repeat = animationShortRepeat |
|
|
|
} |
|
|
|
|
|
|
|
// Swap clock layers.
|
|
|
|
// Swap clock layers (maybe).
|
|
|
|
if !d.config.ClockOnTop { |
|
|
|
clockLayer := layerClockBackground |
|
|
|
if a.ClockInFront() { |
|
|
|
clockLayer = layerClockForeground |
|
|
|
} |
|
|
|
if d.clockLayer != clockLayer { |
|
|
|
copy(d.layers[clockLayer].Pix, d.layers[d.clockLayer].Pix) |
|
|
|
drawLayer(d.layers[clockLayer], d.layers[d.clockLayer], nil) |
|
|
|
clearLayer(d.layers[d.clockLayer]) |
|
|
|
d.clockLayer = clockLayer |
|
|
|
} |
|
|
@ -268,22 +303,18 @@ func (d *Display) RenderAnimation(a dmd.Animation) error { |
|
|
|
|
|
|
|
var ( |
|
|
|
frame image.Image |
|
|
|
//total = a.Len()
|
|
|
|
//frames = 1
|
|
|
|
delay time.Duration |
|
|
|
err error |
|
|
|
) |
|
|
|
// for frames <= total {
|
|
|
|
for { |
|
|
|
if frame, delay, err = a.NextFrame(); err == io.EOF { |
|
|
|
break |
|
|
|
} else if err != nil { |
|
|
|
log.Printf("display: animation error: %v", err) |
|
|
|
// log.Printf("display: animation error: %v", err)
|
|
|
|
return err |
|
|
|
} |
|
|
|
|
|
|
|
//log.Printf("display: animation frame %d/%d for %s", frames, total, delay)
|
|
|
|
//frames++
|
|
|
|
//log.Printf("display: animation frame %T of %s", frame, frame.Bounds())
|
|
|
|
|
|
|
|
wait := time.After(delay) |
|
|
|
select { |
|
|
@ -298,51 +329,32 @@ func (d *Display) RenderAnimation(a dmd.Animation) error { |
|
|
|
} |
|
|
|
|
|
|
|
if lastFrame != nil { |
|
|
|
draw.Draw(d.animationFrame, d.bounds, lastFrame, image.Point{}, draw.Src) |
|
|
|
var effect func(time.Duration, pixel.Image) <-chan time.Time |
|
|
|
switch d.Matrix.ColorDepth() { |
|
|
|
case 2: |
|
|
|
// Monochrome fade out.
|
|
|
|
switch rand.Intn(4) { |
|
|
|
case 0: |
|
|
|
log.Println("display: fade out horizontal") |
|
|
|
effect = pixeleffect.FadeOutHorizontal |
|
|
|
case 1: |
|
|
|
log.Println("display: fade out vertical") |
|
|
|
effect = pixeleffect.FadeOutVertical |
|
|
|
case 2: |
|
|
|
log.Println("display: fade out diagonal") |
|
|
|
effect = pixeleffect.FadeOutDiagonal |
|
|
|
case 3: |
|
|
|
log.Println("display: fade out dither") |
|
|
|
effect = pixeleffect.FadeOutDither |
|
|
|
} |
|
|
|
default: |
|
|
|
switch rand.Intn(5) { |
|
|
|
case 0: |
|
|
|
log.Println("display: fade out horizontal") |
|
|
|
effect = pixeleffect.FadeOutHorizontal |
|
|
|
case 1: |
|
|
|
log.Println("display: fade out vertical") |
|
|
|
effect = pixeleffect.FadeOutVertical |
|
|
|
case 2: |
|
|
|
log.Println("display: fade out diagonal") |
|
|
|
effect = pixeleffect.FadeOutDiagonal |
|
|
|
case 3: |
|
|
|
log.Println("display: fade out dither") |
|
|
|
effect = pixeleffect.FadeOutDither |
|
|
|
case 4: |
|
|
|
log.Println("display: fade out shift") |
|
|
|
effect = pixeleffect.FadeOutShift |
|
|
|
} |
|
|
|
} |
|
|
|
effect = pixeleffect.FadeOutDiagonal |
|
|
|
for range effect(animationFadeOut, d.animationFrame) { |
|
|
|
d.update <- newDisplayUpdateLayer(layerAnimation, d.animationFrame, mask) |
|
|
|
} |
|
|
|
draw.Draw(d.animationFrame, d.animationFrame.Rect, image.Transparent, image.Point{}, draw.Src) |
|
|
|
d.update <- newDisplayUpdateLayer(layerAnimation, d.animationFrame, mask) |
|
|
|
d.showEffect(lastFrame, mask) |
|
|
|
} |
|
|
|
|
|
|
|
if m, ok := d.Matrix.(*text.Matrix); ok { |
|
|
|
m.Title = "" |
|
|
|
} |
|
|
|
|
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
func (d *Display) showEffect(frame, mask image.Image) { |
|
|
|
draw.Draw(d.animationFrame, d.bounds, frame, image.Point{}, draw.Src) |
|
|
|
|
|
|
|
var effect effectFunc |
|
|
|
switch d.Matrix.ColorDepth() { |
|
|
|
case 2: |
|
|
|
// Monochrome fade out.
|
|
|
|
effect = monoEffects[rand.Intn(len(monoEffects))] |
|
|
|
default: |
|
|
|
effect = colorEffects[rand.Intn(len(colorEffects))] |
|
|
|
} |
|
|
|
|
|
|
|
for range effect(animationFadeOut, d.animationFrame) { |
|
|
|
d.update <- newDisplayUpdateLayer(layerAnimation, d.animationFrame, mask) |
|
|
|
} |
|
|
|
|
|
|
|
clearLayer(d.animationFrame) // TODO (maze): is this required?
|
|
|
|
d.update <- newDisplayUpdateLayer(layerAnimation, d.animationFrame, mask) |
|
|
|
} |
|
|
|