You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/waveshare2in13v3/drawing.go

224 lines
5.8 KiB
Go

// Copyright 2021 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 waveshare2in13v3
import (
"encoding/binary"
"image"
"image/draw"
"periph.io/x/devices/v3/ssd1306/image1bit"
)
// setMemoryArea configures the target drawing area (horizontal is in bytes,
// vertical in pixels).
func setMemoryArea(ctrl controller, area image.Rectangle) {
startX, endX := uint8(area.Min.X), uint8(area.Max.X-1)
startY, endY := uint16(area.Min.Y), uint16(area.Max.Y-1)
startEndY := [4]byte{}
binary.LittleEndian.PutUint16(startEndY[0:], startY)
binary.LittleEndian.PutUint16(startEndY[2:], endY)
ctrl.sendCommand(dataEntryModeSetting)
ctrl.sendData([]byte{
// Y increment, X increment; update address counter in X direction
0b011,
})
ctrl.sendCommand(setRAMXAddressStartEndPosition)
ctrl.sendData([]byte{startX, endX})
ctrl.sendCommand(setRAMYAddressStartEndPosition)
ctrl.sendData(startEndY[:4])
ctrl.sendCommand(setRAMXAddressCounter)
ctrl.sendData([]byte{startX})
ctrl.sendCommand(setRAMYAddressCounter)
ctrl.sendData(startEndY[:2])
}
type drawOpts struct {
commands []byte
devSize image.Point
origin Corner
buffer *image1bit.VerticalLSB
dstRect image.Rectangle
src image.Image
srcPts image.Point
}
type drawSpec struct {
// Amount by which buffer contents must be moved to align with the physical
// top-left corner of the display.
//
// TODO: The offset shifts the buffer contents to be aligned such that the
// translated position of the physical, on-display (0,0) location is at
// a multiple of 8 on the equivalent to the physical X axis. With a bit of
// additional work transfers for the TopRight and BottomLeft origins should
// not require per-pixel processing by exploiting image1bit.VerticalLSB's
// underlying pixel storage format.
bufferDstOffset image.Point
// Destination in buffer in pixels.
bufferDstRect image.Rectangle
// Destination in device RAM, rotated and shifted to match the origin.
memDstRect image.Rectangle
// Area to send to device; horizontally in bytes (thus aligned to
// 8 pixels), vertically in pixels. Computed from memDstRect.
memRect image.Rectangle
}
// spec pre-computes the various offsets required for sending image updates to
// the device.
func (o *drawOpts) spec() drawSpec {
s := drawSpec{
bufferDstRect: image.Rectangle{Max: o.devSize}.Intersect(o.dstRect),
}
switch o.origin {
case TopRight:
s.bufferDstOffset.Y = o.buffer.Bounds().Dy() - o.devSize.Y
case BottomRight:
s.bufferDstOffset.Y = o.buffer.Bounds().Dy() - o.devSize.Y
s.bufferDstOffset.X = o.buffer.Bounds().Dx() - o.devSize.X
case BottomLeft:
s.bufferDstOffset.Y = o.buffer.Bounds().Dy() - o.devSize.Y
s.bufferDstOffset.X = o.buffer.Bounds().Dx() - o.devSize.X
}
if !s.bufferDstRect.Empty() {
switch o.origin {
case TopLeft:
s.memDstRect = s.bufferDstRect
case TopRight:
s.memDstRect.Min.X = o.devSize.Y - s.bufferDstRect.Max.Y
s.memDstRect.Max.X = o.devSize.Y - s.bufferDstRect.Min.Y
s.memDstRect.Min.Y = s.bufferDstRect.Min.X
s.memDstRect.Max.Y = s.bufferDstRect.Max.X
case BottomRight:
s.memDstRect.Min.X = o.devSize.X - s.bufferDstRect.Max.X
s.memDstRect.Max.X = o.devSize.X - s.bufferDstRect.Min.X
s.memDstRect.Min.Y = o.devSize.Y - s.bufferDstRect.Max.Y
s.memDstRect.Max.Y = o.devSize.Y - s.bufferDstRect.Min.Y
case BottomLeft:
s.memDstRect.Min.X = s.bufferDstRect.Min.Y
s.memDstRect.Max.X = s.bufferDstRect.Max.Y
s.memDstRect.Min.Y = o.devSize.X - s.bufferDstRect.Max.X
s.memDstRect.Max.Y = o.devSize.X - s.bufferDstRect.Min.X
}
s.bufferDstRect = s.bufferDstRect.Add(s.bufferDstOffset)
s.memRect.Min.X = s.memDstRect.Min.X / 8
s.memRect.Max.X = (s.memDstRect.Max.X + 7) / 8
s.memRect.Min.Y = s.memDstRect.Min.Y
s.memRect.Max.Y = s.memDstRect.Max.Y
}
return s
}
// sendImage sends an image to the controller after setting up the registers.
func (o *drawOpts) sendImage(ctrl controller, cmd byte, spec *drawSpec) {
if spec.memRect.Empty() {
return
}
setMemoryArea(ctrl, spec.memRect)
ctrl.sendCommand(cmd)
var posFor func(destY, destX, bit int) image.Point
switch o.origin {
case TopLeft:
posFor = func(destY, destX, bit int) image.Point {
return image.Point{
X: destX + bit,
Y: destY,
}
}
case TopRight:
posFor = func(destY, destX, bit int) image.Point {
return image.Point{
X: destY,
Y: o.devSize.Y - destX - bit - 1,
}
}
case BottomRight:
posFor = func(destY, destX, bit int) image.Point {
return image.Point{
X: o.devSize.X - destX - bit - 1,
Y: o.devSize.Y - destY - 1,
}
}
case BottomLeft:
posFor = func(destY, destX, bit int) image.Point {
return image.Point{
X: o.devSize.X - destY - 1,
Y: destX + bit,
}
}
}
rowData := make([]byte, spec.memRect.Dx())
for destY := spec.memRect.Min.Y; destY < spec.memRect.Max.Y; destY++ {
for destX := 0; destX < len(rowData); destX++ {
rowData[destX] = 0
for bit := 0; bit < 8; bit++ {
bufPos := posFor(destY, (spec.memRect.Min.X+destX)*8, bit)
bufPos = bufPos.Add(spec.bufferDstOffset)
if o.buffer.BitAt(bufPos.X, bufPos.Y) {
rowData[destX] |= 0x80 >> bit
}
}
}
ctrl.sendData(rowData)
}
}
func drawImage(ctrl controller, opts *drawOpts, mode PartialUpdate) {
s := opts.spec()
if s.memRect.Empty() {
return
}
// The buffer is kept in logical orientation. Rotation and alignment with
// the origin happens while sending the image data.
draw.Src.Draw(opts.buffer, s.bufferDstRect, opts.src, opts.srcPts)
commands := opts.commands
if len(commands) == 0 {
commands = []byte{writeRAMBW, writeRAMRed}
}
// Keep the two buffers in sync.
for _, cmd := range commands {
opts.sendImage(ctrl, cmd, &s)
}
turnOnDisplay(ctrl, mode)
}