summaryrefslogtreecommitdiffstats
path: root/lib/Python/Lib/PIL/BmpImagePlugin.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Python/Lib/PIL/BmpImagePlugin.py')
-rw-r--r--lib/Python/Lib/PIL/BmpImagePlugin.py281
1 files changed, 281 insertions, 0 deletions
diff --git a/lib/Python/Lib/PIL/BmpImagePlugin.py b/lib/Python/Lib/PIL/BmpImagePlugin.py
new file mode 100644
index 000000000..30ca10971
--- /dev/null
+++ b/lib/Python/Lib/PIL/BmpImagePlugin.py
@@ -0,0 +1,281 @@
+#
+# The Python Imaging Library.
+# $Id$
+#
+# BMP file handler
+#
+# Windows (and OS/2) native bitmap storage format.
+#
+# history:
+# 1995-09-01 fl Created
+# 1996-04-30 fl Added save
+# 1997-08-27 fl Fixed save of 1-bit images
+# 1998-03-06 fl Load P images as L where possible
+# 1998-07-03 fl Load P images as 1 where possible
+# 1998-12-29 fl Handle small palettes
+# 2002-12-30 fl Fixed load of 1-bit palette images
+# 2003-04-21 fl Fixed load of 1-bit monochrome images
+# 2003-04-23 fl Added limited support for BI_BITFIELDS compression
+#
+# Copyright (c) 1997-2003 by Secret Labs AB
+# Copyright (c) 1995-2003 by Fredrik Lundh
+#
+# See the README file for information on usage and redistribution.
+#
+
+
+__version__ = "0.7"
+
+
+from PIL import Image, ImageFile, ImagePalette, _binary
+import math
+
+
+i8 = _binary.i8
+i16 = _binary.i16le
+i32 = _binary.i32le
+o8 = _binary.o8
+o16 = _binary.o16le
+o32 = _binary.o32le
+
+#
+# --------------------------------------------------------------------
+# Read BMP file
+
+BIT2MODE = {
+ # bits => mode, rawmode
+ 1: ("P", "P;1"),
+ 4: ("P", "P;4"),
+ 8: ("P", "P"),
+ 16: ("RGB", "BGR;15"),
+ 24: ("RGB", "BGR"),
+ 32: ("RGB", "BGRX"),
+}
+
+
+def _accept(prefix):
+ return prefix[:2] == b"BM"
+
+
+# ==============================================================================
+# Image plugin for the Windows BMP format.
+# ==============================================================================
+class BmpImageFile(ImageFile.ImageFile):
+ """ Image plugin for the Windows Bitmap format (BMP) """
+
+ # -------------------------------------------------------------- Description
+ format_description = "Windows Bitmap"
+ format = "BMP"
+ # --------------------------------------------------- BMP Compression values
+ COMPRESSIONS = {'RAW': 0, 'RLE8': 1, 'RLE4': 2, 'BITFIELDS': 3, 'JPEG': 4, 'PNG': 5}
+ RAW, RLE8, RLE4, BITFIELDS, JPEG, PNG = 0, 1, 2, 3, 4, 5
+
+ def _bitmap(self, header=0, offset=0):
+ """ Read relevant info about the BMP """
+ read, seek = self.fp.read, self.fp.seek
+ if header:
+ seek(header)
+ file_info = dict()
+ file_info['header_size'] = i32(read(4)) # read bmp header size @offset 14 (this is part of the header size)
+ file_info['direction'] = -1
+ # --------------------- If requested, read header at a specific position
+ header_data = ImageFile._safe_read(self.fp, file_info['header_size'] - 4) # read the rest of the bmp header, without its size
+ # --------------------------------------------------- IBM OS/2 Bitmap v1
+ # ------ This format has different offsets because of width/height types
+ if file_info['header_size'] == 12:
+ file_info['width'] = i16(header_data[0:2])
+ file_info['height'] = i16(header_data[2:4])
+ file_info['planes'] = i16(header_data[4:6])
+ file_info['bits'] = i16(header_data[6:8])
+ file_info['compression'] = self.RAW
+ file_info['palette_padding'] = 3
+ # ---------------------------------------------- Windows Bitmap v2 to v5
+ elif file_info['header_size'] in (40, 64, 108, 124): # v3, OS/2 v2, v4, v5
+ if file_info['header_size'] >= 40: # v3 and OS/2
+ file_info['y_flip'] = i8(header_data[7]) == 0xff
+ file_info['direction'] = 1 if file_info['y_flip'] else -1
+ file_info['width'] = i32(header_data[0:4])
+ file_info['height'] = i32(header_data[4:8]) if not file_info['y_flip'] else 2**32 - i32(header_data[4:8])
+ file_info['planes'] = i16(header_data[8:10])
+ file_info['bits'] = i16(header_data[10:12])
+ file_info['compression'] = i32(header_data[12:16])
+ file_info['data_size'] = i32(header_data[16:20]) # byte size of pixel data
+ file_info['pixels_per_meter'] = (i32(header_data[20:24]), i32(header_data[24:28]))
+ file_info['colors'] = i32(header_data[28:32])
+ file_info['palette_padding'] = 4
+ self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701), file_info['pixels_per_meter']))
+ if file_info['compression'] == self.BITFIELDS:
+ if len(header_data) >= 52:
+ for idx, mask in enumerate(['r_mask', 'g_mask', 'b_mask', 'a_mask']):
+ file_info[mask] = i32(header_data[36+idx*4:40+idx*4])
+ else:
+ for mask in ['r_mask', 'g_mask', 'b_mask', 'a_mask']:
+ file_info[mask] = i32(read(4))
+ file_info['rgb_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'])
+ file_info['rgba_mask'] = (file_info['r_mask'], file_info['g_mask'], file_info['b_mask'], file_info['a_mask'])
+ else:
+ raise IOError("Unsupported BMP header type (%d)" % file_info['header_size'])
+ # ------------------ Special case : header is reported 40, which
+ # ---------------------- is shorter than real size for bpp >= 16
+ self.size = file_info['width'], file_info['height']
+ # -------- If color count was not found in the header, compute from bits
+ file_info['colors'] = file_info['colors'] if file_info.get('colors', 0) else (1 << file_info['bits'])
+ # -------------------------------- Check abnormal values for DOS attacks
+ if file_info['width'] * file_info['height'] > 2**31:
+ raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
+ # ----------------------- Check bit depth for unusual unsupported values
+ self.mode, raw_mode = BIT2MODE.get(file_info['bits'], (None, None))
+ if self.mode is None:
+ raise IOError("Unsupported BMP pixel depth (%d)" % file_info['bits'])
+ # ----------------- Process BMP with Bitfields compression (not palette)
+ if file_info['compression'] == self.BITFIELDS:
+ SUPPORTED = {
+ 32: [(0xff0000, 0xff00, 0xff, 0x0), (0xff0000, 0xff00, 0xff, 0xff000000), (0x0, 0x0, 0x0, 0x0)],
+ 24: [(0xff0000, 0xff00, 0xff)],
+ 16: [(0xf800, 0x7e0, 0x1f), (0x7c00, 0x3e0, 0x1f)]}
+ MASK_MODES = {
+ (32, (0xff0000, 0xff00, 0xff, 0x0)): "BGRX", (32, (0xff0000, 0xff00, 0xff, 0xff000000)): "BGRA", (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
+ (24, (0xff0000, 0xff00, 0xff)): "BGR",
+ (16, (0xf800, 0x7e0, 0x1f)): "BGR;16", (16, (0x7c00, 0x3e0, 0x1f)): "BGR;15"}
+ if file_info['bits'] in SUPPORTED:
+ if file_info['bits'] == 32 and file_info['rgba_mask'] in SUPPORTED[file_info['bits']]:
+ raw_mode = MASK_MODES[(file_info['bits'], file_info['rgba_mask'])]
+ self.mode = "RGBA" if raw_mode in ("BGRA",) else self.mode
+ elif file_info['bits'] in (24, 16) and file_info['rgb_mask'] in SUPPORTED[file_info['bits']]:
+ raw_mode = MASK_MODES[(file_info['bits'], file_info['rgb_mask'])]
+ else:
+ raise IOError("Unsupported BMP bitfields layout")
+ else:
+ raise IOError("Unsupported BMP bitfields layout")
+ elif file_info['compression'] == self.RAW:
+ if file_info['bits'] == 32 and header == 22: # 32-bit .cur offset
+ raw_mode, self.mode = "BGRA", "RGBA"
+ else:
+ raise IOError("Unsupported BMP compression (%d)" % file_info['compression'])
+ # ---------------- Once the header is processed, process the palette/LUT
+ if self.mode == "P": # Paletted for 1, 4 and 8 bit images
+ # ----------------------------------------------------- 1-bit images
+ if not (0 < file_info['colors'] <= 65536):
+ raise IOError("Unsupported BMP Palette size (%d)" % file_info['colors'])
+ else:
+ padding = file_info['palette_padding']
+ palette = read(padding * file_info['colors'])
+ greyscale = True
+ indices = (0, 255) if file_info['colors'] == 2 else list(range(file_info['colors']))
+ # ------------------ Check if greyscale and ignore palette if so
+ for ind, val in enumerate(indices):
+ rgb = palette[ind*padding:ind*padding + 3]
+ if rgb != o8(val) * 3:
+ greyscale = False
+ # -------- If all colors are grey, white or black, ditch palette
+ if greyscale:
+ self.mode = "1" if file_info['colors'] == 2 else "L"
+ raw_mode = self.mode
+ else:
+ self.mode = "P"
+ self.palette = ImagePalette.raw("BGRX" if padding == 4 else "BGR", palette)
+
+ # ----------------------------- Finally set the tile data for the plugin
+ self.info['compression'] = file_info['compression']
+ self.tile = [('raw', (0, 0, file_info['width'], file_info['height']), offset or self.fp.tell(),
+ (raw_mode, ((file_info['width'] * file_info['bits'] + 31) >> 3) & (~3), file_info['direction'])
+ )]
+
+ def _open(self):
+ """ Open file, check magic number and read header """
+ # read 14 bytes: magic number, filesize, reserved, header final offset
+ head_data = self.fp.read(14)
+ # choke if the file does not have the required magic bytes
+ if head_data[0:2] != b"BM":
+ raise SyntaxError("Not a BMP file")
+ # read the start position of the BMP image data (u32)
+ offset = i32(head_data[10:14])
+ # load bitmap information (offset=raster info)
+ self._bitmap(offset=offset)
+
+
+# ==============================================================================
+# Image plugin for the DIB format (BMP alias)
+# ==============================================================================
+class DibImageFile(BmpImageFile):
+
+ format = "DIB"
+ format_description = "Windows Bitmap"
+
+ def _open(self):
+ self._bitmap()
+
+#
+# --------------------------------------------------------------------
+# Write BMP file
+
+SAVE = {
+ "1": ("1", 1, 2),
+ "L": ("L", 8, 256),
+ "P": ("P", 8, 256),
+ "RGB": ("BGR", 24, 0),
+ "RGBA": ("BGRA", 32, 0),
+}
+
+
+def _save(im, fp, filename, check=0):
+ try:
+ rawmode, bits, colors = SAVE[im.mode]
+ except KeyError:
+ raise IOError("cannot write mode %s as BMP" % im.mode)
+
+ if check:
+ return check
+
+ info = im.encoderinfo
+
+ dpi = info.get("dpi", (96, 96))
+
+ # 1 meter == 39.3701 inches
+ ppm = tuple(map(lambda x: int(x * 39.3701), dpi))
+
+ stride = ((im.size[0]*bits+7)//8+3) & (~3)
+ header = 40 # or 64 for OS/2 version 2
+ offset = 14 + header + colors * 4
+ image = stride * im.size[1]
+
+ # bitmap header
+ fp.write(b"BM" + # file type (magic)
+ o32(offset+image) + # file size
+ o32(0) + # reserved
+ o32(offset)) # image data offset
+
+ # bitmap info header
+ fp.write(o32(header) + # info header size
+ o32(im.size[0]) + # width
+ o32(im.size[1]) + # height
+ o16(1) + # planes
+ o16(bits) + # depth
+ o32(0) + # compression (0=uncompressed)
+ o32(image) + # size of bitmap
+ o32(ppm[0]) + o32(ppm[1]) + # resolution
+ o32(colors) + # colors used
+ o32(colors)) # colors important
+
+ fp.write(b"\0" * (header - 40)) # padding (for OS/2 format)
+
+ if im.mode == "1":
+ for i in (0, 255):
+ fp.write(o8(i) * 4)
+ elif im.mode == "L":
+ for i in range(256):
+ fp.write(o8(i) * 4)
+ elif im.mode == "P":
+ fp.write(im.im.getpalette("RGB", "BGRX"))
+
+ ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0,
+ (rawmode, stride, -1))])
+
+#
+# --------------------------------------------------------------------
+# Registry
+
+Image.register_open(BmpImageFile.format, BmpImageFile, _accept)
+Image.register_save(BmpImageFile.format, _save)
+
+Image.register_extension(BmpImageFile.format, ".bmp")