Source code for pyExcelerator.Bitmap

#!/usr/bin/env python
# -*- coding: windows-1251 -*-

#  Copyright (C) 2005 Roman V. Kiseliov

#  Portions are Copyright (c) 2004 Evgeny Filatov <fufff@users.sourceforge.net>

#  Portions are Copyright (c) 2002-2004 John McNamara (Perl Spreadsheet::WriteExcel)

#  All rights reserved.

# 
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
# 
#  1. Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# 
#  2. Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions and the following disclaimer in
#     the documentation and/or other materials provided with the
#     distribution.
# 
#  3. All advertising materials mentioning features or use of this
#     software must display the following acknowledgment:
#     "This product includes software developed by
#      Roman V. Kiseliov <roman@kiseliov.ru>."
# 
#  4. Redistributions of any form whatsoever must retain the following
#     acknowledgment:
#     "This product includes software developed by
#      Roman V. Kiseliov <roman@kiseliov.ru>."
# 
#  THIS SOFTWARE IS PROVIDED BY Roman V. Kiseliov ``AS IS'' AND ANY
#  EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL Roman V. Kiseliov OR
#  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
#  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
#  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
#  OF THE POSSIBILITY OF SUCH DAMAGE.


__rev_id__ = """$Id: Bitmap.py,v 1.4 2005/07/20 07:24:11 rvk Exp $"""


from BIFFRecords import BiffRecord
from struct import *


def _size_col(sheet, col):
    return sheet.col_width(col)


def _size_row(sheet, row):
    return sheet.row_height(row)     


def _position_image(sheet, row_start, col_start, x1, y1, width, height):
    """Calculate the vertices that define the position of the image as required by
    the OBJ record.

             +------------+------------+
             |     A      |      B     |
       +-----+------------+------------+
       |     |(x1,y1)     |            |
       |  1  |(A1)._______|______      |
       |     |    |              |     |
       |     |    |              |     |
       +-----+----|    BITMAP    |-----+
       |     |    |              |     |
       |  2  |    |______________.     |
       |     |            |        (B2)|
       |     |            |     (x2,y2)|
       +---- +------------+------------+

    Example of a bitmap that covers some of the area from cell A1 to cell B2.

    Based on the width and height of the bitmap we need to calculate 8 vars:
        col_start, row_start, col_end, row_end, x1, y1, x2, y2.
    The width and height of the cells are also variable and have to be taken into
    account.
    The values of col_start and row_start are passed in from the calling
    function. The values of col_end and row_end are calculated by subtracting
    the width and height of the bitmap from the width and height of the
    underlying cells.
    The vertices are expressed as a percentage of the underlying cell width as
    follows (rhs values are in pixels):

           x1 = X / W *1024
           y1 = Y / H *256
           x2 = (X-1) / W *1024
           y2 = (Y-1) / H *256

           Where:  X is distance from the left side of the underlying cell
                   Y is distance from the top of the underlying cell
                   W is the width of the cell
                   H is the height of the cell

    Note: the SDK incorrectly states that the height should be expressed as a
    percentage of 1024.

    col_start  - Col containing upper left corner of object
    row_start  - Row containing top left corner of object
    x1  - Distance to left side of object
    y1  - Distance to top of object
    width  - Width of image frame
    height  - Height of image frame
    
    """
    # Adjust start column for offsets that are greater than the col width
    while x1 >= _size_col(sheet, col_start):
        x1 -= _size_col(sheet, col_start)
        col_start += 1
    # Adjust start row for offsets that are greater than the row height
    while y1 >= _size_row(sheet, row_start):
        y1 -= _size_row(sheet, row_start)
        row_start += 1
    # Initialise end cell to the same as the start cell
    row_end = row_start   # Row containing bottom right corner of object
    col_end = col_start   # Col containing lower right corner of object
    width = width + x1 - 1
    height = height + y1 - 1
    # Subtract the underlying cell widths to find the end cell of the image
    while (width >= _size_col(sheet, col_end)):
        width -= _size_col(sheet, col_end)
        col_end += 1
    # Subtract the underlying cell heights to find the end cell of the image
    while (height >= _size_row(sheet, row_end)):
        height -= _size_row(sheet, row_end)
        row_end += 1
    # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
    # with zero height or width.
    if ((_size_col(sheet, col_start) == 0) or (_size_col(sheet, col_end) == 0)
            or (_size_row(sheet, row_start) == 0) or (_size_row(sheet, row_end) == 0)):
        return
    # Convert the pixel values to the percentage value expected by Excel
    x1 = float(x1) / _size_col(sheet, col_start) * 1024
    y1 = float(y1) / _size_row(sheet, row_start) * 256
    # Distance to right side of object
    x2 = float(width) / _size_col(sheet, col_end) * 1024
    # Distance to bottom of object
    y2 = float(height) / _size_row(sheet, row_end) * 256
    return (col_start, x1, row_start, y1, col_end, x2, row_end, y2)


[docs]class ObjBmpRecord(BiffRecord): _REC_ID = 0x005D # Record identifier def __init__(self, row, col, sheet, im_data_bmp, x, y, scale_x, scale_y): # Scale the frame of the image. width = im_data_bmp.width * scale_x height = im_data_bmp.height * scale_y # Calculate the vertices of the image and write the OBJ record col_start, x1, row_start, y1, col_end, x2, row_end, y2 = _position_image(sheet, row, col, x, y, width, height) """Store the OBJ record that precedes an IMDATA record. This could be generalise to support other Excel objects. """ cObj = 0x0001 # Count of objects in file (set to 1) OT = 0x0008 # Object type. 8 = Picture id = 0x0001 # Object ID grbit = 0x0614 # Option flags colL = col_start # Col containing upper left corner of object dxL = x1 # Distance from left side of cell rwT = row_start # Row containing top left corner of object dyT = y1 # Distance from top of cell colR = col_end # Col containing lower right corner of object dxR = x2 # Distance from right of cell rwB = row_end # Row containing bottom right corner of object dyB = y2 # Distance from bottom of cell cbMacro = 0x0000 # Length of FMLA structure Reserved1 = 0x0000 # Reserved Reserved2 = 0x0000 # Reserved icvBack = 0x09 # Background colour icvFore = 0x09 # Foreground colour fls = 0x00 # Fill pattern fAuto = 0x00 # Automatic fill icv = 0x08 # Line colour lns = 0xff # Line style lnw = 0x01 # Line weight fAutoB = 0x00 # Automatic border frs = 0x0000 # Frame style cf = 0x0009 # Image format, 9 = bitmap Reserved3 = 0x0000 # Reserved cbPictFmla = 0x0000 # Length of FMLA structure Reserved4 = 0x0000 # Reserved grbit2 = 0x0001 # Option flags Reserved5 = 0x0000 # Reserved data = pack("<L", cObj) data += pack("<H", OT) data += pack("<H", id) data += pack("<H", grbit) data += pack("<H", colL) data += pack("<H", dxL) data += pack("<H", rwT) data += pack("<H", dyT) data += pack("<H", colR) data += pack("<H", dxR) data += pack("<H", rwB) data += pack("<H", dyB) data += pack("<H", cbMacro) data += pack("<L", Reserved1) data += pack("<H", Reserved2) data += pack("<B", icvBack) data += pack("<B", icvFore) data += pack("<B", fls) data += pack("<B", fAuto) data += pack("<B", icv) data += pack("<B", lns) data += pack("<B", lnw) data += pack("<B", fAutoB) data += pack("<H", frs) data += pack("<L", cf) data += pack("<H", Reserved3) data += pack("<H", cbPictFmla) data += pack("<H", Reserved4) data += pack("<H", grbit2) data += pack("<L", Reserved5) self._rec_data = data
def _process_bitmap(bitmap): """Convert a 24 bit bitmap into the modified internal format used by Windows. This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the MSDN library. """ if hasattr(bitmap, 'read'): fh = bitmap else: # Open file and binmode the data in case the platform needs it. fh = file(bitmap, "rb") try: # Slurp the file into a string. data = fh.read() finally: if not hasattr(bitmap, 'read'): fh.close() # Check that the file is big enough to be a bitmap. if len(data) <= 0x36: raise Exception("bitmap doesn't contain enough data.") # The first 2 bytes are used to identify the bitmap. if (data[:2] != "BM"): raise Exception("bitmap doesn't appear to to be a valid bitmap image.") # Remove bitmap data: ID. data = data[2:] # Read and remove the bitmap size. This is more reliable than reading # the data size at offset 0x22. # size = unpack("<L", data[:4])[0] size -= 0x36 # Subtract size of bitmap header. size += 0x0C # Add size of BIFF header. data = data[4:] # Remove bitmap data: reserved, offset, header length. data = data[12:] # Read and remove the bitmap width and height. Verify the sizes. width, height = unpack("<LL", data[:8]) data = data[8:] if (width > 0xFFFF): raise Exception("bitmap: largest image width supported is 65k.") if (height > 0xFFFF): raise Exception("bitmap: largest image height supported is 65k.") # Read and remove the bitmap planes and bpp data. Verify them. planes, bitcount = unpack("<HH", data[:4]) data = data[4:] if (bitcount != 24): raise Exception("bitmap isn't a 24bit true color bitmap.") if (planes != 1): raise Exception("bitmap: only 1 plane supported in bitmap image.") # Read and remove the bitmap compression. Verify compression. compression = unpack("<L", data[:4])[0] data = data[4:] if (compression != 0): raise Exception("bitmap: compression not supported in bitmap image.") # Remove bitmap data: data size, hres, vres, colours, imp. colours. data = data[20:] # Add the BITMAPCOREHEADER data header = pack("<LHHHH", 0x000c, width, height, 0x01, 0x18) data = header + data return (width, height, size, data)
[docs]class ImDataBmpRecord(BiffRecord): _REC_ID = 0x007F def __init__(self, filename): """Insert a 24bit bitmap image in a worksheet. The main record required is IMDATA but it must be proceeded by a OBJ record to define its position. """ BiffRecord.__init__(self) self.width, self.height, self.size, data = _process_bitmap(filename) # Write the IMDATA record to store the bitmap data cf = 0x09 env = 0x01 lcb = self.size self._rec_data = pack("<HHL", cf, env, lcb) + data