""" 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))