-
Notifications
You must be signed in to change notification settings - Fork 37
/
drawing.go
220 lines (174 loc) · 5.73 KB
/
drawing.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// 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 waveshare2in13v2
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) {
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)
}
}