Browse Source

Support for monochrome bitmaps

master
maze 7 months ago
parent
commit
19795a2fd2
6 changed files with 157 additions and 140 deletions
  1. +1
    -1
      clock.go
  2. +129
    -117
      display.go
  3. +19
    -13
      driver/text/driver.go
  4. +5
    -8
      font.go
  5. +1
    -1
      go.mod
  6. +2
    -0
      go.sum

+ 1
- 1
clock.go View File

@ -68,7 +68,7 @@ func NewClock(config *ClockConfig) (*Clock, error) {
return &Clock{
config: config,
buffer: newLayer(image.Pt(128, 32)),
buffer: newLayer(image.Pt(128, 32), 8).(*image.RGBA),
bounds: image.Rect(0, 0, 128, 32),
font: f,
fontSize: f.Size(),


+ 129
- 117
display.go View File

@ -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)
}

+ 19
- 13
driver/text/driver.go View File

@ -5,6 +5,7 @@ import (
"image"
"image/color"
"os"
"strings"
"sync/atomic"
"syscall"
@ -17,7 +18,8 @@ func init() {
driver.Register("text", New)
}
type textMatrix struct {
type Matrix struct {
Title string
screen tcell.Screen
width int
height int
@ -30,7 +32,7 @@ func New(config *driver.Config) (driver.Matrix, error) {
return newDriver(config)
}
func newDriver(config *driver.Config) (*textMatrix, error) {
func newDriver(config *driver.Config) (*Matrix, error) {
screen, err := tcell.NewTerminfoScreen()
if err != nil {
return nil, err
@ -45,7 +47,7 @@ func newDriver(config *driver.Config) (*textMatrix, error) {
screen.HideCursor()
w, h := config.Geometry()
return &textMatrix{
return &Matrix{
screen: screen,
width: w,
height: h,
@ -75,35 +77,39 @@ func handleEvents(screen tcell.Screen) {
}
}
func (m *textMatrix) Close() error {
func (m *Matrix) Close() error {
return syscall.Kill(os.Getpid(), syscall.SIGKILL)
}
func (textMatrix) Name() string {
func (Matrix) Name() string {
return "test matrix"
}
func (m *textMatrix) Bounds() image.Rectangle {
func (m *Matrix) Bounds() image.Rectangle {
return image.Rect(0, 0, m.width, m.height)
}
// Swap buffers.
func (m *textMatrix) Swap(leds []driver.Color) (prev []driver.Color, err error) {
func (m *Matrix) Swap(leds []driver.Color) (prev []driver.Color, err error) {
return
}
func (m *textMatrix) Render() error {
func (m *Matrix) Render() error {
var (
call = atomic.AddInt64(&m.render, 1)
info = fmt.Sprintf("text matrix %dx%d frame %d", m.width, m.height, call)
style = tcell.StyleDefault.Foreground(tcell.ColorWhite).Background(tcell.ColorBlue)
)
if m.Title != "" {
info += fmt.Sprintf(": %s", m.Title)
}
info += strings.Repeat(" ", m.width-len(info))
m.screen.SetCell(0, 0, style, []rune(info)...)
m.screen.Show()
return nil
}
func (m *textMatrix) At(x, y int) color.Color {
func (m *Matrix) At(x, y int) color.Color {
if !image.Pt(x, y).In(m.Bounds()) {
return color.Transparent
}
@ -120,15 +126,15 @@ func toColor(c tcell.Color) color.Color {
}
}
func (textMatrix) ColorDepth() int {
func (Matrix) ColorDepth() int {
return 24
}
func (m *textMatrix) ColorModel() color.Model {
func (m *Matrix) ColorModel() color.Model {
return driver.ColorModel
}
func (m *textMatrix) Set(x, y int, c color.Color) {
func (m *Matrix) Set(x, y int, c color.Color) {
if !image.Pt(x, y).In(m.Bounds()) {
return
}
@ -149,4 +155,4 @@ func (m *textMatrix) Set(x, y int, c color.Color) {
}
}
var _ driver.Matrix = (*textMatrix)(nil)
var _ driver.Matrix = (*Matrix)(nil)

+ 5
- 8
font.go View File

@ -7,16 +7,13 @@ import (
"image/draw"
"image/png"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"golang.org/x/image/math/fixed"
"golang.org/x/image/font"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
type Font interface {
@ -80,7 +77,7 @@ func LoadPNGFont(pngFile, xmlFile string) (Font, error) {
return nil, err
}
log.Printf("description: %#+v", description)
// log.Printf("description: %#+v", description)
if f, err = os.Open(pngFile); err != nil {
return nil, err
@ -189,9 +186,9 @@ func calcDimensions(face font.Face) image.Point {
if h > y {
y = h
}
log.Printf("font: glyph %c is %dx%d", r, w, h)
// log.Printf("font: glyph %c is %dx%d", r, w, h)
}
log.Printf("bounds: %d,%d", x, y)
// log.Printf("bounds: %d,%d", x, y)
return image.Pt(x, y)
}

+ 1
- 1
go.mod View File

@ -9,7 +9,7 @@ require (
github.com/labstack/echo/v4 v4.1.16
github.com/urfave/cli/v2 v2.2.0
golang.org/x/image v0.0.0-20200618115811-c13761719519
maze.io/x/dmd v1.0.0
maze.io/x/dmd v1.0.2
maze.io/x/pixel v0.1.4
periph.io/x/periph v3.6.4+incompatible
)


+ 2
- 0
go.sum View File

@ -71,6 +71,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
maze.io/x/dmd v1.0.0 h1:aBF1erx/ij9eotP0aCNMmFfAigI3oqU72x86AclKKcU=
maze.io/x/dmd v1.0.0/go.mod h1:wTAeU3KqnGOU0j1r1ZLPuOIOHrkET8wLLqVrtwjcX7g=
maze.io/x/dmd v1.0.2 h1:rwb7eaQFf51X/kYeDW87PfvsMHFUG1KBxm5O2dtbHOs=
maze.io/x/dmd v1.0.2/go.mod h1:wTAeU3KqnGOU0j1r1ZLPuOIOHrkET8wLLqVrtwjcX7g=
maze.io/x/pixel v0.1.4 h1:TrYY8uydO8jtKCP+wqW2FqNZ20I5zp0GAEoA2fRAZ2Y=
maze.io/x/pixel v0.1.4/go.mod h1:sQ0+PZUApBxJiEbYTp1cZAefIzx0DrWxp+loPzc85vE=
periph.io/x/periph v3.6.4+incompatible h1:8FyXTbu9lcMVofz8mf+cj1pzTLN4V6EuPY2EF+DoJF4=


Loading…
Cancel
Save