# 
 | 
# 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" 
 | 
  
 | 
  
 | 
import string 
 | 
import Image, ImageFile, ImagePalette 
 | 
  
 | 
  
 | 
# 
 | 
# -------------------------------------------------------------------- 
 | 
# Read BMP file 
 | 
  
 | 
def i16(c): 
 | 
    return ord(c[0]) + (ord(c[1])<<8) 
 | 
  
 | 
def i32(c): 
 | 
    return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24) 
 | 
  
 | 
  
 | 
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] == "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]: 
 | 
  
 | 
            # 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:]) 
 | 
            lutsize = 4 
 | 
            colors = i32(s[32:]) 
 | 
            direction = -1 
 | 
            if s[11] == '\xff': 
 | 
                # upside-down storage 
 | 
                self.size = self.size[0], 2**32 - self.size[1] 
 | 
                direction = 0 
 | 
  
 | 
        else: 
 | 
            raise IOError("Unsupported BMP header type (%d)" % len(s)) 
 | 
  
 | 
        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) 
 | 
            else: 
 | 
                indices = range(colors) 
 | 
            for i in indices: 
 | 
                rgb = read(lutsize)[:3] 
 | 
                if rgb != chr(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", string.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] != "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 
 | 
  
 | 
def o16(i): 
 | 
    return chr(i&255) + chr(i>>8&255) 
 | 
  
 | 
def o32(i): 
 | 
    return chr(i&255) + chr(i>>8&255) + chr(i>>16&255) + chr(i>>24&255) 
 | 
  
 | 
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 
 | 
  
 | 
    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("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(1) + o32(1) +          # resolution 
 | 
             o32(colors) +              # colors used 
 | 
             o32(colors))               # colors important 
 | 
  
 | 
    fp.write("\000" * (header - 40))    # padding (for OS/2 format) 
 | 
  
 | 
    if im.mode == "1": 
 | 
        for i in (0, 255): 
 | 
            fp.write(chr(i) * 4) 
 | 
    elif im.mode == "L": 
 | 
        for i in range(256): 
 | 
            fp.write(chr(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") 
 |