@@ -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,8 @@ 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 = 1.0
103
+ self .brightness = brightness
114
104
115
105
@staticmethod
116
106
def parse_byteorder (byteorder ):
@@ -144,6 +134,7 @@ def parse_byteorder(byteorder):
144
134
if "W" in byteorder :
145
135
w = byteorder .index ("W" )
146
136
byteorder = (r , g , b , w )
137
+ has_white = True
147
138
elif "P" in byteorder :
148
139
lum = byteorder .index ("P" )
149
140
byteorder = (r , g , b , lum )
@@ -164,24 +155,33 @@ def bpp(self):
164
155
def brightness (self ):
165
156
"""
166
157
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.
172
161
"""
173
162
return self ._brightness
174
163
175
164
@brightness .setter
176
165
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
+ )
185
185
186
186
if self .auto_write :
187
187
self .show ()
@@ -203,103 +203,128 @@ def show(self):
203
203
"""
204
204
Call the associated write function to display the pixels
205
205
"""
206
- raise NotImplementedError ( "Must be subclassed" )
206
+ return self . _transmit ( self . _post_brightness_buffer )
207
207
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 ):
216
221
r = 0
217
222
g = 0
218
223
b = 0
219
224
w = 0
220
- has_w = False
221
225
if isinstance (value , int ):
222
226
r = value >> 16
223
227
g = (value >> 8 ) & 0xFF
224
228
b = value & 0xFF
225
229
w = 0
226
230
# If all components are the same and we have a white pixel then use it
227
231
# 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 :
229
233
w = r
230
234
r = 0
231
235
g = 0
232
236
b = 0
233
237
elif self ._dotstar_mode :
234
238
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 :
237
241
r , g , b = value
238
242
else :
239
243
r , g , b , w = value
240
- has_w = True
241
244
elif len (value ) == 3 and self ._dotstar_mode :
242
245
r , g , b = value
243
246
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
+ )
271
288
272
289
def __setitem__ (self , index , val ):
273
290
if isinstance (index , slice ):
274
291
start , stop , step = index .indices (self ._pixels )
275
292
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 )
277
295
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 )
279
298
280
299
if self .auto_write :
281
300
self .show ()
282
301
283
302
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
+ )
285
309
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 ]],
289
313
]
290
314
if self ._has_white :
291
- value .append (self . _bytearray [start + self ._byteorder [2 ]])
315
+ value .append (buffer [start + self ._byteorder [3 ]])
292
316
elif self ._dotstar_mode :
293
317
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
296
319
)
297
320
return value
298
321
299
322
def __getitem__ (self , index ):
300
323
if isinstance (index , slice ):
301
324
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
+ ):
303
328
out .append (self ._getitem (in_i ))
304
329
return out
305
330
if index < 0 :
@@ -308,6 +333,9 @@ def __getitem__(self, index):
308
333
raise IndexError
309
334
return self ._getitem (index )
310
335
336
+ def _transmit (self , buffer ):
337
+ raise NotImplementedError ("Must be subclassed" )
338
+
311
339
312
340
def wheel (pos ):
313
341
"""
@@ -327,18 +355,3 @@ def wheel(pos):
327
355
return 0 , 255 - pos * 3 , pos * 3
328
356
pos -= 170
329
357
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