From 94908ff801d59fef59adb72e9517a6bdb575672b Mon Sep 17 00:00:00 2001 From: Marc-Antoine Ruel Date: Sun, 28 Mar 2021 11:35:38 -0400 Subject: [PATCH] screen1d: import from periph/extra Tweak it a bit to expose the palette as an option. Uses the same license. This is the first commit that causes a dependency on golang.org/x/sys. --- go.mod | 2 + go.sum | 9 +++ screen1d/screen1d.go | 131 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 screen1d/screen1d.go diff --git a/go.mod b/go.mod index 4c070d8..3157d65 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,8 @@ go 1.13 require ( github.com/fogleman/gg v1.3.0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + github.com/maruel/ansi256 v1.0.2 + github.com/mattn/go-colorable v0.1.8 golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb periph.io/x/conn/v3 v3.6.7 periph.io/x/host/v3 v3.6.7 diff --git a/go.sum b/go.sum index 32c3936..5defd74 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,17 @@ github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/maruel/ansi256 v1.0.2 h1:AE5gYrrZ5vQaFTTwy5vxva8Bak7p7wID3Uqu3t1j3No= +github.com/maruel/ansi256 v1.0.2/go.mod h1:x7uow2KFkUgjdzvYHyfZuMEOTGKvCYLyVUHIVg1vYic= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk= golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= periph.io/x/conn/v3 v3.6.7 h1:hem/gzoUI0tnvdJOJAk+XLBhqBGX9sHkwShBXRGGy0k= periph.io/x/conn/v3 v3.6.7/go.mod h1:3OD27w9YVa5DS97VsUxsPGzD9Qrm5Ny7cF5b6xMMIWg= diff --git a/screen1d/screen1d.go b/screen1d/screen1d.go new file mode 100644 index 0000000..ab50b8e --- /dev/null +++ b/screen1d/screen1d.go @@ -0,0 +1,131 @@ +// Copyright 2017 The Periph Authors. All rights reserved. +// Use of this source code is governed under the Apache License, Version 2.0 +// that can be found in the LICENSE file. + +// Package screen1d implements a 1D display.Drawer that outputs to terminal +// (stdout) using ANSI color codes. +// +// Useful while you are waiting for your super nice APA-102 LED strip to come +// by mail. +package screen1d + +import ( + "bytes" + "errors" + "fmt" + "image" + "image/color" + "io" + + "github.com/maruel/ansi256" + "github.com/mattn/go-colorable" + "periph.io/x/conn/v3/display" +) + +// Opts represents the options available for this display. +type Opts struct { + X int + Palette *ansi256.Palette +} + +// Dev is a 1D LED strip emulator that outputs to the console. +type Dev struct { + w io.Writer + l int + palette ansi256.Palette + + pixels []byte + buf bytes.Buffer +} + +// New returns a Dev that displays at the console. +// +// Permits to do local testing of LEDs animation. +func New(opts *Opts) *Dev { + p := opts.Palette + if p == nil { + p = ansi256.Default + } + d := &Dev{ + w: colorable.NewColorableStdout(), + l: opts.X, + palette: *p, + pixels: make([]byte, 3*opts.X), + } + return d +} + +func (d *Dev) String() string { + return "Screen1D" +} + +// Halt implements conn.Resource. +// +// It clears the display so it is not corrupted. +func (d *Dev) Halt() error { + _, err := d.w.Write([]byte("\n\033[0m")) + if err != nil { + return err + } + return nil +} + +// Write accepts a stream of raw RGB pixels and writes it to the console. +func (d *Dev) Write(pixels []byte) (int, error) { + if len(pixels)%3 != 0 { + return 0, errors.New("invalid RGB stream length") + } + copy(d.pixels, pixels) + return d.refresh() +} + +// ColorModel implements display.Drawer. +func (d *Dev) ColorModel() color.Model { + return color.NRGBAModel +} + +// Bounds implements display.Drawer. +func (d *Dev) Bounds() image.Rectangle { + return image.Rectangle{Max: image.Point{X: d.l, Y: 1}} +} + +// Draw implements display.Drawer. +func (d *Dev) Draw(r image.Rectangle, src image.Image, sp image.Point) error { + r = r.Intersect(d.Bounds()) + srcR := src.Bounds() + srcR.Min = srcR.Min.Add(sp) + if dX := r.Dx(); dX < srcR.Dx() { + srcR.Max.X = srcR.Min.X + dX + } + if dY := r.Dy(); dY < srcR.Dy() { + srcR.Max.Y = srcR.Min.Y + dY + } + // TODO(maruel): Allow non-full screen drawing. + // Generic version. + deltaX3 := 3 * (r.Min.X - srcR.Min.X) + for sX := srcR.Min.X; sX < srcR.Max.X; sX++ { + r16, g16, b16, _ := src.At(sX, srcR.Min.Y).RGBA() + dX3 := 3*sX + deltaX3 + d.pixels[dX3] = byte(r16 >> 8) + d.pixels[dX3+1] = byte(g16 >> 8) + d.pixels[dX3+2] = byte(b16 >> 8) + } + _, err := d.refresh() + return err +} + +func (d *Dev) refresh() (int, error) { + // This code is designed to minimize the amount of memory allocated per call. + d.buf.Reset() + _, _ = d.buf.WriteString("\r\033[0m") + for i := 0; i < len(d.pixels)/3; i++ { + c := color.NRGBA{d.pixels[3*i], d.pixels[3*i+1], d.pixels[3*i+2], 255} + _, _ = io.WriteString(&d.buf, d.palette.Block(c)) + } + _, _ = d.buf.WriteString("\033[0m ") + _, err := d.buf.WriteTo(d.w) + return len(d.pixels), err +} + +var _ display.Drawer = &Dev{} +var _ fmt.Stringer = &Dev{}