diff options
Diffstat (limited to 'external/construct/formats/graphics/bmp.py')
-rw-r--r-- | external/construct/formats/graphics/bmp.py | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/external/construct/formats/graphics/bmp.py b/external/construct/formats/graphics/bmp.py new file mode 100644 index 0000000..abe1ad0 --- /dev/null +++ b/external/construct/formats/graphics/bmp.py @@ -0,0 +1,113 @@ +""" +Windows/OS2 Bitmap (BMP) +this could have been a perfect show-case file format, but they had to make +it ugly (all sorts of alignment or +""" +from construct import * + + +#=============================================================================== +# pixels: uncompressed +#=============================================================================== +def UncompressedRows(subcon, align_to_byte = False): + """argh! lines must be aligned to a 4-byte boundary, and bit-pixel + lines must be aligned to full bytes...""" + if align_to_byte: + line_pixels = Bitwise( + Aligned(Array(lambda ctx: ctx.width, subcon), modulus = 8) + ) + else: + line_pixels = Array(lambda ctx: ctx.width, subcon) + return Array(lambda ctx: ctx.height, + Aligned(line_pixels, modulus = 4) + ) + +uncompressed_pixels = Switch("uncompressed", lambda ctx: ctx.bpp, + { + 1 : UncompressedRows(Bit("index"), align_to_byte = True), + 4 : UncompressedRows(Nibble("index"), align_to_byte = True), + 8 : UncompressedRows(Byte("index")), + 24 : UncompressedRows( + Sequence("rgb", Byte("red"), Byte("green"), Byte("blue")) + ), + } +) + +#=============================================================================== +# pixels: Run Length Encoding (RLE) 8 bit +#=============================================================================== +class RunLengthAdapter(Adapter): + def _encode(self, obj): + return len(obj), obj[0] + def _decode(self, obj): + length, value = obj + return [value] * length + +rle8pixel = RunLengthAdapter( + Sequence("rle8pixel", + Byte("length"), + Byte("value") + ) +) + +#=============================================================================== +# file structure +#=============================================================================== +bitmap_file = Struct("bitmap_file", + # header + Const(String("signature", 2), "BM"), + ULInt32("file_size"), + Padding(4), + ULInt32("data_offset"), + ULInt32("header_size"), + Enum(Alias("version", "header_size"), + v2 = 12, + v3 = 40, + v4 = 108, + ), + ULInt32("width"), + ULInt32("height"), + Value("number_of_pixels", lambda ctx: ctx.width * ctx.height), + ULInt16("planes"), + ULInt16("bpp"), # bits per pixel + Enum(ULInt32("compression"), + Uncompressed = 0, + RLE8 = 1, + RLE4 = 2, + Bitfields = 3, + JPEG = 4, + PNG = 5, + ), + ULInt32("image_data_size"), # in bytes + ULInt32("horizontal_dpi"), + ULInt32("vertical_dpi"), + ULInt32("colors_used"), + ULInt32("important_colors"), + + # palette (24 bit has no palette) + OnDemand( + Array(lambda ctx: 2 ** ctx.bpp if ctx.bpp <= 8 else 0, + Struct("palette", + Byte("blue"), + Byte("green"), + Byte("red"), + Padding(1), + ) + ) + ), + + # pixels + OnDemandPointer(lambda ctx: ctx.data_offset, + Switch("pixels", lambda ctx: ctx.compression, + { + "Uncompressed" : uncompressed_pixels, + } + ), + ), +) + + +if __name__ == "__main__": + obj = bitmap_file.parse_stream(open("../../../tests/bitmap8.bmp", "rb")) + print (obj) + print (repr(obj.pixels.value)) |