diff --git a/README.rst b/README.rst index 36397bf..d8650f3 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ overhead of the decoding code. Usage Example ============= -.. literalinclude:: examples/imageload_simpletest.py +.. literalinclude:: ../examples/imageload_simpletest.py Contributing ============ diff --git a/adafruit_imageload/__init__.py b/adafruit_imageload/__init__.py index 8b09882..5c36a32 100644 --- a/adafruit_imageload/__init__.py +++ b/adafruit_imageload/__init__.py @@ -41,11 +41,10 @@ def load(filename, *, bitmap=None, palette=None): palette is the desired pallete type. The constructor should take the number of colors and support assignment to indices via []. """ - with open(filename, "rb") as f: - header = f.read(3) + with open(filename, "rb") as file: + header = file.read(3) if header.startswith(b"BM"): from . import bmp - f.seek(0) - return bmp.load(f, bitmap=bitmap, palette=palette) - else: - raise RuntimeError("Unsupported image format") + file.seek(0) + return bmp.load(file, bitmap=bitmap, palette=palette) + raise RuntimeError("Unsupported image format") diff --git a/adafruit_imageload/bmp/__init__.py b/adafruit_imageload/bmp/__init__.py index 3e3f906..e83bd2b 100644 --- a/adafruit_imageload/bmp/__init__.py +++ b/adafruit_imageload/bmp/__init__.py @@ -32,25 +32,33 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(f, *, bitmap=None, palette=None): - f.seek(10) - data_start = int.from_bytes(f.read(4), 'little') +def load(file, *, bitmap=None, palette=None): + """Loads a bmp image from the open ``file``. + + Returns tuple of bitmap object and palette object. + + :param object bitmap: Type to store bitmap data. Must have API similar to `displayio.Bitmap`. + Will be skipped if None + :param object palette: Type to store the palette. Must have API similar to + `displayio.Palette`. Will be skipped if None""" + file.seek(10) + data_start = int.from_bytes(file.read(4), 'little') # f.seek(14) - # bmp_header_length = int.from_bytes(f.read(4), 'little') + # bmp_header_length = int.from_bytes(file.read(4), 'little') # print(bmp_header_length) - f.seek(18) - width = int.from_bytes(f.read(4), 'little') - height = int.from_bytes(f.read(4), 'little') - f.seek(28) - color_depth = int.from_bytes(f.read(2), 'little') - f.seek(46) - colors = int.from_bytes(f.read(4), 'little') - - compute_palette = False + file.seek(0x12) # Width of the bitmap in pixels + width = int.from_bytes(file.read(4), 'little') + height = int.from_bytes(file.read(4), 'little') + file.seek(0x1c) # Number of bits per pixel + color_depth = int.from_bytes(file.read(2), 'little') + file.seek(0x2e) # Number of colors in the color palette + colors = int.from_bytes(file.read(4), 'little') + if colors == 0 and color_depth >= 16: raise NotImplementedError("True color BMP unsupported") else: if colors == 0: colors = 2 ** color_depth from . import indexed - return indexed.load(f, width, height, data_start, colors, color_depth, bitmap=bitmap, palette=palette) + return indexed.load(file, width, height, data_start, colors, color_depth, bitmap=bitmap, + palette=palette) diff --git a/adafruit_imageload/bmp/indexed.py b/adafruit_imageload/bmp/indexed.py index 76e35cd..e4ffd97 100644 --- a/adafruit_imageload/bmp/indexed.py +++ b/adafruit_imageload/bmp/indexed.py @@ -32,16 +32,27 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -import math - -def load(f, width, height, data_start, colors, color_depth, *, bitmap=None, palette=None): +def load(file, width, height, data_start, colors, color_depth, *, bitmap=None, palette=None): + """Loads indexed bitmap data into bitmap and palette objects. + + :param file file: The open bmp file + :param int width: Image width in pixels + :param int height: Image height in pixels + :param int data_start: Byte location where the data starts (after headers) + :param int colors: Number of distinct colors in the image + :param int color_depth: Number of bits used to store a value""" + # pylint: disable=too-many-arguments,too-many-locals if palette: palette = palette(colors) - f.seek(data_start - colors * 4) - for color in range(colors): - c = f.read(4) - palette[color] = c + file.seek(data_start - colors * 4) + for value in range(colors): + c_bytes = file.read(4) + # Need to swap red & blue bytes (bytes 0 and 2) + palette[value] = bytes(b''.join([c_bytes[2:3], + c_bytes[1:2], + c_bytes[0:1], + c_bytes[3:1]])) if bitmap: minimum_color_depth = 1 @@ -49,35 +60,22 @@ def load(f, width, height, data_start, colors, color_depth, *, bitmap=None, pale minimum_color_depth *= 2 bitmap = bitmap(width, height, colors) - f.seek(data_start) + file.seek(data_start) line_size = width // (8 // color_depth) if line_size % 4 != 0: line_size += (4 - line_size % 4) - packed_pixels = None - if color_depth != minimum_color_depth and minimum_color_depth == 2: - target_line_size = width // 4 - if target_line_size % 4 != 0: - target_line_size += (4 - target_line_size % 4) - - packed_pixels = bytearray(target_line_size) - - for line in range(height-1,-1,-1): - chunk = f.read(line_size) - if packed_pixels: - original_pixels_per_byte = 8 // color_depth - packed_pixels_per_byte = 8 // minimum_color_depth - - for i in range(width // packed_pixels_per_byte): - packed_pixels[i] = 0 + chunk = bytearray(line_size) + mask = (1 << minimum_color_depth) - 1 - for i in range(width): - pi = i // packed_pixels_per_byte - ci = i // original_pixels_per_byte - packed_pixels[pi] |= ((chunk[ci] >> (8 - color_depth*(i % original_pixels_per_byte + 1))) & 0x3) << (8 - minimum_color_depth*(i % packed_pixels_per_byte + 1)) + for y in range(height - 1, -1, -1): + file.readinto(chunk) + pixels_per_byte = 8 // color_depth + offset = y * width - bitmap._load_row(line, packed_pixels) - else: - bitmap._load_row(line, chunk) + for x in range(width): + i = x // pixels_per_byte + pixel = (chunk[i] >> (8 - color_depth*(x % pixels_per_byte + 1))) & mask + bitmap[offset + x] = pixel return bitmap, palette diff --git a/docs/developing.rst b/docs/developing.rst index 9588b07..7b1d345 100644 --- a/docs/developing.rst +++ b/docs/developing.rst @@ -102,16 +102,16 @@ The corresponding Bitmap to the example above appears like this after loading:: 4 4 4 5 5 5 12 12 12 5 5 5 7 7 7 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -This grid represents the example image (`15 pixels wide` and `17 pixels tall`). -The coordinates are arranged in a zero indexed grid, starting in the top left at `[0,0]`, -and continuing down and to the right to a final coordinate of `[14,16]`. +This grid represents the example image (``15 pixels wide`` and ``17 pixels tall``). +The coordinates are arranged in a zero indexed grid, starting in the top left at ``[0,0]``, +and continuing down and to the right to a final coordinate of ``[14,16]``. The value at each position is an integer, representing an entry in the palette object. -For example, the Bitmap coordinate `[0,0]` has the value (integer) `5`. +For example, the Bitmap coordinate ``[0,0]`` has the value (integer) ``5``. -This corresponds to the the Palette object's, `[5]` which is `b'\x00\x00\xff\x00'`. This is a byte string that represents a color. +This corresponds to the the Palette object's, ``[5]`` which is ``b'\x00\x00\xff\x00'``. This is a byte string that represents a color. diff --git a/docs/index.rst b/docs/index.rst index 166594f..6428fa2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,15 +23,15 @@ Table of Contents .. toctree:: :caption: Tutorials -.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave - the toctree above for use later. +.. toctree:: + :caption: Development + :maxdepth: 3 + + developing .. toctree:: :caption: Related Products -.. todo:: Add any product links here. If there are none, then simply delete this todo and leave - the toctree above for use later. - .. toctree:: :caption: Other Links diff --git a/examples/imageload_colorwheel.py b/examples/imageload_colorwheel.py new file mode 100644 index 0000000..a60f5b9 --- /dev/null +++ b/examples/imageload_colorwheel.py @@ -0,0 +1,17 @@ +import board +import displayio +import adafruit_imageload + +display = board.DISPLAY + +bitmap, palette = adafruit_imageload.load("images/color_wheel.bmp", + bitmap=displayio.Bitmap, + palette=displayio.Palette) + +tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette) + +group = displayio.Group() +group.append(tile_grid) +display.show(group) +while True: + pass diff --git a/examples/imageload_simpletest.py b/examples/imageload_simpletest.py index 56962b7..17dc3bf 100644 --- a/examples/imageload_simpletest.py +++ b/examples/imageload_simpletest.py @@ -1,4 +1,6 @@ import displayio import adafruit_imageload -image, palette = adafruit_imageload.load("images/4bit.bmp", bitmap=displayio.Bitmap, palette=displayio.Palette) +image, palette = adafruit_imageload.load("images/4bit.bmp", + bitmap=displayio.Bitmap, + palette=displayio.Palette) diff --git a/examples/images/color_wheel.bmp b/examples/images/color_wheel.bmp new file mode 100644 index 0000000..34d7e5f Binary files /dev/null and b/examples/images/color_wheel.bmp differ