From 03c47bf2d9f6429cdec77adf68c17a89cd306eba Mon Sep 17 00:00:00 2001 From: Fiona Hopkins Date: Fri, 16 Aug 2019 22:16:26 -0400 Subject: [PATCH] Adds support for RLE compression to BMPs Fixes adafruit/Adafruit_CircuitPython_ImageLoad#20 RLE compression is allowed on 4-bit and 8-bit BMP images. This commit adds handling for compression levels 1 and 2. It adds 2 tiny test files for the 4-bit and 8-bit depths, since the algorithm differs slightly between them. It also includes a version of the color_wheel.bmp encoded with 8-bit RLE. I cut the image in half horizontally so that it would load on the PyGamer, which couldn't allocate the full Bitmap (presumably due to heap fragmentation). --- adafruit_imageload/bmp/__init__.py | 9 +- adafruit_imageload/bmp/indexed.py | 164 ++++++++++++++++++++++++++-- examples/images/4bit_rle.bmp | Bin 0 -> 298 bytes examples/images/8bit_rle.bmp | Bin 0 -> 1250 bytes examples/images/color_wheel_rle.bmp | Bin 0 -> 16998 bytes 5 files changed, 159 insertions(+), 14 deletions(-) create mode 100644 examples/images/4bit_rle.bmp create mode 100644 examples/images/8bit_rle.bmp create mode 100644 examples/images/color_wheel_rle.bmp diff --git a/adafruit_imageload/bmp/__init__.py b/adafruit_imageload/bmp/__init__.py index 26dcca3..0c77f07 100644 --- a/adafruit_imageload/bmp/__init__.py +++ b/adafruit_imageload/bmp/__init__.py @@ -51,14 +51,19 @@ def load(file, *, bitmap=None, palette=None): 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(0x1e) # Compression type + 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') if colors == 0 and color_depth >= 16: raise NotImplementedError("True color BMP unsupported") + if compression > 2: + raise NotImplementedError("bitmask compression unsupported") + if colors == 0: colors = 2 ** color_depth from . import indexed - return indexed.load(file, width, height, data_start, colors, color_depth, bitmap=bitmap, - palette=palette) + return indexed.load(file, width, height, data_start, colors, color_depth, + compression, bitmap=bitmap, palette=palette) diff --git a/adafruit_imageload/bmp/indexed.py b/adafruit_imageload/bmp/indexed.py index 5242dde..e25c89b 100644 --- a/adafruit_imageload/bmp/indexed.py +++ b/adafruit_imageload/bmp/indexed.py @@ -32,7 +32,8 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_ImageLoad.git" -def load(file, width, height, data_start, colors, color_depth, *, bitmap=None, palette=None): +def load(file, width, height, data_start, colors, color_depth, compression, *, + bitmap=None, palette=None): """Loads indexed bitmap data into bitmap and palette objects. :param file file: The open bmp file @@ -40,8 +41,9 @@ def load(file, width, height, data_start, colors, color_depth, *, bitmap=None, p :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 + :param int color_depth: Number of bits used to store a value + :param int compression: 0 - none, 1 - 8bit RLE, 2 - 4bit RLE""" + # pylint: disable=too-many-arguments,too-many-locals,too-many-branches if palette: palette = palette(colors) @@ -70,7 +72,6 @@ def load(file, width, height, data_start, colors, color_depth, *, bitmap=None, p if line_size % 4 != 0: line_size += (4 - line_size % 4) - chunk = bytearray(line_size) mask = (1 << minimum_color_depth) - 1 if height > 0: range1 = height - 1 @@ -80,14 +81,153 @@ def load(file, width, height, data_start, colors, color_depth, *, bitmap=None, p range1 = 0 range2 = abs(height) range3 = 1 - for y in range(range1, range2, range3): - file.readinto(chunk) - pixels_per_byte = 8 // color_depth - offset = y * width - 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 + if compression == 0: + chunk = bytearray(line_size) + for y in range(range1, range2, range3): + file.readinto(chunk) + pixels_per_byte = 8 // color_depth + offset = y * width + + 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 + elif compression in (1, 2): + decode_rle( + bitmap=bitmap, + file=file, + compression=compression, + y_range=(range1, range2, range3), + width=width) return bitmap, palette + +def decode_rle(bitmap, file, compression, y_range, width): + """Helper to decode RLE images""" + # pylint: disable=too-many-locals,too-many-nested-blocks,too-many-branches + + # RLE algorithm, either 8-bit (1) or 4-bit (2) + # + # Ref: http://www.fileformat.info/format/bmp/egff.htm + + is_4bit = compression == 2 + + # This will store the 2-byte run commands, which are either an + # amount to repeat and a value to repeat, or a 0x00 and command + # marker. + run_buf = bytearray(2) + + # We need to be prepared to load up to 256 pixels of literal image + # data. (0xFF is max literal length, but odd literal runs are padded + # up to an even byte count, so we need space for 256 in the case of + # 8-bit.) 4-bit images can get away with half that. + literal_buf = bytearray(128 if is_4bit else 256) + + # We iterate with numbers rather than a range because the "delta" + # command can cause us to jump forward arbitrarily in the output + # image. + # + # In theory RLE images are only stored in bottom-up scan line order, + # but we support either. + (range1, range2, range3) = y_range + y = range1 + x = 0 + + while y * range3 < range2 * range3: + offset = y * width + x + + # We keep track of how much space is left in our row so that we + # can avoid writing extra data outside of the Bitmap. While the + # reference above seems to say that the "end run" command is + # optional and that image data should wrap from one scan line to + # the next, in practice (looking at the output of ImageMagick + # and GIMP, and what Preview renders) the bitmap part of the + # image can contain data that goes beyond the image’s stated + # width that should just be ignored. For example, the 8bit RLE + # file is 15px wide but has data for 16px. + width_remaining = width - x + + file.readinto(run_buf) + + if run_buf[0] == 0: + # A repeat length of "0" is a special command. The next byte + # tells us what needs to happen. + if run_buf[1] == 0: + # end of the current scan line + y = y + range3 + x = 0 + elif run_buf[1] == 1: + # end of image + break + elif run_buf[1] == 2: + # delta command jumps us ahead in the bitmap output by + # the x, y amounts stored in the next 2 bytes. + file.readinto(run_buf) + + x = x + run_buf[0] + y = y + run_buf[1] * range3 + else: + # command values of 3 or more indicate that many pixels + # of literal (uncompressed) image data. For 8-bit mode, + # this is raw bytes, but 4-bit mode counts in nibbles. + literal_length_px = run_buf[1] + + # Inverting the value here to get round-up integer division + if is_4bit: + read_length_bytes = -(-literal_length_px // 2) + else: + read_length_bytes = literal_length_px + + # If the run has an odd length then there’s a 1-byte padding + # we need to consume but not write into the output + if read_length_bytes % 2 == 1: + read_length_bytes += 1 + + # We use memoryview to artificially limit the length of + # literal_buf so that readinto only reads the amount + # that we want. + literal_buf_mem = memoryview(literal_buf) + file.readinto(literal_buf_mem[0:read_length_bytes]) + + if is_4bit: + for i in range(0, min(literal_length_px, width_remaining)): + # Expanding the two nibbles of the 4-bit data + # into two bytes for our output bitmap. + if i % 2 == 0: + bitmap[offset + i] = literal_buf[i // 2] >> 4 + else: + bitmap[offset + i] = literal_buf[i // 2] & 0x0F + else: + # 8-bit values are just a raw copy (limited by + # what’s left in the row so we don’t overflow out of + # the buffer) + for i in range(0, min(literal_length_px, width_remaining)): + bitmap[offset + i] = literal_buf[i] + + x = x + literal_length_px + else: + # first byte was not 0, which means it tells us how much to + # repeat the next byte into the output + run_length_px = run_buf[0] + + if is_4bit: + # In 4 bit mode, we repeat the *two* values that are + # packed into the next byte. The repeat amount is based + # on pixels, not bytes, though, so if we were to repeat + # 0xab 3 times, the output pixel values would be: 0x0a + # 0x0b 0x0a (notice how it ends at 0x0a) rather than + # 0x0a 0x0b 0x0a 0x0b 0x0a 0x0b + run_values = [ + run_buf[1] >> 4, + run_buf[1] & 0x0F + ] + for i in range(0, min(run_length_px, width_remaining)): + bitmap[offset + i] = run_values[i % 2] + else: + run_value = run_buf[1] + for i in range(0, min(run_length_px, width_remaining)): + bitmap[offset + i] = run_value + + + x = x + run_length_px diff --git a/examples/images/4bit_rle.bmp b/examples/images/4bit_rle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..427d56516ffa36fabd93d9ed8bd96eb51490dca3 GIT binary patch literal 298 zcmZ?r)na4-gEAng0mS@3EC|Gm3@i*xKza)hE9*h90FVvBAW#p)|ADxiVIz>93+DZ2 z_{Xrnfq|i_s*2(Nyrm2bGk_|9CpwI0Z0ul_U^5r|G$PRDS*@<*bo;Y Gk&FQOzfLFs literal 0 HcmV?d00001 diff --git a/examples/images/8bit_rle.bmp b/examples/images/8bit_rle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0d5cc8bbe85f75a3c9410b8e318e46141e919f4c GIT binary patch literal 1250 zcmeH@y$!-J5QU#{0^!$U1{x|#2B2pTHb8O%W6+Q;9Ssw(3u{=yd^QwmLXVV_Ki~7a zS6H{(KMUpB($^>lOE9WHy1{DmAsfl0P7vX!JN=qpQ7=8tlJ{P*Zlntd&H+NK)1?ps z*}nP>^c(os4Y*RWg)Qw2MnAM=E|`;P>jmBzD*P@zWNu(Wrb|zmI~b5@>GZ!Z<_!pb B9l`(r literal 0 HcmV?d00001 diff --git a/examples/images/color_wheel_rle.bmp b/examples/images/color_wheel_rle.bmp new file mode 100644 index 0000000000000000000000000000000000000000..656c3b6a2ceff6e3aa06b27d472e1d2f04268328 GIT binary patch literal 16998 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 A)&Kwi literal 0 HcmV?d00001