waveshare213v2: Keep display contents in shadow buffer

This is necessary for partial updates of the display--not just
incremental updates of the full display, but updating part of the
display in general.

On the horizontal axis only whole bytes can be written, so updates need
to be aligned to 8 pixels. One option would be to require the caller of
Dev.Draw to only operate at multiples of 8. Another would be to read
back the contents of bytes to be updated partially and combine the data.
According to the datasheet the controller supports that (command 0x27).

The simplest option, however, is to maintain a shadow buffer in the
driver. Updates are applied to the buffer from which only the changed
destination rectangle is sent to the display. The permanent cost is
a bit more than 4kB of memory for a 122x250 display while saving on
memory allocations for a temporary image buffer on updates.

Signed-off-by: Michael Hanselmann <public@hansmi.ch>
pull/41/head
Michael Hanselmann 4 years ago
parent cbda71a0c8
commit bb13c4c789

@ -54,6 +54,7 @@ func setMemoryArea(ctrl controller, area image.Rectangle) {
type drawOpts struct { type drawOpts struct {
cmd byte cmd byte
devSize image.Point devSize image.Point
buffer *image1bit.VerticalLSB
dstRect image.Rectangle dstRect image.Rectangle
src image.Image src image.Image
srcPts image.Point srcPts image.Point
@ -63,15 +64,9 @@ type drawSpec struct {
// Destination on display in pixels, normalized to fit into actual size. // Destination on display in pixels, normalized to fit into actual size.
DstRect image.Rectangle DstRect image.Rectangle
// Size of memory area to write; horizontally in bytes, vertically in // Area to send to device; horizontally in bytes (thus aligned to
// pixels. // 8 pixels), vertically in pixels.
MemRect image.Rectangle MemRect image.Rectangle
// Size of image buffer, horizontally aligned to multiples of 8 pixels.
BufferSize image.Point
// Destination rectangle within image buffer.
BufferRect image.Rectangle
} }
func (o *drawOpts) spec() drawSpec { func (o *drawOpts) spec() drawSpec {
@ -83,12 +78,6 @@ func (o *drawOpts) spec() drawSpec {
s.DstRect.Min.X/8, s.DstRect.Min.Y, s.DstRect.Min.X/8, s.DstRect.Min.Y,
(s.DstRect.Max.X+7)/8, s.DstRect.Max.Y, (s.DstRect.Max.X+7)/8, s.DstRect.Max.Y,
) )
s.BufferSize = image.Pt(s.MemRect.Dx()*8, s.MemRect.Dy())
s.BufferRect = image.Rectangle{
Min: image.Point{X: s.DstRect.Min.X - (s.MemRect.Min.X * 8)},
Max: image.Point{Y: s.DstRect.Dy()},
}
s.BufferRect.Max.X = s.BufferRect.Min.X + s.DstRect.Dx()
return s return s
} }
@ -101,8 +90,7 @@ func drawImage(ctrl controller, opts *drawOpts) {
return return
} }
img := image1bit.NewVerticalLSB(image.Rectangle{Max: s.BufferSize}) draw.Src.Draw(opts.buffer, s.DstRect, opts.src, opts.srcPts)
draw.Src.Draw(img, s.BufferRect, opts.src, opts.srcPts)
setMemoryArea(ctrl, s.MemRect) setMemoryArea(ctrl, s.MemRect)
@ -110,12 +98,12 @@ func drawImage(ctrl controller, opts *drawOpts) {
rowData := make([]byte, s.MemRect.Dx()) rowData := make([]byte, s.MemRect.Dx())
for y := 0; y < img.Bounds().Dy(); y++ { for y := s.MemRect.Min.Y; y < s.MemRect.Max.Y; y++ {
for x := 0; x < len(rowData); x++ { for x := 0; x < len(rowData); x++ {
rowData[x] = 0 rowData[x] = 0
for bit := 0; bit < 8; bit++ { for bit := 0; bit < 8; bit++ {
if img.BitAt((x*8)+bit, y) { if opts.buffer.BitAt(((s.MemRect.Min.X+x)*8)+bit, y) {
rowData[x] |= 0x80 >> bit rowData[x] |= 0x80 >> bit
} }
} }

@ -27,26 +27,24 @@ func TestDrawSpec(t *testing.T) {
name: "smaller than display", name: "smaller than display",
opts: drawOpts{ opts: drawOpts{
devSize: image.Pt(100, 200), devSize: image.Pt(100, 200),
buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 120, 210)),
dstRect: image.Rect(17, 4, 25, 8), dstRect: image.Rect(17, 4, 25, 8),
}, },
want: drawSpec{ want: drawSpec{
DstRect: image.Rect(17, 4, 25, 8), DstRect: image.Rect(17, 4, 25, 8),
MemRect: image.Rect(2, 4, 4, 8), MemRect: image.Rect(2, 4, 4, 8),
BufferSize: image.Pt(16, 4),
BufferRect: image.Rect(1, 0, 9, 4),
}, },
}, },
{ {
name: "larger than display", name: "larger than display",
opts: drawOpts{ opts: drawOpts{
devSize: image.Pt(100, 200), devSize: image.Pt(100, 200),
buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 100, 200)),
dstRect: image.Rect(-20, 50, 125, 300), dstRect: image.Rect(-20, 50, 125, 300),
}, },
want: drawSpec{ want: drawSpec{
DstRect: image.Rect(0, 50, 100, 200), DstRect: image.Rect(0, 50, 100, 200),
MemRect: image.Rect(0, 50, 13, 200), MemRect: image.Rect(0, 50, 13, 200),
BufferSize: image.Pt(13*8, 150),
BufferRect: image.Rect(0, 0, 100, 150),
}, },
}, },
} { } {
@ -77,6 +75,7 @@ func TestDrawImage(t *testing.T) {
opts: drawOpts{ opts: drawOpts{
cmd: writeRAMBW, cmd: writeRAMBW,
devSize: image.Pt(64, 64), devSize: image.Pt(64, 64),
buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 64, 64)),
dstRect: image.Rect(17, 4, 41, 8), dstRect: image.Rect(17, 4, 41, 8),
src: &image.Uniform{image1bit.On}, src: &image.Uniform{image1bit.On},
srcPts: image.Pt(0, 0), srcPts: image.Pt(0, 0),
@ -98,6 +97,7 @@ func TestDrawImage(t *testing.T) {
opts: drawOpts{ opts: drawOpts{
cmd: writeRAMBW, cmd: writeRAMBW,
devSize: image.Pt(80, 120), devSize: image.Pt(80, 120),
buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 80, 120)),
dstRect: image.Rect(0, 0, 80, 120), dstRect: image.Rect(0, 0, 80, 120),
src: &image.Uniform{image1bit.On}, src: &image.Uniform{image1bit.On},
srcPts: image.Pt(33, 44), srcPts: image.Pt(33, 44),

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"image/draw"
"time" "time"
"periph.io/x/conn/v3" "periph.io/x/conn/v3"
@ -62,6 +63,8 @@ type Dev struct {
rst gpio.PinOut rst gpio.PinOut
busy gpio.PinIn busy gpio.PinIn
buffer *image1bit.VerticalLSB
opts *Opts opts *Opts
} }
@ -139,9 +142,15 @@ func New(p spi.Port, dc, cs, rst gpio.PinOut, busy gpio.PinIn, opts *Opts) (*Dev
cs: cs, cs: cs,
rst: rst, rst: rst,
busy: busy, busy: busy,
buffer: image1bit.NewVerticalLSB(image.Rectangle{
Max: image.Pt((opts.Width+7)/8*8, opts.Height),
}),
opts: opts, opts: opts,
} }
// Default color
draw.Src.Draw(d.buffer, d.buffer.Bounds(), &image.Uniform{image1bit.On}, image.Point{})
return d, nil return d, nil
} }
@ -176,8 +185,10 @@ func (d *Dev) Init(partialUpdate PartialUpdate) error {
func (d *Dev) Clear(color color.Color) error { func (d *Dev) Clear(color color.Color) error {
eh := errorHandler{d: *d} eh := errorHandler{d: *d}
clearDisplay(&eh, image.Pt(d.opts.Width, d.opts.Height), c := image1bit.BitModel.Convert(color).(image1bit.Bit)
image1bit.BitModel.Convert(color).(image1bit.Bit)) draw.Src.Draw(d.buffer, d.buffer.Bounds(), &image.Uniform{c}, image.Point{})
clearDisplay(&eh, image.Pt(d.opts.Width, d.opts.Height), c)
if eh.err == nil { if eh.err == nil {
eh.err = d.turnOnDisplay() eh.err = d.turnOnDisplay()
@ -201,6 +212,7 @@ func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPts image.Point)
opts := drawOpts{ opts := drawOpts{
cmd: writeRAMBW, cmd: writeRAMBW,
devSize: image.Pt(d.opts.Width, d.opts.Height), devSize: image.Pt(d.opts.Width, d.opts.Height),
buffer: d.buffer,
dstRect: dstRect, dstRect: dstRect,
src: src, src: src,
srcPts: srcPts, srcPts: srcPts,
@ -221,6 +233,7 @@ func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPts image.Point)
func (d *Dev) DrawPartial(dstRect image.Rectangle, src image.Image, srcPts image.Point) error { func (d *Dev) DrawPartial(dstRect image.Rectangle, src image.Image, srcPts image.Point) error {
opts := drawOpts{ opts := drawOpts{
devSize: image.Pt(d.opts.Width, d.opts.Height), devSize: image.Pt(d.opts.Width, d.opts.Height),
buffer: d.buffer,
dstRect: dstRect, dstRect: dstRect,
src: src, src: src,
srcPts: srcPts, srcPts: srcPts,

Loading…
Cancel
Save