From 4f995754b0d8a559aa19cafd77fe12222074219f Mon Sep 17 00:00:00 2001 From: Michael Hanselmann Date: Thu, 23 Dec 2021 20:44:23 +0100 Subject: [PATCH] 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 --- waveshare2in13v2/drawing.go | 24 ++++++------------------ waveshare2in13v2/drawing_test.go | 16 ++++++++-------- waveshare2in13v2/waveshare213v2.go | 17 +++++++++++++++-- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/waveshare2in13v2/drawing.go b/waveshare2in13v2/drawing.go index f606a11..05ef1ae 100644 --- a/waveshare2in13v2/drawing.go +++ b/waveshare2in13v2/drawing.go @@ -54,6 +54,7 @@ func setMemoryArea(ctrl controller, area image.Rectangle) { type drawOpts struct { cmd byte devSize image.Point + buffer *image1bit.VerticalLSB dstRect image.Rectangle src image.Image srcPts image.Point @@ -63,15 +64,9 @@ type drawSpec struct { // Destination on display in pixels, normalized to fit into actual size. DstRect image.Rectangle - // Size of memory area to write; horizontally in bytes, vertically in - // pixels. + // Area to send to device; horizontally in bytes (thus aligned to + // 8 pixels), vertically in pixels. 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 { @@ -83,12 +78,6 @@ func (o *drawOpts) spec() drawSpec { s.DstRect.Min.X/8, s.DstRect.Min.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 } @@ -101,8 +90,7 @@ func drawImage(ctrl controller, opts *drawOpts) { return } - img := image1bit.NewVerticalLSB(image.Rectangle{Max: s.BufferSize}) - draw.Src.Draw(img, s.BufferRect, opts.src, opts.srcPts) + draw.Src.Draw(opts.buffer, s.DstRect, opts.src, opts.srcPts) setMemoryArea(ctrl, s.MemRect) @@ -110,12 +98,12 @@ func drawImage(ctrl controller, opts *drawOpts) { 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++ { rowData[x] = 0 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 } } diff --git a/waveshare2in13v2/drawing_test.go b/waveshare2in13v2/drawing_test.go index 7ad5476..dc9a384 100644 --- a/waveshare2in13v2/drawing_test.go +++ b/waveshare2in13v2/drawing_test.go @@ -27,26 +27,24 @@ func TestDrawSpec(t *testing.T) { name: "smaller than display", opts: drawOpts{ devSize: image.Pt(100, 200), + buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 120, 210)), dstRect: image.Rect(17, 4, 25, 8), }, want: drawSpec{ - DstRect: image.Rect(17, 4, 25, 8), - MemRect: image.Rect(2, 4, 4, 8), - BufferSize: image.Pt(16, 4), - BufferRect: image.Rect(1, 0, 9, 4), + DstRect: image.Rect(17, 4, 25, 8), + MemRect: image.Rect(2, 4, 4, 8), }, }, { name: "larger than display", opts: drawOpts{ devSize: image.Pt(100, 200), + buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 100, 200)), dstRect: image.Rect(-20, 50, 125, 300), }, want: drawSpec{ - DstRect: image.Rect(0, 50, 100, 200), - MemRect: image.Rect(0, 50, 13, 200), - BufferSize: image.Pt(13*8, 150), - BufferRect: image.Rect(0, 0, 100, 150), + DstRect: image.Rect(0, 50, 100, 200), + MemRect: image.Rect(0, 50, 13, 200), }, }, } { @@ -77,6 +75,7 @@ func TestDrawImage(t *testing.T) { opts: drawOpts{ cmd: writeRAMBW, devSize: image.Pt(64, 64), + buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 64, 64)), dstRect: image.Rect(17, 4, 41, 8), src: &image.Uniform{image1bit.On}, srcPts: image.Pt(0, 0), @@ -98,6 +97,7 @@ func TestDrawImage(t *testing.T) { opts: drawOpts{ cmd: writeRAMBW, devSize: image.Pt(80, 120), + buffer: image1bit.NewVerticalLSB(image.Rect(0, 0, 80, 120)), dstRect: image.Rect(0, 0, 80, 120), src: &image.Uniform{image1bit.On}, srcPts: image.Pt(33, 44), diff --git a/waveshare2in13v2/waveshare213v2.go b/waveshare2in13v2/waveshare213v2.go index 11b4deb..1e7454c 100644 --- a/waveshare2in13v2/waveshare213v2.go +++ b/waveshare2in13v2/waveshare213v2.go @@ -8,6 +8,7 @@ import ( "fmt" "image" "image/color" + "image/draw" "time" "periph.io/x/conn/v3" @@ -62,6 +63,8 @@ type Dev struct { rst gpio.PinOut busy gpio.PinIn + buffer *image1bit.VerticalLSB + opts *Opts } @@ -139,9 +142,15 @@ func New(p spi.Port, dc, cs, rst gpio.PinOut, busy gpio.PinIn, opts *Opts) (*Dev cs: cs, rst: rst, busy: busy, + buffer: image1bit.NewVerticalLSB(image.Rectangle{ + Max: image.Pt((opts.Width+7)/8*8, opts.Height), + }), opts: opts, } + // Default color + draw.Src.Draw(d.buffer, d.buffer.Bounds(), &image.Uniform{image1bit.On}, image.Point{}) + return d, nil } @@ -176,8 +185,10 @@ func (d *Dev) Init(partialUpdate PartialUpdate) error { func (d *Dev) Clear(color color.Color) error { eh := errorHandler{d: *d} - clearDisplay(&eh, image.Pt(d.opts.Width, d.opts.Height), - image1bit.BitModel.Convert(color).(image1bit.Bit)) + c := 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 { eh.err = d.turnOnDisplay() @@ -201,6 +212,7 @@ func (d *Dev) Draw(dstRect image.Rectangle, src image.Image, srcPts image.Point) opts := drawOpts{ cmd: writeRAMBW, devSize: image.Pt(d.opts.Width, d.opts.Height), + buffer: d.buffer, dstRect: dstRect, src: src, 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 { opts := drawOpts{ devSize: image.Pt(d.opts.Width, d.opts.Height), + buffer: d.buffer, dstRect: dstRect, src: src, srcPts: srcPts,