diff --git a/adafruit_imageload/bmp/__init__.py b/adafruit_imageload/bmp/__init__.py index b4409fb..56d9b7c 100755 --- a/adafruit_imageload/bmp/__init__.py +++ b/adafruit_imageload/bmp/__init__.py @@ -44,8 +44,8 @@ def load( `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(file.read(4), 'little') + file.seek(14) + bmp_header_length = int.from_bytes(file.read(4), "little") # print(bmp_header_length) file.seek(0x12) # Width of the bitmap in pixels _width = int.from_bytes(file.read(4), "little") @@ -61,8 +61,18 @@ def load( compression = 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") + bitfield_masks = None + if compression == 3 and bmp_header_length >= 56: + bitfield_masks = {} + endianess = "little" if color_depth == 16 else "big" + file.seek(0x36) + bitfield_masks["red"] = int.from_bytes(file.read(4), endianess) + file.seek(0x3A) + bitfield_masks["green"] = int.from_bytes(file.read(4), endianess) + file.seek(0x3E) + bitfield_masks["blue"] = int.from_bytes(file.read(4), endianess) - if compression > 2: + if compression > 3: raise NotImplementedError("bitmask compression unsupported") if colors == 0 and color_depth >= 16: @@ -74,6 +84,7 @@ def load( _height, data_start, color_depth, + bitfield_masks, bitmap=bitmap, ) if colors == 0: diff --git a/adafruit_imageload/bmp/truecolor.py b/adafruit_imageload/bmp/truecolor.py index 710d6bf..b1b0889 100755 --- a/adafruit_imageload/bmp/truecolor.py +++ b/adafruit_imageload/bmp/truecolor.py @@ -16,18 +16,41 @@ import sys try: - from typing import Tuple, Optional + from typing import Union, Optional, Tuple from io import BufferedReader - from displayio import Bitmap from ..displayio_types import BitmapConstructor except ImportError: pass -from displayio import ColorConverter, Colorspace +from displayio import ColorConverter, Colorspace, Bitmap __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" +bitfield_colorspaces = ( + { # 16-bit RGB555 + "mask_values": (0x00007C00, 0x000003E0, 0x0000001F), + "color_space": Colorspace.RGB555, + }, + { # 16-bit RGB565 + "mask_values": (0x0000F800, 0x000007E0, 0x0000001F), + "color_space": Colorspace.RGB565, + }, + { # 24 or 32-bit RGB888 (Alpha ignored for 32-bit) + "mask_values": (0x0000FF00, 0x00FF0000, 0xFF000000), + "color_space": Colorspace.RGB888, + }, +) + + +def bitfield_format(bitfield_mask): + """Returns the colorspace for the given bitfield mask""" + mask = (bitfield_mask["red"], bitfield_mask["green"], bitfield_mask["blue"]) + for colorspace in bitfield_colorspaces: + if colorspace["mask_values"] == mask: + return colorspace["color_space"] + return None + def load( file: BufferedReader, @@ -35,6 +58,7 @@ def load( height: int, data_start: int, color_depth: int, + bitfield_masks: Union[dict, None], *, bitmap: Optional[BitmapConstructor] = None, ) -> Tuple[Optional[Bitmap], Optional[ColorConverter]]: @@ -46,6 +70,7 @@ def load( :param int height: Image height in pixels :param int data_start: Byte location where the data starts (after headers) :param int color_depth: Number of bits used to store a value + :param dict bitfield_masks: The bitfield masks for each color if using bitfield compression :param BitmapConstructor bitmap: a function that returns a displayio.Bitmap """ # pylint: disable=too-many-arguments,too-many-locals,too-many-branches @@ -55,7 +80,13 @@ def load( # Set up a ColorConverter object and set appropriate colorspace # to convert from based on the color depth input_colorspace = Colorspace.RGB888 - if color_depth == 16: + if bitfield_masks is not None: + colorspace = bitfield_format(bitfield_masks) + if colorspace is not None: + input_colorspace = colorspace + else: + raise NotImplementedError("Bitfield mask not supported") + elif color_depth == 16: input_colorspace = Colorspace.RGB555 converter_obj = ColorConverter(input_colorspace=input_colorspace) if sys.maxsize > 1073741823: @@ -64,7 +95,7 @@ def load( # convert unsigned int to signed int when height is negative height = negative_height_check(height) - bitmap_obj = bitmap(width, abs(height), 65535) + bitmap_obj = Bitmap(width, abs(height), 65535) file.seek(data_start) line_size = width * (color_depth // 8) # Set the seek direction based on whether the height value is negative or positive @@ -84,10 +115,23 @@ def load( for x in range(width): i = x * bytes_per_pixel - if color_depth == 16: - pixel = chunk[i] | chunk[i + 1] << 8 + if bitfield_masks is not None: + color = 0 + for byte in range(bytes_per_pixel): + color |= chunk[i + byte] << (8 * byte) + mask = ( + bitfield_masks["red"] + | bitfield_masks["green"] + | bitfield_masks["blue"] + ) + if color_depth in (24, 32): + mask = mask >> 8 + pixel = color & mask else: - pixel = chunk[i + 2] << 16 | chunk[i + 1] << 8 | chunk[i] + if color_depth == 16: + pixel = chunk[i] | chunk[i + 1] << 8 + else: + pixel = chunk[i + 2] << 16 | chunk[i + 1] << 8 | chunk[i] bitmap_obj[offset + x] = converter_obj.convert(pixel) return bitmap_obj, ColorConverter(input_colorspace=Colorspace.RGB565)