@@ -143,23 +143,24 @@ def scandir(self, path):
143143 return valid_list
144144
145145
146- def read_row (self , row , num_bytes ):
146+ def read_row (self , row , dest ):
147147 """
148148 Read one row of pixels from BMP file, clipped to minimum of BMP
149149 image width or LED strip length.
150150 Arguments:
151- row (int): index of row to read (0 to (image height - 1))
152- Returns: ulab ndarray (uint8 type) containing pixel data in
153- BMP-native order (B,G,R per pixel), no need to reorder to DotStar
154- order until later.
151+ row (int) : Index of row to read (0 to (img height-1)).
152+ dest (uint8 ndarray) : Destination buffer. After reading, buffer
153+ contains pixel data in BMP-native order
154+ (B,G,R per pixel), no need to reorder to
155+ DotStar order until later.
155156 """
156157 # 'flip' logic is intentionally backwards from typical BMP loader,
157158 # this makes BMP image prep an easy 90 degree CCW rotation.
158159 if not self .bmp_specs .flip :
159160 row = self .bmp_specs .height - 1 - row
160161 self .bmp_file .seek (self .bmp_specs .image_offset +
161162 row * self .bmp_specs .row_size )
162- return ulab . array ( self .bmp_file .read ( num_bytes ), dtype = ulab . uint8 )
163+ self .bmp_file .readinto ( dest )
163164
164165
165166 # pylint: disable=too-many-arguments, too-many-locals
@@ -209,14 +210,11 @@ def process(self, input_filename, output_filename, rows,
209210 # It's formed just like valid strip data (with header, per-pixel
210211 # start markers and footer), with colors all '0' to start...these
211212 # will be filled later.
212- dotstar_buffer = bytearray ([0 ] * 4 +
213- [255 , 0 , 0 , 0 ] * self .num_pixels +
214- [255 ] * ((self .num_pixels + 15 ) // 16 ))
213+ dotstar_buffer = ulab .array ([0 ] * 4 +
214+ [255 , 0 , 0 , 0 ] * self .num_pixels +
215+ [255 ] * ((self .num_pixels + 15 ) // 16 ),
216+ dtype = ulab .uint8 )
215217 dotstar_row_size = len (dotstar_buffer )
216- # Operation performed later requires a list, not a bytearray.
217- # Make a copy, keeping the same values.
218- # dotstar_list = list(dotstar_buffer)
219- #reorder = [0] * dotstar_row_size
220218
221219 # Output rows are held in RAM and periodically written,
222220 # marginally faster than writing each row separately.
@@ -251,46 +249,14 @@ def process(self, input_filename, output_filename, rows,
251249 # Constrain bytes-to-read to pixel strip length
252250 clipped_width = min (self .bmp_specs .width , self .num_pixels )
253251 row_bytes = 3 * clipped_width
254- # Compute reorder list here (needs row bytes to work)
255252
256253 # Each output row is interpolated from two BMP rows,
257254 # we'll call them 'a' and 'b' here.
258- row_a_data , row_b_data = None , None
255+ row_a_data = ulab .zeros (row_bytes , dtype = ulab .uint8 )
256+ row_b_data = ulab .zeros (row_bytes , dtype = ulab .uint8 )
259257 prev_row_a_index , prev_row_b_index = None , None
260258
261259 with open (output_filename , 'wb' ) as led_file :
262- # Determine remapping indices from BMP's always-BGR
263- # pixel byte order to DotStar's variable order
264- # (contained in self.red_index, green_index, blue_index).
265- # I'm sure there's better ways but have a headache.
266- # This is ONLY needed if using the first of two
267- # benchmarked methods later (or something similar to it).
268- # There's really only six possible orders, I could make a list
269- # if self.blue_index is 0: # BXX DotStar
270- # offset_0 = 0 # DotStar byte 0 is BMP byte 0 (B)
271- # if self.green_index is 1: # BGR
272- # offset_1 = 1 # DotStar byte 1 is BMP byte 1 (G)
273- # offset_2 = 2 # DotStar byte 2 is BMP byte 2 (R)
274- # else: # BRG
275- # offset_1 = 2 # DotStar byte 1 is BMP byte 2 (R)
276- # offset_2 = 1 # DotStar byte 2 is BMP byte 1 (G)
277- # elif self.green_index is 0: # GXX DotStar
278- # offset_0 = 1 # DotStar byte 0 is BMP byte 1 (G)
279- # if self.blue_index is 1: # GBR
280- # offset_1 = 0 # DotStar byte 1 is BMP byte 0 (B)
281- # offset_2 = 2 # DotStar byte 2 is BMP byte 2 (R)
282- # else: # GRB
283- # offset_1 = 2 # DotStar byte 1 is BMP byte 2 (R)
284- # offset_2 = 0 # DotStar byte 2 is BMP byte 0 (B)
285- # else: # RXX DotStar
286- # offset_0 = 2 # DotStar byte 0 is BMP byte 2 (R)
287- # if self.green_index is 1: # RGB
288- # offset_1 = 1 # DotStar byte 1 is BMP byte 1 (G)
289- # offset_2 = 0 # DotStar byte 2 is BMP byte 0 (R)
290- # else: # RBG
291- # offset_1 = 0 # DotStar byte 1 is BMP byte 0 (R)
292- # offset_2 = 1 # DotStar byte 2 is BMP byte 1 (G)
293-
294260 # To avoid continually appending to output file (a slow
295261 # operation), seek to where the end of the file would
296262 # be, write a nonsense byte there, then seek back to
@@ -324,15 +290,15 @@ def process(self, input_filename, output_filename, rows,
324290 # (else do another interp/dither with existing data)
325291 if row_a_index != prev_row_a_index :
326292 # If we've advanced exactly one row, reassign
327- # old 'b' data to 'a' row, else read new 'a'.
293+ # old 'b' data to 'a' row (swap, so buffers
294+ # remain distinct), else read new 'a'.
328295 if row_a_index == prev_row_b_index :
329- row_a_data = row_b_data
296+ row_a_data , row_b_data = row_b_data , row_a_data
330297 else :
331- row_a_data = self .read_row (row_a_index ,
332- row_bytes )
298+ self .read_row (row_a_index , row_a_data )
333299 # Read new 'b' data on any row change
334- row_b_data = self .read_row (row_b_index ,
335- row_bytes )
300+ self .read_row (row_b_index , row_b_data )
301+
336302 prev_row_a_index = row_a_index
337303 prev_row_b_index = row_b_index
338304 time1 += (monotonic () - row_start_time )
@@ -390,55 +356,15 @@ def process(self, input_filename, output_filename, rows,
390356 # Reorder data from BGR to DotStar color order,
391357 # allowing for header and start-of-pixel markers
392358 # in the DotStar data.
393-
394- # Benchmarking two approaches here...first uses a
395- # zipped list working from the ndarray (because
396- # CircuitPython bytearrays don't allow step-by-3),
397- # converting to a bytearray before write.
398- # This needs the 3 offset_* variables from earlier.
399-
400- # for dot_idx, color in enumerate(
401- # list(zip(got[offset_0::3],
402- # got[offset_1::3],
403- # got[offset_2::3]))):
404- # dot_pos = 5 + dot_idx * 4
405- # dotstar_list[dot_pos:dot_pos + 3] = color
406- # output_buffer[output_position:output_position +
407- # dotstar_row_size] = bytearray(
408- # dotstar_list)
409-
410- # Other approach, 'got' is converted from uint8
411- # ndarray to bytearray (seems a bit faster) and then
412- # a brute-force walkthrough loop...
413- bgr = bytearray (got )
414- for column in range (clipped_width ):
415- bmp_pos = column * 3
416- dotstar_pos = 5 + column * 4
417- dotstar_buffer [dotstar_pos +
418- self .blue_index ] = bgr [bmp_pos ]
419- dotstar_buffer [dotstar_pos +
420- self .green_index ] = bgr [bmp_pos + 1 ]
421- dotstar_buffer [dotstar_pos +
422- self .red_index ] = bgr [bmp_pos + 2 ]
359+ dotstar_buffer [5 + self .blue_index :
360+ 5 + 4 * clipped_width :4 ] = got [0 ::3 ]
361+ dotstar_buffer [5 + self .green_index :
362+ 5 + 4 * clipped_width :4 ] = got [1 ::3 ]
363+ dotstar_buffer [5 + self .red_index :
364+ 5 + 4 * clipped_width :4 ] = got [2 ::3 ]
423365 output_buffer [output_position :output_position +
424- dotstar_row_size ] = dotstar_buffer
425- # Performance of the two is pretty similar.
426- # Walkthrough loop seems a twee faster but then
427- # has a negative effect on ulab performance, maybe
428- # memory-management related?
429-
430- # And a third, using a reordering table...
431- # This doesn't actually work yet because the
432- # reorder table hasn't been computed.
433- # Two extra items (0 and 255) are appended for
434- # use by headers/footers/etc. Can't directly append
435- # to ndarray, so we bytearray-ify it first.
436- #got = bytearray(got) + bytearray([0, 255])
437- #output_buffer[output_position:output_position +
438- # dotstar_row_size] = bytearray(
439- # got[i] for i in reorder)
440- # Initial testing (with a nonsense reorder table)
441- # doesn't suggest it's any faster.
366+ dotstar_row_size ] = memoryview (
367+ dotstar_buffer )
442368
443369 time3 += (monotonic () - row_start_time )
444370
0 commit comments