Skip to content
This repository was archived by the owner on Apr 20, 2022. It is now read-only.

Commit 040d151

Browse files
committed
Update to match _pixelbuf from CP
1 parent ce58f06 commit 040d151

File tree

2 files changed

+129
-115
lines changed

2 files changed

+129
-115
lines changed

adafruit_pypixelbuf.py

Lines changed: 122 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -42,51 +42,49 @@ class PixelBuf: # pylint: disable=too-many-instance-attributes
4242
This is the pure python implementation of CircuitPython's _pixelbuf.
4343
4444
:param ~int n: Number of pixels
45-
:param ~bytearray buf: Bytearray to store pixel data in
4645
:param ~str byteorder: Byte order string constant (also sets bpp)
4746
:param ~float brightness: Brightness (0 to 1.0, default 1.0)
48-
:param ~bytearray rawbuf: Bytearray to store raw pixel colors in
49-
:param ~int offset: Offset from start of buffer (default 0)
5047
:param ~bool auto_write: Whether to automatically write pixels (Default False)
48+
:param bytes header: Sequence of bytes to always send before pixel values.
49+
:param bytes trailer: Sequence of bytes to always send after pixel values.
5150
"""
5251

5352
def __init__( # pylint: disable=too-many-locals,too-many-arguments
5453
self,
5554
n,
56-
buf,
5755
byteorder="BGR",
5856
brightness=1.0,
59-
rawbuf=None,
60-
offset=0,
6157
auto_write=False,
58+
header=None,
59+
trailer=None,
6260
):
6361

6462
bpp, byteorder_tuple, has_white, dotstar_mode = self.parse_byteorder(byteorder)
65-
if not isinstance(buf, bytearray):
66-
raise TypeError("buf must be a bytearray")
67-
if rawbuf is not None and not isinstance(rawbuf, bytearray):
68-
raise TypeError("rawbuf must be a bytearray")
6963

7064
effective_bpp = 4 if dotstar_mode else bpp
7165
_bytes = effective_bpp * n
72-
two_buffers = rawbuf is not None and buf is not None
73-
if two_buffers and len(buf) != len(rawbuf):
74-
raise ValueError("rawbuf is not the same size as buf")
66+
buf = bytearray(_bytes)
67+
offset = 0
7568

76-
if (len(buf) + offset) < _bytes:
77-
raise TypeError("buf is too small")
78-
if two_buffers and (len(rawbuf) + offset) < _bytes:
79-
raise TypeError("buf is too small. need %d bytes" % (_bytes,))
69+
if header is not None:
70+
if not isinstance(header, bytearray):
71+
raise TypeError("header must be a bytearray")
72+
buf = header + buf
73+
offset = len(header)
74+
75+
if trailer is not None:
76+
if not isinstance(trailer, bytearray):
77+
raise TypeError("trailer must be a bytearray")
78+
buf += trailer
8079

8180
self._pixels = n
8281
self._bytes = _bytes
8382
self._byteorder = byteorder_tuple
8483
self._byteorder_string = byteorder
8584
self._has_white = has_white
8685
self._bpp = bpp
87-
self._bytearray = buf
88-
self._two_buffers = two_buffers
89-
self._rawbytearray = rawbuf
86+
self._pre_brightness_buffer = None
87+
self._post_brightness_buffer = buf
9088
self._offset = offset
9189
self._dotstar_mode = dotstar_mode
9290
self._pixel_step = effective_bpp
@@ -101,16 +99,7 @@ def __init__( # pylint: disable=too-many-locals,too-many-arguments
10199
0,
102100
)
103101

104-
self._brightness = min(1.0, max(0, brightness))
105-
106-
if dotstar_mode:
107-
for i in range(0, self._pixels * 4, 4):
108-
self._bytearray[i + self._offset] = DOTSTAR_LED_START_FULL_BRIGHT
109-
110-
@property
111-
def buf(self):
112-
"""The brightness adjusted pixel buffer data."""
113-
return bytearray([int(i * self.brightness) for i in self._bytearray])
102+
self._brightness = brightness
114103

115104
@staticmethod
116105
def parse_byteorder(byteorder):
@@ -144,6 +133,7 @@ def parse_byteorder(byteorder):
144133
if "W" in byteorder:
145134
w = byteorder.index("W")
146135
byteorder = (r, g, b, w)
136+
has_white = True
147137
elif "P" in byteorder:
148138
lum = byteorder.index("P")
149139
byteorder = (r, g, b, lum)
@@ -164,24 +154,35 @@ def bpp(self):
164154
def brightness(self):
165155
"""
166156
Float value between 0 and 1. Output brightness.
167-
If the PixelBuf was allocated with two both a buf and a rawbuf,
168-
setting this value causes a recomputation of the values in buf.
169-
If only a buf was provided, then the brightness only applies to
170-
future pixel changes.
171-
In DotStar mode
157+
158+
When brightness is less than 1.0, a second buffer will be used to store the color values
159+
before they are adjusted for brightness.
172160
"""
173161
return self._brightness
174162

175163
@brightness.setter
176164
def brightness(self, value):
177-
self._brightness = min(max(value, 0.0), 1.0)
165+
value = min(max(value, 0.0), 1.0)
166+
change = value - self._brightness
167+
if -0.001 < change < 0.001:
168+
return
169+
170+
self._brightness = value
171+
172+
if self._pre_brightness_buffer is None:
173+
self._pre_brightness_buffer = bytearray(
174+
(i for i in self._post_brightness_buffer)
175+
)
178176

179-
# Adjust brightness of existing pixels when two buffers are available
180-
if self._two_buffers:
181-
offset_check = self._offset % self._pixel_step
182-
for i in range(self._offset, self._bytes + self._offset):
183-
if self._dotstar_mode and (i % 4 != offset_check):
184-
self._bytearray[i] = int(self._rawbytearray[i] * self._brightness)
177+
# Adjust brightness of existing pixels
178+
offset_check = self._offset % self._pixel_step
179+
for i in range(self._offset, self._bytes + self._offset):
180+
# Don't adjust per-pixel luminance bytes in dotstar mode
181+
if self._dotstar_mode and (i % 4 != offset_check):
182+
continue
183+
self._post_brightness_buffer[i] = int(
184+
self._pre_brightness_buffer[i] * self._brightness
185+
)
185186

186187
if self.auto_write:
187188
self.show()
@@ -203,103 +204,128 @@ def show(self):
203204
"""
204205
Call the associated write function to display the pixels
205206
"""
206-
raise NotImplementedError("Must be subclassed")
207+
return self._transmit(self._post_brightness_buffer)
207208

208-
def _set_item(
209-
self, index, value
210-
): # pylint: disable=too-many-locals,too-many-branches
211-
if index < 0:
212-
index += len(self)
213-
if index >= self._pixels or index < 0:
214-
raise IndexError
215-
offset = self._offset + (index * self.bpp)
209+
def fill(self, color):
210+
"""
211+
Fills the given pixelbuf with the given color.
212+
:param pixelbuf: A pixel object.
213+
:param color: Color to set.
214+
"""
215+
r, g, b, w = self._parse_color(color)
216+
for i in range(self._pixels):
217+
self._set_item(i, r, g, b, w)
218+
if self.auto_write:
219+
self.show()
220+
221+
def _parse_color(self, value):
216222
r = 0
217223
g = 0
218224
b = 0
219225
w = 0
220-
has_w = False
221226
if isinstance(value, int):
222227
r = value >> 16
223228
g = (value >> 8) & 0xFF
224229
b = value & 0xFF
225230
w = 0
226231
# If all components are the same and we have a white pixel then use it
227232
# instead of the individual components.
228-
if self.bpp == 4 and self._has_white and r == g and g == b:
233+
if self._bpp == 4 and self._has_white and r == g and g == b:
229234
w = r
230235
r = 0
231236
g = 0
232237
b = 0
233238
elif self._dotstar_mode:
234239
w = 1.0
235-
elif len(value) == self.bpp:
236-
if self.bpp == 3:
240+
elif len(value) == self._bpp:
241+
if self._bpp == 3:
237242
r, g, b = value
238243
else:
239244
r, g, b, w = value
240-
has_w = True
241245
elif len(value) == 3 and self._dotstar_mode:
242246
r, g, b = value
243247

244-
if self._two_buffers:
245-
self._rawbytearray[offset + self._byteorder[0]] = r
246-
self._rawbytearray[offset + self._byteorder[1]] = g
247-
self._rawbytearray[offset + self._byteorder[2]] = b
248-
249-
self._bytearray[offset + self._byteorder[0]] = int(r * self._brightness)
250-
self._bytearray[offset + self._byteorder[1]] = int(g * self._brightness)
251-
self._bytearray[offset + self._byteorder[2]] = int(b * self._brightness)
252-
253-
if has_w:
254-
if self._dotstar_mode:
255-
# LED startframe is three "1" bits, followed by 5 brightness bits
256-
# then 8 bits for each of R, G, and B. The order of those 3 are configurable and
257-
# vary based on hardware
258-
# same as math.ceil(brightness * 31) & 0b00011111
259-
# Idea from https://www.codeproject.com/Tips/700780/Fast-floor-ceiling-functions
260-
self._bytearray[offset + self._byteorder[3]] = (
261-
32 - int(32 - w * 31) & 0b00011111
262-
) | DOTSTAR_LED_START
263-
else:
264-
self._bytearray[offset + self._byteorder[3]] = int(w * self._brightness)
265-
if self._two_buffers:
266-
self._rawbytearray[offset + self._byteorder[3]] = self._bytearray[
267-
offset + self._byteorder[3]
268-
]
269-
elif self._dotstar_mode:
270-
self._bytearray[offset + self._byteorder[3]] = DOTSTAR_LED_START_FULL_BRIGHT
248+
if self._bpp == 4 and self._dotstar_mode:
249+
# LED startframe is three "1" bits, followed by 5 brightness bits
250+
# then 8 bits for each of R, G, and B. The order of those 3 are configurable and
251+
# vary based on hardware
252+
# same as math.ceil(brightness * 31) & 0b00011111
253+
# Idea from https://www.codeproject.com/Tips/700780/Fast-floor-ceiling-functions
254+
w = (32 - int(32 - w * 31) & 0b00011111) | DOTSTAR_LED_START
255+
256+
return (r, g, b, w)
257+
258+
def _set_item(
259+
self, index, r, g, b, w
260+
): # pylint: disable=too-many-locals,too-many-branches,too-many-arguments
261+
if index < 0:
262+
index += len(self)
263+
if index >= self._pixels or index < 0:
264+
raise IndexError
265+
offset = self._offset + (index * self._bpp)
266+
267+
if self._pre_brightness_buffer is not None:
268+
if self._bpp == 4:
269+
self._pre_brightness_buffer[offset + self._byteorder[3]] = w
270+
self._pre_brightness_buffer[offset + self._byteorder[0]] = r
271+
self._pre_brightness_buffer[offset + self._byteorder[1]] = g
272+
self._pre_brightness_buffer[offset + self._byteorder[2]] = b
273+
274+
if self._bpp == 4:
275+
# Only apply brightness if w is actually white (aka not DotStar.)
276+
if not self._dotstar_mode:
277+
w = int(w * self._brightness)
278+
self._post_brightness_buffer[offset + self._byteorder[3]] = w
279+
280+
self._post_brightness_buffer[offset + self._byteorder[0]] = int(
281+
r * self._brightness
282+
)
283+
self._post_brightness_buffer[offset + self._byteorder[1]] = int(
284+
g * self._brightness
285+
)
286+
self._post_brightness_buffer[offset + self._byteorder[2]] = int(
287+
b * self._brightness
288+
)
271289

272290
def __setitem__(self, index, val):
273291
if isinstance(index, slice):
274292
start, stop, step = index.indices(self._pixels)
275293
for val_i, in_i in enumerate(range(start, stop, step)):
276-
self._set_item(in_i, val[val_i])
294+
r, g, b, w = self._parse_color(val[val_i])
295+
self._set_item(in_i, r, g, b, w)
277296
else:
278-
self._set_item(index, val)
297+
r, g, b, w = self._parse_color(val)
298+
self._set_item(index, r, g, b, w)
279299

280300
if self.auto_write:
281301
self.show()
282302

283303
def _getitem(self, index):
284-
start = self._offset + (index * self.bpp)
304+
start = self._offset + (index * self._bpp)
305+
buffer = (
306+
self._pre_brightness_buffer
307+
if self._pre_brightness_buffer is not None
308+
else self._post_brightness_buffer
309+
)
285310
value = [
286-
self._bytearray[start + self._byteorder[0]],
287-
self._bytearray[start + self._byteorder[1]],
288-
self._bytearray[start + self._byteorder[2]],
311+
buffer[start + self._byteorder[0]],
312+
buffer[start + self._byteorder[1]],
313+
buffer[start + self._byteorder[2]],
289314
]
290315
if self._has_white:
291-
value.append(self._bytearray[start + self._byteorder[2]])
316+
value.append(buffer[start + self._byteorder[3]])
292317
elif self._dotstar_mode:
293318
value.append(
294-
(self._bytearray[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS)
295-
/ 31.0
319+
(buffer[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS) / 31.0
296320
)
297321
return value
298322

299323
def __getitem__(self, index):
300324
if isinstance(index, slice):
301325
out = []
302-
for in_i in range(*index.indices(len(self._bytearray) // self.bpp)):
326+
for in_i in range(
327+
*index.indices(len(self._post_brightness_buffer) // self._bpp)
328+
):
303329
out.append(self._getitem(in_i))
304330
return out
305331
if index < 0:
@@ -308,6 +334,9 @@ def __getitem__(self, index):
308334
raise IndexError
309335
return self._getitem(index)
310336

337+
def _transmit(self, buffer):
338+
raise NotImplementedError("Must be subclassed")
339+
311340

312341
def wheel(pos):
313342
"""
@@ -327,18 +356,3 @@ def wheel(pos):
327356
return 0, 255 - pos * 3, pos * 3
328357
pos -= 170
329358
return pos * 3, 0, 255 - pos * 3
330-
331-
332-
def fill(pixelbuf, color):
333-
"""
334-
Helper to fill the strip a specific color.
335-
:param pixelbuf: A pixel object.
336-
:param color: Color to set.
337-
"""
338-
auto_write = pixelbuf.auto_write
339-
pixelbuf.auto_write = False
340-
for i, _ in enumerate(pixelbuf):
341-
pixelbuf[i] = color
342-
if auto_write:
343-
pixelbuf.show()
344-
pixelbuf.auto_write = auto_write

examples/pypixelbuf_simpletest.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
class TestBuf(adafruit_pypixelbuf.PixelBuf):
55
called = False
66

7-
def show(self):
7+
def _transmit(self, buffer):
88
self.called = True
99

1010

11-
buffer = TestBuf(20, bytearray(20 * 3), "RGB", 1.0, auto_write=True)
12-
buffer[0] = (1, 2, 3)
11+
buf = TestBuf(20, "RGB", 1.0, auto_write=True)
12+
buf[0] = (1, 2, 3)
1313

14-
print(buffer[0])
15-
print(buffer[0:2])
16-
print(buffer[0:2:2])
17-
print(buffer.called)
14+
print(buf[0])
15+
print(buf[0:2])
16+
print(buf[0:2:2])
17+
print(buf.called)

0 commit comments

Comments
 (0)