Skip to content

Commit e965b84

Browse files
WIP
1 parent acdd28e commit e965b84

2 files changed

Lines changed: 162 additions & 351 deletions

File tree

CLUE_Light_Painter/bmp2led.py

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44

55
import os
6+
import math
67
import ulab
78

89
class BMPError(Exception):
@@ -59,7 +60,8 @@ def __init__(self, num_pixels, order='brg', gamma=2.6):
5960
self.blue_index = order.find('b')
6061
self.num_pixels = num_pixels
6162
self.gamma = gamma
62-
self.bmpfile = None
63+
self.bmp_file = None
64+
self.bmp_specs = None
6365

6466

6567
def read_le(self, num_bytes):
@@ -73,7 +75,7 @@ def read_le(self, num_bytes):
7375
Converted integer product.
7476
"""
7577
result = 0
76-
for byte_index, byte in enumerate(self.bmpfile.read(num_bytes)):
78+
for byte_index, byte in enumerate(self.bmp_file.read(num_bytes)):
7779
result += byte << (byte_index * 8)
7880
return result
7981

@@ -85,13 +87,13 @@ def read_header(self):
8587
Returns:
8688
BMPSpecs object containing size, offset, etc.
8789
"""
88-
if self.bmpfile.read(2) != b'BM': # Check signature
90+
if self.bmp_file.read(2) != b'BM': # Check signature
8991
raise BMPError("Not BMP file")
9092

91-
self.bmpfile.read(8) # Read & ignore file size & creator bytes
93+
self.bmp_file.read(8) # Read & ignore file size & creator bytes
9294

9395
image_offset = self.read_le(4) # Start of image data
94-
self.bmpfile.read(4) # Read & ignore header size
96+
self.bmp_file.read(4) # Read & ignore header size
9597
width = self.read_le(4)
9698
height = self.read_le(4)
9799
# BMPs are traditionally stored bottom-to-top.
@@ -127,7 +129,7 @@ def scandir(self, path):
127129
valid_list = []
128130
for entry in full_list:
129131
try:
130-
with open(entry, "rb") as self.file:
132+
with open(path + '/' + entry, 'rb') as self.bmp_file:
131133
self.read_header()
132134
valid_list.append(entry)
133135
except (OSError, BMPError):
@@ -137,20 +139,7 @@ def scandir(self, path):
137139
return valid_list
138140

139141

140-
# old file will be overwritten.
141-
# gamma is stored in self,
142-
# brightness and loop will be passed in.
143-
# Oh...also need to pass in number of rows to stretch.
144-
# Number of LEDs is known from constructor. These are the items:
145-
# self.red_index = order.find('r')
146-
# self.green_index = order.find('g')
147-
# self.blue_index = order.find('b')
148-
# self.num_pixels = num_pixels
149-
# self.gamma = gamma
150-
# self.file = None
151-
# Delete existing tempfile before checking free space.
152-
153-
def read_row(self, row):
142+
def read_row(self, row, num_bytes):
154143
"""
155144
Read one row of pixels from BMP file, clipped to minimum of BMP
156145
image width or LED strip length.
@@ -162,10 +151,11 @@ def read_row(self, row):
162151
"""
163152
# 'flip' logic is intentionally backwards from typical BMP loader,
164153
# this makes BMP image prep an easy 90 degree CCW rotation.
165-
if not bmp.flip:
166-
row = bmp.height - 1 - row
167-
self.file.seek(bmp.image_offset + row * bmp.row_size)
168-
return ulab.array(self.file.read(clipped_row_size), dtype=uint8)
154+
if not self.bmp_specs.flip:
155+
row = self.bmp_specs.height - 1 - row
156+
self.bmp_file.seek(self.bmp_specs.image_offset +
157+
row * self.bmp_specs.row_size)
158+
return ulab.array(self.bmp_file.read(num_bytes), dtype=ulab.uint8)
169159

170160

171161
def process(self, input_filename, output_filename, rows,
@@ -214,8 +204,8 @@ def process(self, input_filename, output_filename, rows,
214204
# start markers and footer), with colors all '0' to start...these
215205
# will be filled later.
216206
dotstar_buffer = bytearray([0] * 4 +
217-
[255, 0, 0, 0] * num_pixels +
218-
[255] * ((num_pixels + 15) // 16))
207+
[255, 0, 0, 0] * self.num_pixels +
208+
[255] * ((self.num_pixels + 15) // 16))
219209
dotstar_row_size = len(dotstar_buffer)
220210

221211
# Delete old temporary file, if any
@@ -234,39 +224,42 @@ def process(self, input_filename, output_filename, rows,
234224
rows = min(rows, bytes_free // dotstar_row_size)
235225

236226
try:
237-
with open(input_filename, 'rb') as file_in:
227+
with open(input_filename, 'rb') as self.bmp_file:
238228
#print("File opened")
239229

240-
bmp = self.read_header()
230+
self.bmp_specs = self.read_header()
241231

242-
#print("WxH: (%d,%d)" % (bmp.width, bmp.height))
232+
#print("WxH: (%d,%d)" % (self.bmp_specs.width,
233+
# self.bmp_specs.height))
243234
#print("Image format OK, reading data...")
244235

245-
# Constrain row width to pixel strip length
246-
clipped_width = min(bmp.width, self.num_pixels)
236+
# Constrain bytes-to-read to pixel strip length
237+
clipped_width = min(self.bmp_specs.width, self.num_pixels)
238+
row_bytes = 3 * clipped_width
247239

248240
# Each output row is interpolated from two BMP rows,
249241
# we'll call them 'a' and 'b' here.
250242
row_a_data, row_b_data = None, None
251-
prev_row_a_index, prev_row_b_index = None
243+
prev_row_a_index, prev_row_b_index = None, None
252244

253-
with open(output_filename, 'wb') as file_out:
245+
with open(output_filename, 'wb') as led_file:
246+
err = 0
254247
for row in range(rows): # For each output row...
255248
position = row / (rows - 1) # 0.0 to 1.0
256249
if callback:
257250
callback(position)
258251
# Scale position into pixel space...
259-
if self.loop: # 0 to image height
260-
position *= len(self.columns)
252+
if loop: # 0 to image height
253+
position *= rows
261254
else: # 0 to last row
262-
position *= (len(self.columns) - 1)
255+
position *= (rows - 1)
263256

264257
# Separate absolute position into several values:
265258
# integer 'a' and 'b' row indices, floating 'a' and
266259
# 'b' weights (0.0 to 1.0) for interpolation.
267-
row_b_weight, row_a_index = modf(position)
260+
row_b_weight, row_a_index = math.modf(position)
268261
row_a_index = int(row_a_index)
269-
row_b_index = (row_a_index + 1) % bmp.height
262+
row_b_index = (row_a_index + 1) % self.bmp_specs.height
270263
row_a_weight = 1.0 - row_b_weight
271264

272265
# New data ONLY needs reading if row index changed
@@ -277,9 +270,11 @@ def process(self, input_filename, output_filename, rows,
277270
if row_a_index == prev_row_b_index:
278271
row_a_data = row_b_data
279272
else:
280-
row_a_data = self.read(row_a_index)
273+
row_a_data = self.read_row(row_a_index,
274+
row_bytes)
281275
# Read new 'b' data on any row change
282-
row_b_data = self.read(row_b_index)
276+
row_b_data = self.read_row(row_b_index,
277+
row_bytes)
283278
prev_row_a_index = row_a_index
284279
prev_row_b_index = row_b_index
285280

@@ -299,9 +294,10 @@ def process(self, input_filename, output_filename, rows,
299294
# floating-point) pixel values resulting from the
300295
# interpolation, with gamma correction applied and
301296
# scaled back up to the 0-255 range.
302-
want = ((row_a_data * row_a_weight +
303-
row_b_data * row_b_weight) **
304-
self.gamma * 255.001)
297+
# ValueError: operands could not be broadcast together
298+
want = ((((row_a_data * row_a_weight) +
299+
(row_b_data * row_b_weight)) **
300+
self.gamma) * 255.001)
305301

306302
# 'got' will be an ndarray of the values that get
307303
# issued to the LED strip, formed through several
@@ -334,23 +330,28 @@ def process(self, input_filename, output_filename, rows,
334330
# allowing for header and start-of-pixel markers
335331
# in the DotStar data.
336332
for column in range(clipped_width):
337-
bmp_pos = x * 3
338-
dotstar_pos = 5 + x * 4
339-
bgr = data[bmp_pos:bmp_pos + 3]
340-
dotstar_buffer[dotstar_pos + blue_index] = bgr[0]
341-
dotstar_buffer[dotstar_pos + green_index] = bgr[1]
342-
dotstar_buffer[dotstar_pos + red_index] = bgr[2]
343-
344-
file_out.write(dotstar_buffer)
333+
bmp_pos = column * 3
334+
dotstar_pos = 5 + column * 4
335+
bgr = got[bmp_pos:bmp_pos + 3]
336+
dotstar_buffer[dotstar_pos +
337+
self.blue_index] = bgr[0]
338+
dotstar_buffer[dotstar_pos +
339+
self.green_index] = bgr[1]
340+
dotstar_buffer[dotstar_pos +
341+
self.red_index] = bgr[2]
342+
343+
led_file.write(dotstar_buffer)
345344

346345
# If not looping, add an 'all off' row of LED data
347346
# at end to ensure last row timing is consistent.
348347
if not loop:
349348
rows += 1
350-
file_out.write(bytearray([0] * 4 +
351-
[255, 0, 0, 0] * num_pixels +
352-
[255] * ((num_pixels + 15) //
353-
16)))
349+
led_file.write(bytearray([0] * 4 +
350+
[255, 0, 0, 0] *
351+
self.num_pixels +
352+
[255] *
353+
((self.num_pixels + 15) //
354+
16)))
354355

355356
#print("Loaded OK!")
356357
return rows

0 commit comments

Comments
 (0)