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

Commit 0b40444

Browse files
authored
Merge pull request #18 from dunkmann00/_pixelbuf_cp
Update to match _pixelbuf from CP
2 parents ce58f06 + b47fb61 commit 0b40444

File tree

2 files changed

+129
-116
lines changed

2 files changed

+129
-116
lines changed

adafruit_pypixelbuf.py

+122-109
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,8 @@ 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 = 1.0
103+
self.brightness = brightness
114104

115105
@staticmethod
116106
def parse_byteorder(byteorder):
@@ -144,6 +134,7 @@ def parse_byteorder(byteorder):
144134
if "W" in byteorder:
145135
w = byteorder.index("W")
146136
byteorder = (r, g, b, w)
137+
has_white = True
147138
elif "P" in byteorder:
148139
lum = byteorder.index("P")
149140
byteorder = (r, g, b, lum)
@@ -164,24 +155,33 @@ def bpp(self):
164155
def brightness(self):
165156
"""
166157
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
158+
159+
When brightness is less than 1.0, a second buffer will be used to store the color values
160+
before they are adjusted for brightness.
172161
"""
173162
return self._brightness
174163

175164
@brightness.setter
176165
def brightness(self, value):
177-
self._brightness = min(max(value, 0.0), 1.0)
178-
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)
166+
value = min(max(value, 0.0), 1.0)
167+
change = value - self._brightness
168+
if -0.001 < change < 0.001:
169+
return
170+
171+
self._brightness = value
172+
173+
if self._pre_brightness_buffer is None:
174+
self._pre_brightness_buffer = bytearray(self._post_brightness_buffer)
175+
176+
# Adjust brightness of existing pixels
177+
offset_check = self._offset % self._pixel_step
178+
for i in range(self._offset, self._bytes + self._offset):
179+
# Don't adjust per-pixel luminance bytes in dotstar mode
180+
if self._dotstar_mode and (i % 4 != offset_check):
181+
continue
182+
self._post_brightness_buffer[i] = int(
183+
self._pre_brightness_buffer[i] * self._brightness
184+
)
185185

186186
if self.auto_write:
187187
self.show()
@@ -203,103 +203,128 @@ def show(self):
203203
"""
204204
Call the associated write function to display the pixels
205205
"""
206-
raise NotImplementedError("Must be subclassed")
206+
return self._transmit(self._post_brightness_buffer)
207207

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)
208+
def fill(self, color):
209+
"""
210+
Fills the given pixelbuf with the given color.
211+
:param pixelbuf: A pixel object.
212+
:param color: Color to set.
213+
"""
214+
r, g, b, w = self._parse_color(color)
215+
for i in range(self._pixels):
216+
self._set_item(i, r, g, b, w)
217+
if self.auto_write:
218+
self.show()
219+
220+
def _parse_color(self, value):
216221
r = 0
217222
g = 0
218223
b = 0
219224
w = 0
220-
has_w = False
221225
if isinstance(value, int):
222226
r = value >> 16
223227
g = (value >> 8) & 0xFF
224228
b = value & 0xFF
225229
w = 0
226230
# If all components are the same and we have a white pixel then use it
227231
# instead of the individual components.
228-
if self.bpp == 4 and self._has_white and r == g and g == b:
232+
if self._bpp == 4 and self._has_white and r == g and g == b:
229233
w = r
230234
r = 0
231235
g = 0
232236
b = 0
233237
elif self._dotstar_mode:
234238
w = 1.0
235-
elif len(value) == self.bpp:
236-
if self.bpp == 3:
239+
elif len(value) == self._bpp:
240+
if self._bpp == 3:
237241
r, g, b = value
238242
else:
239243
r, g, b, w = value
240-
has_w = True
241244
elif len(value) == 3 and self._dotstar_mode:
242245
r, g, b = value
243246

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

272289
def __setitem__(self, index, val):
273290
if isinstance(index, slice):
274291
start, stop, step = index.indices(self._pixels)
275292
for val_i, in_i in enumerate(range(start, stop, step)):
276-
self._set_item(in_i, val[val_i])
293+
r, g, b, w = self._parse_color(val[val_i])
294+
self._set_item(in_i, r, g, b, w)
277295
else:
278-
self._set_item(index, val)
296+
r, g, b, w = self._parse_color(val)
297+
self._set_item(index, r, g, b, w)
279298

280299
if self.auto_write:
281300
self.show()
282301

283302
def _getitem(self, index):
284-
start = self._offset + (index * self.bpp)
303+
start = self._offset + (index * self._bpp)
304+
buffer = (
305+
self._pre_brightness_buffer
306+
if self._pre_brightness_buffer is not None
307+
else self._post_brightness_buffer
308+
)
285309
value = [
286-
self._bytearray[start + self._byteorder[0]],
287-
self._bytearray[start + self._byteorder[1]],
288-
self._bytearray[start + self._byteorder[2]],
310+
buffer[start + self._byteorder[0]],
311+
buffer[start + self._byteorder[1]],
312+
buffer[start + self._byteorder[2]],
289313
]
290314
if self._has_white:
291-
value.append(self._bytearray[start + self._byteorder[2]])
315+
value.append(buffer[start + self._byteorder[3]])
292316
elif self._dotstar_mode:
293317
value.append(
294-
(self._bytearray[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS)
295-
/ 31.0
318+
(buffer[start + self._byteorder[3]] & DOTSTAR_LED_BRIGHTNESS) / 31.0
296319
)
297320
return value
298321

299322
def __getitem__(self, index):
300323
if isinstance(index, slice):
301324
out = []
302-
for in_i in range(*index.indices(len(self._bytearray) // self.bpp)):
325+
for in_i in range(
326+
*index.indices(len(self._post_brightness_buffer) // self._bpp)
327+
):
303328
out.append(self._getitem(in_i))
304329
return out
305330
if index < 0:
@@ -308,6 +333,9 @@ def __getitem__(self, index):
308333
raise IndexError
309334
return self._getitem(index)
310335

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

312340
def wheel(pos):
313341
"""
@@ -327,18 +355,3 @@ def wheel(pos):
327355
return 0, 255 - pos * 3, pos * 3
328356
pos -= 170
329357
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

+7-7
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)