summaryrefslogtreecommitdiff
path: root/external/construct/formats/graphics/bmp.py
diff options
context:
space:
mode:
Diffstat (limited to 'external/construct/formats/graphics/bmp.py')
-rw-r--r--external/construct/formats/graphics/bmp.py113
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))