@@ -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
312341def 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
0 commit comments