diff --git a/examples/badger2040/image_converter/convert.py b/examples/badger2040/image_converter/convert.py index 6e7931899..d8613cce5 100755 --- a/examples/badger2040/image_converter/convert.py +++ b/examples/badger2040/image_converter/convert.py @@ -1,15 +1,32 @@ #!/usr/bin/env python3 +""" +Converts images into a format suitable for display on Badger 2040. -# converts images into a format suitable for display on badger2040. this -# includes scaling the image fit the longest edge, cropping down to 296x128 -# and reducing to black and white with dither. the data is then output as an -# array that can be embedded directly into your c++ code +Optionally resizes images to 296x128 to fit the display. + +Crunches images down to dithered, 1bit colour depth. + +Outputs either in raw binary format or as a .py file for embedding into MicroPython. + +Output to py functionality is borrwed from data_to_py.py, Copyright (c) 2016 Peter Hinch +""" import io import argparse from PIL import Image, ImageEnhance from pathlib import Path -import data_to_py + + +PY_HEADER = """# Code generated by convert.py. +""" + +PY_FOOTER = """_mvdata = memoryview(_data) + +def data(): + return _mvdata + +""" + parser = argparse.ArgumentParser(description='Converts images into the format used by Badger2040.') parser.add_argument('file', nargs="+", help='input files to convert') @@ -21,18 +38,67 @@ options = parser.parse_args() +class ByteWriter(object): + bytes_per_line = 16 + + def __init__(self, stream, varname): + self.stream = stream + self.stream.write('{} =\\\n'.format(varname)) + self.bytecount = 0 # For line breaks + + def _eol(self): + self.stream.write("'\\\n") + + def _eot(self): + self.stream.write("'\n") + + def _bol(self): + self.stream.write("b'") + + # Output a single byte + def obyte(self, data): + if not self.bytecount: + self._bol() + self.stream.write('\\x{:02x}'.format(data)) + self.bytecount += 1 + self.bytecount %= self.bytes_per_line + if not self.bytecount: + self._eol() + + # Output from a sequence + def odata(self, bytelist): + for byt in bytelist: + self.obyte(byt) + + # ensure a correct final line + def eot(self): # User force EOL if one hasn't occurred + if self.bytecount: + self._eot() + self.stream.write('\n') + + def convert_image(img): if options.resize: - img = img.resize((296, 128)) # resize + img = img.resize((296, 128)) # resize try: - enhancer = ImageEnhance.Contrast(img) - img = enhancer.enhance(2.0) + enhancer = ImageEnhance.Contrast(img) + img = enhancer.enhance(2.0) except ValueError: - pass - img = img.convert("1") # convert to black and white + pass + img = img.convert("1") # convert to black and white return img +def write_stream(header, footer, ip_stream, op_stream): + op_stream.write(header) + op_stream.write('\n') + data = ip_stream.read() + bw_data = ByteWriter(op_stream, '_data') + bw_data.odata(data) + bw_data.eot() + op_stream.write(footer) + + # create map of images based on input filenames for input_filename in options.file: with Image.open(input_filename) as img: @@ -59,7 +125,7 @@ def convert_image(img): output_filename = Path(input_filename).with_suffix(".py") print(f"Saving to {output_filename}, {w}x{h}") with open(output_filename, "w") as out: - data_to_py.write_stream(io.BytesIO(bytes(output_data)), out) + write_stream(PY_HEADER, PY_FOOTER, io.BytesIO(bytes(output_data)), out) else: image_code = '''\ static const uint8_t {image_name}[{count}] = {{ @@ -68,5 +134,3 @@ def convert_image(img): '''.format(image_name=image_name, count=len(output_data), byte_data=", ".join(str(b) for b in output_data)) print(image_code) - -