265 lines
7.3 KiB
Python
265 lines
7.3 KiB
Python
|
#
|
||
|
# 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):
|
||
|
|
||
|
format = "BMP"
|
||
|
format_description = "Windows Bitmap"
|
||
|
|
||
|
def _bitmap(self, header=0, offset=0):
|
||
|
if header:
|
||
|
self.fp.seek(header)
|
||
|
|
||
|
read = self.fp.read
|
||
|
|
||
|
# CORE/INFO
|
||
|
s = read(4)
|
||
|
s = s + ImageFile._safe_read(self.fp, i32(s)-4)
|
||
|
|
||
|
if len(s) == 12:
|
||
|
|
||
|
# OS/2 1.0 CORE
|
||
|
bits = i16(s[10:])
|
||
|
self.size = i16(s[4:]), i16(s[6:])
|
||
|
compression = 0
|
||
|
lutsize = 3
|
||
|
colors = 0
|
||
|
direction = -1
|
||
|
|
||
|
elif len(s) in [40, 64, 108, 124]:
|
||
|
|
||
|
# WIN 3.1 or OS/2 2.0 INFO
|
||
|
bits = i16(s[14:])
|
||
|
self.size = i32(s[4:]), i32(s[8:])
|
||
|
compression = i32(s[16:])
|
||
|
pxperm = (i32(s[24:]), i32(s[28:])) # Pixels per meter
|
||
|
lutsize = 4
|
||
|
colors = i32(s[32:])
|
||
|
direction = -1
|
||
|
if i8(s[11]) == 0xff:
|
||
|
# upside-down storage
|
||
|
self.size = self.size[0], 2**32 - self.size[1]
|
||
|
direction = 0
|
||
|
|
||
|
self.info["dpi"] = tuple(map(lambda x: math.ceil(x / 39.3701),
|
||
|
pxperm))
|
||
|
|
||
|
else:
|
||
|
raise IOError("Unsupported BMP header type (%d)" % len(s))
|
||
|
|
||
|
if (self.size[0]*self.size[1]) > 2**31:
|
||
|
# Prevent DOS for > 2gb images
|
||
|
raise IOError("Unsupported BMP Size: (%dx%d)" % self.size)
|
||
|
|
||
|
if not colors:
|
||
|
colors = 1 << bits
|
||
|
|
||
|
# MODE
|
||
|
try:
|
||
|
self.mode, rawmode = BIT2MODE[bits]
|
||
|
except KeyError:
|
||
|
raise IOError("Unsupported BMP pixel depth (%d)" % bits)
|
||
|
|
||
|
if compression == 3:
|
||
|
# BI_BITFIELDS compression
|
||
|
mask = i32(read(4)), i32(read(4)), i32(read(4))
|
||
|
if bits == 32 and mask == (0xff0000, 0x00ff00, 0x0000ff):
|
||
|
rawmode = "BGRX"
|
||
|
elif bits == 16 and mask == (0x00f800, 0x0007e0, 0x00001f):
|
||
|
rawmode = "BGR;16"
|
||
|
elif bits == 16 and mask == (0x007c00, 0x0003e0, 0x00001f):
|
||
|
rawmode = "BGR;15"
|
||
|
else:
|
||
|
# print bits, map(hex, mask)
|
||
|
raise IOError("Unsupported BMP bitfields layout")
|
||
|
elif compression != 0:
|
||
|
raise IOError("Unsupported BMP compression (%d)" % compression)
|
||
|
|
||
|
# LUT
|
||
|
if self.mode == "P":
|
||
|
palette = []
|
||
|
greyscale = 1
|
||
|
if colors == 2:
|
||
|
indices = (0, 255)
|
||
|
elif colors > 2**16 or colors <= 0: # We're reading a i32.
|
||
|
raise IOError("Unsupported BMP Palette size (%d)" % colors)
|
||
|
else:
|
||
|
indices = list(range(colors))
|
||
|
for i in indices:
|
||
|
rgb = read(lutsize)[:3]
|
||
|
if rgb != o8(i)*3:
|
||
|
greyscale = 0
|
||
|
palette.append(rgb)
|
||
|
if greyscale:
|
||
|
if colors == 2:
|
||
|
self.mode = rawmode = "1"
|
||
|
else:
|
||
|
self.mode = rawmode = "L"
|
||
|
else:
|
||
|
self.mode = "P"
|
||
|
self.palette = ImagePalette.raw(
|
||
|
"BGR", b"".join(palette)
|
||
|
)
|
||
|
|
||
|
if not offset:
|
||
|
offset = self.fp.tell()
|
||
|
|
||
|
self.tile = [("raw",
|
||
|
(0, 0) + self.size,
|
||
|
offset,
|
||
|
(rawmode, ((self.size[0]*bits+31) >> 3) & (~3),
|
||
|
direction))]
|
||
|
|
||
|
self.info["compression"] = compression
|
||
|
|
||
|
def _open(self):
|
||
|
|
||
|
# HEAD
|
||
|
s = self.fp.read(14)
|
||
|
if s[:2] != b"BM":
|
||
|
raise SyntaxError("Not a BMP file")
|
||
|
offset = i32(s[10:])
|
||
|
|
||
|
self._bitmap(offset=offset)
|
||
|
|
||
|
|
||
|
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),
|
||
|
}
|
||
|
|
||
|
|
||
|
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")
|