Skip to content

Commit 8ead623

Browse files
Fix up interpolation and dithering
1 parent 321c4f2 commit 8ead623

2 files changed

Lines changed: 33 additions & 29 deletions

File tree

CLUE_Light_Painter/bmp2led.py

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

CLUE_Light_Painter/code.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,9 @@ def load_image(self):
195195
group.append(self.rect)
196196
board.DISPLAY.show(group)
197197

198-
# Playback time is about 1/4 to 5 seconds, non linearly spaced
199-
duration = 0.25 + 4.75 * ((1.0 - self.speed) ** 2.5)
200-
rows = duration * self.rows_per_second
198+
# Playback time is about 1/2 to 6 seconds, non linearly spaced
199+
duration = 0.5 + 5.5 * ((1.0 - self.speed) ** 2)
200+
rows = int(duration * self.rows_per_second + 0.5)
201201
try:
202202
self.num_rows = self.bmp2led.process(self.path + '/' +
203203
self.images[self.image_num],

0 commit comments

Comments
 (0)