Skip to content

Commit

Permalink
Merge pull request #72 from makermelissa/master
Browse files Browse the repository at this point in the history
Add Bitfield Compression Support for GIMP
  • Loading branch information
FoamyGuy authored Aug 13, 2023
2 parents 4a2f5a6 + 09fece6 commit 5e3e784
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 11 deletions.
17 changes: 14 additions & 3 deletions adafruit_imageload/bmp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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:
Expand All @@ -74,6 +84,7 @@ def load(
_height,
data_start,
color_depth,
bitfield_masks,
bitmap=bitmap,
)
if colors == 0:
Expand Down
60 changes: 52 additions & 8 deletions adafruit_imageload/bmp/truecolor.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,49 @@
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,
width: int,
height: int,
data_start: int,
color_depth: int,
bitfield_masks: Union[dict, None],
*,
bitmap: Optional[BitmapConstructor] = None,
) -> Tuple[Optional[Bitmap], Optional[ColorConverter]]:
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand All @@ -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)

0 comments on commit 5e3e784

Please sign in to comment.