PSE Entertainment Corp
September 06, 2010, 06:49:12 PM *
Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
News: Enjoy PSE Tunes!  They're great!
 
   Home   Help Search Login Register  
Pages: [1]
  Print  
Author Topic: Generate a BMP image file with Python (BMP Writer)  (Read 831 times)
webmaster
Administrator
Newbie
*****
Posts: 18


« on: February 15, 2010, 10:59:18 PM »

As an exercise, I decided to see if I could create an bmp file using pure python.  I know how to write raw bytes to the disk using the very handy struct module so I figured it was just a matter of getting the image specification down.  Turns out I was right.  The BMP format (like most image formats) contains some header bytes followed by data bytes.  A decent explanation of the format can be found on the Wikipedia (check it out!).

This code generates a 200 x 200 bmp file with a random color for each pixel.  It uses a very minimal header.  The output will look something like this:


Enjoy!

Code:
#Some key imports.
#Struct is used to create the actual bytes.
#It is super handy for this type of thing.
import struct, random

#Function to write a bmp file.  It takes a dictionary (d) of
#header values and the pixel data (bytes) and writes them
#to a file.  This function is called at the bottom of the code.
def bmp_write(d,the_bytes):
    mn1 = struct.pack('<B',d['mn1'])
    mn2 = struct.pack('<B',d['mn2'])
    filesize = struct.pack('<L',d['filesize'])
    undef1 = struct.pack('<H',d['undef1'])
    undef2 = struct.pack('<H',d['undef2'])
    offset = struct.pack('<L',d['offset'])
    headerlength = struct.pack('<L',d['headerlength'])
    width = struct.pack('<L',d['width'])
    height = struct.pack('<L',d['height'])
    colorplanes = struct.pack('<H',d['colorplanes'])
    colordepth = struct.pack('<H',d['colordepth'])
    compression = struct.pack('<L',d['compression'])
    imagesize = struct.pack('<L',d['imagesize'])
    res_hor = struct.pack('<L',d['res_hor'])
    res_vert = struct.pack('<L',d['res_vert'])
    palette = struct.pack('<L',d['palette'])
    importantcolors = struct.pack('<L',d['importantcolors'])
    #create the outfile
    outfile = open('bitmap_image.bmp','wb')
    #write the header + the_bytes
    outfile.write(mn1+mn2+filesize+undef1+undef2+offset+headerlength+width+height+\
                  colorplanes+colordepth+compression+imagesize+res_hor+res_vert+\
                  palette+importantcolors+the_bytes)
    outfile.close()

###################################    
def main():
    #Here is a minimal dictionary with header values.
    #Of importance is the offset, headerlength, width,
    #height and colordepth.
    #Edit the width and height to your liking.
    #These header values are described in the bmp format spec.
    #You can find it on the internet. This is for a Windows
    #Version 3 DIB header.
    d = {
        'mn1':66,
        'mn2':77,
        'filesize':0,
        'undef1':0,
        'undef2':0,
        'offset':54,
        'headerlength':40,
        'width':200,
        'height':200,
        'colorplanes':0,
        'colordepth':24,
        'compression':0,
        'imagesize':0,
        'res_hor':0,
        'res_vert':0,
        'palette':0,
        'importantcolors':0
        }

    #Function to generate a random number between 0 and 255
    def rand_color():
        x = random.randint(0,255)
        return x

    #Build the byte array.  This code takes the height
    #and width values from the dictionary above and
    #generates the pixels row by row.  The row_mod and padding
    #stuff is necessary to ensure that the byte count for each
    #row is divisible by 4.  This is part of the specification.
    the_bytes = ''
    for row in range(d['height']-1,-1,-1):# (BMPs are L to R from the bottom L row)
        for column in range(d['width']):
            b = rand_color()
            g = rand_color()
            r = rand_color()
            pixel = struct.pack('<BBB',b,g,r)
            the_bytes = the_bytes + pixel
        row_mod = (d['width']*d['colordepth']/8) % 4
        if row_mod == 0:
            padding = 0
        else:
            padding = (4 - row_mod)
        padbytes = ''
        for i in range(padding):
            x = struct.pack('<B',0)
            padbytes = padbytes + x
        the_bytes = the_bytes + padbytes
        
    #call the bmp_write function with the
    #dictionary of header values and the
    #bytes created above.
    bmp_write(d,the_bytes)

if __name__ == '__main__':
    main()




« Last Edit: May 26, 2010, 10:07:32 PM by webmaster » Report to moderator   Logged
webmaster
Administrator
Newbie
*****
Posts: 18


« Reply #1 on: May 01, 2010, 10:21:59 AM »

I'm pleased to announce that the above BMP writer has found it's way into the very cool GOLLY project.  One of the contributors, Tim Hutton, needed a quick and dirty way to output BMP images without requiring the user to install the Python Image Library (PIL).

From the GOLLY WEB SITE:  (http://golly.sourceforge.net)

"Golly is an open source, cross-platform application for exploring Conway's Game of Life and other cellular automata. The primary authors are Andrew Trevorrow and Tomas Rokicki, with code contributions by Tim Hutton, Dave Greene and Jason Summers."

Right on!
Report to moderator   Logged
riseofthedecays
Newbie
*
Posts: 1


« Reply #2 on: May 24, 2010, 04:19:11 PM »

Hi !

Thanks for this code, it will be very useful for me.
For some reasons, it did not work with my python v3.1.2 :
  • It did not like naming a variable 'bytes' (the IDLE editor detects it as a function name)
  • It didn't recognize '' as an empty bytes object but as an empty string. I replaced it by bytes() and it went fine
You'll find below the modified version of your code that runs ok on my computer.

Code:
#Some key imports.
#Struct is used to create the actual bytes.
#It is super handy for this type of thing.
import struct, random

#Function to write a bmp file.  It takes a dictionary (d) of
#header values and the pixel data (bytes) and writes them
#to a file.  This function is called at the bottom of the code.
def bmp_write(d,byte):
    mn1 = struct.pack('<B',d['mn1'])
    mn2 = struct.pack('<B',d['mn2'])
    filesize = struct.pack('<L',d['filesize'])
    undef1 = struct.pack('<H',d['undef1'])
    undef2 = struct.pack('<H',d['undef2'])
    offset = struct.pack('<L',d['offset'])
    headerlength = struct.pack('<L',d['headerlength'])
    width = struct.pack('<L',d['width'])
    height = struct.pack('<L',d['height'])
    colorplanes = struct.pack('<H',d['colorplanes'])
    colordepth = struct.pack('<H',d['colordepth'])
    compression = struct.pack('<L',d['compression'])
    imagesize = struct.pack('<L',d['imagesize'])
    res_hor = struct.pack('<L',d['res_hor'])
    res_vert = struct.pack('<L',d['res_vert'])
    palette = struct.pack('<L',d['palette'])
    importantcolors = struct.pack('<L',d['importantcolors'])
    #create the outfile
    outfile = open('bitmap_image.bmp','wb')
    #write the header + the bytes
    outfile.write(mn1+mn2+filesize+undef1+undef2+offset+headerlength+width+height+\
                  colorplanes+colordepth+compression+imagesize+res_hor+res_vert+\
                  palette+importantcolors+byte)
    outfile.close()

###################################   
def main():
    #Here is a minimal dictionary with header values.
    #Of importance is the offset, headerlength, width,
    #height and colordepth.
    #Edit the width and height to your liking.
    #These header values are described in the bmp format spec.
    #You can find it on the internet. This is for a Windows
    #Version 3 DIB header.
    d = {
        'mn1':66,
        'mn2':77,
        'filesize':0,
        'undef1':0,
        'undef2':0,
        'offset':54,
        'headerlength':40,
        'width':200,
        'height':200,
        'colorplanes':0,
        'colordepth':24,
        'compression':0,
        'imagesize':0,
        'res_hor':0,
        'res_vert':0,
        'palette':0,
        'importantcolors':0
        }

    #Function to generate a random number between 0 and 255
    def rand_color():
        x = random.randint(0,255)
        return x

    #Build the byte array.  This code takes the height
    #and width values from the dictionary above and
    #generates the pixels row by row.  The row_mod and padding
    #stuff is necessary to ensure that the byte count for each
    #row is divisible by 4.  This is part of the specification.
    byte = bytes()
    for row in range(d['height']-1,-1,-1):# (BMPs are L to R from the bottom L row)
        for column in range(d['width']):
            b = rand_color()
            g = rand_color()
            r = rand_color()
            pixel = struct.pack('<BBB',b,g,r)
            byte = byte + pixel
        row_mod = (d['width']*d['colordepth']/8) % 4
        if row_mod == 0:
            padding = 0
        else:
            padding = (4 - row_mod)
        padbytes = bytes()
        for i in range(padding):
            x = struct.pack('<B',0)
            padbytes = padbytes + x
        byte = byte + padbytes
       
    #call the bmp_write function with the
    #dictionary of header values and the
    #bytes created above.
    bmp_write(d,byte)

if __name__ == '__main__':
    main()

I must tell you how much I'm delighted to have solved this problem, especially because I have been learning python (which is my first language apart from R and a little Matlab at school) for only 2 weeks !!!
Thanks again
Etienne
Report to moderator   Logged
webmaster
Administrator
Newbie
*****
Posts: 18


« Reply #3 on: May 26, 2010, 09:41:27 PM »

Good catch, Etienne!

Indeed, Python 3.x uses "bytes" as new object type.  I wrote this script in Python v2.6 and didn't have that problem.

The original source code has been modified and "bytes" has been replace with "the_bytes" everywhere to make sure it works across python versions.

JDM2
Report to moderator   Logged
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.11 | SMF © 2006-2008, Simple Machines LLC Valid XHTML 1.0! Valid CSS!