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