@@ -42,51 +42,49 @@ class PixelBuf: # pylint: disable=too-many-instance-attributes
42
42
This is the pure python implementation of CircuitPython's _pixelbuf.
43
43
44
44
:param ~int n: Number of pixels
45
- :param ~bytearray buf: Bytearray to store pixel data in
46
45
:param ~str byteorder: Byte order string constant (also sets bpp)
47
46
: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)
50
47
: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.
51
50
"""
52
51
53
52
def __init__ ( # pylint: disable=too-many-locals,too-many-arguments
54
53
self ,
55
54
n ,
56
- buf ,
57
55
byteorder = "BGR" ,
58
56
brightness = 1.0 ,
59
- rawbuf = None ,
60
- offset = 0 ,
61
57
auto_write = False ,
58
+ header = None ,
59
+ trailer = None ,
62
60
):
63
61
64
62
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" )
69
63
70
64
effective_bpp = 4 if dotstar_mode else bpp
71
65
_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
75
68
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
80
79
81
80
self ._pixels = n
82
81
self ._bytes = _bytes
83
82
self ._byteorder = byteorder_tuple
84
83
self ._byteorder_string = byteorder
85
84
self ._has_white = has_white
86
85
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
90
88
self ._offset = offset
91
89
self ._dotstar_mode = dotstar_mode
92
90
self ._pixel_step = effective_bpp
@@ -101,16 +99,7 @@ def __init__( # pylint: disable=too-many-locals,too-many-arguments
101
99
0 ,
102
100
)
103
101
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
114
103
115
104
@staticmethod
116
105
def parse_byteorder (byteorder ):
@@ -144,6 +133,7 @@ def parse_byteorder(byteorder):
144
133
if "W" in byteorder :
145
134
w = byteorder .index ("W" )
146
135
byteorder = (r , g , b , w )
136
+ has_white = True
147
137
elif "P" in byteorder :
148
138
lum = byteorder .index ("P" )
149
139
byteorder = (r , g , b , lum )
@@ -164,24 +154,35 @@ def bpp(self):
164
154
def brightness (self ):
165
155
"""
166
156
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.
172
160
"""
173
161
return self ._brightness
174
162
175
163
@brightness .setter
176
164
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
+ )
178
176
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
+ )
185
186
186
187
if self .auto_write :
187
188
self .show ()
@@ -203,103 +204,128 @@ def show(self):
203
204
"""
204
205
Call the associated write function to display the pixels
205
206
"""
206
- raise NotImplementedError ( "Must be subclassed" )
207
+ return self . _transmit ( self . _post_brightness_buffer )
207
208
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 ):
216
222
r = 0
217
223
g = 0
218
224
b = 0
219
225
w = 0
220
- has_w = False
221
226
if isinstance (value , int ):
222
227
r = value >> 16
223
228
g = (value >> 8 ) & 0xFF
224
229
b = value & 0xFF
225
230
w = 0
226
231
# If all components are the same and we have a white pixel then use it
227
232
# 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 :
229
234
w = r
230
235
r = 0
231
236
g = 0
232
237
b = 0
233
238
elif self ._dotstar_mode :
234
239
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 :
237
242
r , g , b = value
238
243
else :
239
244
r , g , b , w = value
240
- has_w = True
241
245
elif len (value ) == 3 and self ._dotstar_mode :
242
246
r , g , b = value
243
247
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
+ )
271
289
272
290
def __setitem__ (self , index , val ):
273
291
if isinstance (index , slice ):
274
292
start , stop , step = index .indices (self ._pixels )
275
293
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 )
277
296
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 )
279
299
280
300
if self .auto_write :
281
301
self .show ()
282
302
283
303
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
+ )
285
310
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 ]],
289
314
]
290
315
if self ._has_white :
291
- value .append (self . _bytearray [start + self ._byteorder [2 ]])
316
+ value .append (buffer [start + self ._byteorder [3 ]])
292
317
elif self ._dotstar_mode :
293
318
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
296
320
)
297
321
return value
298
322
299
323
def __getitem__ (self , index ):
300
324
if isinstance (index , slice ):
301
325
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
+ ):
303
329
out .append (self ._getitem (in_i ))
304
330
return out
305
331
if index < 0 :
@@ -308,6 +334,9 @@ def __getitem__(self, index):
308
334
raise IndexError
309
335
return self ._getitem (index )
310
336
337
+ def _transmit (self , buffer ):
338
+ raise NotImplementedError ("Must be subclassed" )
339
+
311
340
312
341
def wheel (pos ):
313
342
"""
@@ -327,18 +356,3 @@ def wheel(pos):
327
356
return 0 , 255 - pos * 3 , pos * 3
328
357
pos -= 170
329
358
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
0 commit comments