@@ -251,11 +251,12 @@ def process(self, input_filename, output_filename, rows,
251251 position = row / (rows - 1 ) # 0.0 to 1.0
252252 if callback :
253253 callback (position )
254+
254255 # Scale position into pixel space...
255256 if loop : # 0 to <image height
256257 position = self .bmp_specs .height * row / rows
257258 else : # 0 to last row.0
258- position *= ( self .bmp_specs .height - 1 )
259+ position *= self .bmp_specs .height - 1
259260
260261 # Separate absolute position into several values:
261262 # integer 'a' and 'b' row indices, floating 'a' and
@@ -297,36 +298,39 @@ def process(self, input_filename, output_filename, rows,
297298 # 'want' is an ndarray of the idealized (as in,
298299 # floating-point) pixel values resulting from the
299300 # interpolation, with gamma correction applied and
300- # scaled back up to the 0-255 range.
301+ # scaled back up to 8-bit range. Scaling to 254.999
302+ # (not 255) lets us avoid a subsequent clip check.
301303 want = ((((row_a_data * row_a_weight ) +
302304 (row_b_data * row_b_weight )) **
303- self .gamma ) * 255.001 )
305+ self .gamma ) * 254.999 )
304306
305307 # 'got' will be an ndarray of the values that get
306308 # issued to the LED strip, formed through several
307- # operations. First, an 'error term' is added to
308- # each pixel, representing how 'wrong' the prior
309- # output was. This is used for error diffusion
310- # dithering. 'got' is floating-point at this stage.
311- got = ulab .array (want + err )
312- # The error term may push some pixel values outside
313- # the required 0-255 range, so clip the result (aka
314- # 'saturate'). (Note to future self: requested a
315- # clip() function in ulab, should be available for
316- # use soon, would replace these two Python ops).
317- got [got < 0 ] = 0
318- got [got > 255 ] = 255
319- #ulab.compare.clip(got, 0, 255)
320- # Now quantize the floating-point 'got' to uint8
321- # type. This represents the actual final byte values
322- # that will be issued to the LED strip.
323- got = ulab .array (got , dtype = ulab .uint8 )
324- # Make note of the difference...the 'error term'...
325- # between what we ideally wanted (float) and what we
326- # actually got (dithered, clipped and quantized).
327- # This gets used on the next pass through the loop.
328- err = err + ((want - got ) * 0.5 )
329- # ('+=' syntax doesn't work on ndarrays)
309+ # operations. First, the 'want' values are quantized
310+ # to uint8's -- so these will always be slightly
311+ # dimmer (v. occasionally equal) to the 'want' vals.
312+ got = ulab .array (want , dtype = ulab .uint8 )
313+ # Note: naive 'foo = foo + bar' syntax used in this
314+ # next section is intentional. ndarrays don't seem
315+ # to always play well with '+=' syntax.
316+ # The difference between what we want and what we
317+ # got will be an ndarray of values from 0.0 to <1.0.
318+ # This is accumulated into the error ndarray to be
319+ # applied to this and subsequent rows.
320+ err = err + want - got
321+ # Accumulated error vals will all now be 0.0 to <2.0.
322+ # Quantizing err into a new uint8 ndarray, all values
323+ # will be 0 or 1.
324+ err_bits = ulab .array (err , dtype = ulab .uint8 )
325+ # Add the 1's back into 'got', increasing the
326+ # brightness of certain pixels by 1. Because the max
327+ # value in 'got' is 254 (not 255), no clipping need
328+ # be performed, everything still fits in uint8.
329+ got = got + err_bits
330+ # Subtract those applied 1's from the error array,
331+ # leaving residue in the range 0.0 to <1.0 which
332+ # will be used on subsequent rows.
333+ err = err - err_bits
330334
331335 # Reorder data from BGR to DotStar color order,
332336 # allowing for header and start-of-pixel markers
0 commit comments