def write( filename, width, height, data ): """ Writes data out to a windows bitmap file. :Parameters: filename : string The name of the file to write to. width : int The width of the image in pixels height : int The height of the image in pixels data : string The data as a binary string. :rtype: boolean :returns: True on success """ bpp = 24 if bpp == 4: # 16 colors are reserved for this depth of images. colors = { "\x00\x00\x00": 0, "\x80\x00\x00": 1, "\x00\x80\x00": 2, "\x80\x80\x00": 3, "\x00\x00\x80": 4, "\x80\x00\x80": 5, "\x00\x80\x80": 6, "\xc0\xc0\xc0": 7, "\x80\x80\x80": 8, "\xff\x00\x00": 9, "\x00\xff\x00":10, "\xff\xff\x00":11, "\x00\x00\xff":12, "\x00\xff\xff":13, "\x00\xff\xff":14, "\xff\xff\xff":15 } data = [ data[ i*width*3 : (i+1)*width*3 ] for i in range( height ) ] data.reverse( ) #reverse the bytes ( they are stored this way in bmp format ) rawdata = ''.join( data ) # I'm not sure of the easiest way to transform the raw data into the format I need here. # Color #7 is causing the problem, otherwise I could just bitmask it with 0xc0. # If I do that, I won't be able to tell the difference between 0xff and 0xc0. if bpp == 24: #swap the red and blue channels to make it easier to write this format data = swapChannels( width, height, data, 0, 2 )[ 2 ] # split data into a list of rows data = [ data[ i*width*3 : (i+1)*width*3 ] for i in range( height ) ] padding = "\x00" * ( ( -width * 3 ) % 4 ) # make each row an even multiple of 4. data.reverse( ) #reverse the bytes ( they are stored this way in bmp format ) bmpdata = padding.join( data ) # create the header (Windows V3) filesize = len( bmpdata ) + 54 header = "BM" header += struct.pack( "IHHI", filesize, 0, 0, 54 ) # The main file header header += struct.pack( "IIIHHIIIIII", 40, width, height, 1, bpp, 0, len( bmpdata ), 3937, 3937, 0, 0 ) # Windows V3 header imageFile = open( filename, 'wb' ) imageFile.write( header + bmpdata ) imageFile.close( ) return True
def execute( width, height, data ): """ Swaps the red and blue channels in an image. :Parameters: width : int The width of the image being converted height : int The height of the image being converted data : string A string containing the data for the image :rtype: tuple :returns: a tuple containing a width, height, and data as a binary string. """ channels = len( data ) // ( width * height ) if channels in ( 3, 4 ): return swapChannels( width, height, data, 0, 2 )
def read( filename ): """ Reads a windows bitmap file. :Parameters: filename : string the name of the file to be read. :rtype: tuple :returns: A tuple ( width, height, data ). Width and height are in pixels, data is a string containing chr(red) + chr(green) + chr(blue) for each pixel. """ imageData = str( ) imageFile = open( filename, 'rb' ) #=========================== READ THE FILE HEADER ======================================== fileHeader = imageFile.read( 14 ) if ( fileHeader[ :2 ] != 'BM' ): log( "Warning: Incorrectly formatted bitmap file" ) dataAddress = struct.unpack( 'I', fileHeader[ 10: ] )[ 0 ] infoHeader = imageFile.read( dataAddress - 14 ) infoHeaderSize = struct.unpack( 'I', infoHeader[ :4 ] )[ 0 ] #============================ READ THE INFOHEADER ========================================== if ( infoHeaderSize == 24 ): # OS/2 V1 Header width, height, colorPlanes, bpp = struct.unpack( "HHHH", infoHeader[ 4:12 ] ) elif ( infoHeaderSize == 40 ): # Windows V3 Header width, height, colorPlanes, bpp, compression = struct.unpack( 'IIHHI', infoHeader[ 4:20 ] ) else: imageFile.close( ) raise ImageFormatError( "Unable to parse header information" ) #======================================================================================== if compression: imageFile.close( ) raise ImageReadError( "Compressed bitmap images are not currently supported" ) if bpp == 4: # 16 colors are reserved for this depth of images. The following are the 20 reserved # Windows colors. The 4 commented out are omitted from this palette. colors = { 0: "\x00\x00\x00", 1: "\x80\x00\x00", 2: "\x00\x80\x00", 3: "\x80\x80\x00", 4: "\x00\x00\x80", 5: "\x80\x00\x80", 6: "\x00\x80\x80", 7: "\xc0\xc0\xc0", #8: "\xc0\xdc\xc0", 9: "\xa6\xca\xf0", 246: "\xff\xfb\xf0", 247: "\xa0\xa0\xa4", 248: "\x80\x80\x80", 249: "\xff\x00\x00", 250: "\x00\xff\x00", 251: "\xff\xff\x00", 252: "\x00\x00\xff", 253: "\x00\xff\xff", 254: "\x00\xff\xff", 255: "\xff\xff\xff" } palette = colors.values( ) data = imageFile.read( ) try: translatedData = str( ) for i in range( len( data ) ): pixels = ord( data[ i ] ) translatedData += palette[ pixels >> 4 ] + palette[ pixels & 0xf ] imageData = [ translatedData[ i*width*3 : (i+1)*width*3 ] for i in range( height ) ] imageData.reverse( ) imageData = ''.join( imageData ) except IndexError: raise ImageReadError( "This image appears to be corrupted." ) elif bpp == 24: eof = width * height * 3 while ( len( imageData ) < eof ): data = imageFile.read( width * 3 ) #throw away the extra \0 padding at the end of a line (if any) padding = ( -width * 3 ) % 4 if padding: imageFile.seek( imageFile.tell( ) + padding ) imageData = data + imageData # swap the red and blue channels, the channels are stored in reverse order # from what we need in the file imageData = swapChannels( width, height, imageData, 0, 2 )[ 2 ] else: imageFile.close( ) raise ImageFormatError( "Images of %d bpp are not supported" % depth ) imageFile.close( ) return ( width, height, imageData )