How do I create a BMP file with pure Python?
Asked Answered
S

4

15

I need to create a black and white BMP file with pure Python.

I read an article on wikipedia, BMP file format, but I am not good at low level programming and want to fill this gap in my knowledge.

So the question is, how do I create a black and white BMP file having a matrix of pixels? I need to do this with pure Python, not using any modules like PIL. It is just for my education.

Stacy answered 4/1, 2012 at 15:35 Comment(2)
Check out the struct module.Implacable
Also, check this forum thread.Mitchmitchael
D
14

This is a complete answer for monochrome bitmaps.

import math, struct

mult4 = lambda n: int(math.ceil(n/4))*4
mult8 = lambda n: int(math.ceil(n/8))*8
lh = lambda n: struct.pack("<h", n)
li = lambda n: struct.pack("<i", n)

def bmp(rows, w):
    h, wB = len(rows), int(mult8(w)/8)
    s, pad = li(mult4(wB)*h+0x20), [0]*(mult4(wB)-wB)
    s = li(mult4(w)*h+0x20)
    return (b"BM" + s + b"\x00\x00\x00\x00\x20\x00\x00\x00\x0C\x00\x00\x00" +
            lh(w) + lh(h) + b"\x01\x00\x01\x00\xff\xff\xff\x00\x00\x00" +
            b"".join([bytes(row+pad) for row in reversed(rows)]))

For example:

FF XXXXXXXX
81 X......X
A5 X.X..X.X
81 X......X
A5 X.X..X.X
BD X.XXXX.X
81 X......X
FF XXXXXXXX

So, encoding this as a series of rows:

smile = [[0xFF], [0x81], [0xA5], [0x81], [0xA5], [0xBD], [0x81], [0xFF]]

Render it with:

bmp(smile, 8)

Note that it is the programmer's responsibility to ensure that the required number of bytes are present in each row supplied.

The black color is specified in the \xff \xff \xff and the white color is specified in the following \x00 \x00 \x00, should you want to change them.

Doralia answered 16/1, 2014 at 3:50 Comment(0)
B
10

construct is a pure-Python library for parsing and building binary structures, protocols and file formats. It has BMP format support out-of-the-box.

This could be a better approach than hand-crafting it with struct. Besides, you will have a chance to learn a really useful library (which construct certainly is)

Betseybetsy answered 4/1, 2012 at 15:56 Comment(0)
L
7

UPD: Warning! This is not for production use. You can use it for learning purposes, but please be aware that this code was written when I was only starting to learn programming and it has some bugs related to incorrect generation of images of some resolutions. If you're searching for more robust solution for production, consider using graphic libraries such as PIL or see already mentioned library "construct" for low-level bitmap generation.

There is my implementation of 24-bit bitmap in Python 3:

from struct import pack

class Bitmap():
  def __init__(s, width, height):
    s._bfType = 19778 # Bitmap signature
    s._bfReserved1 = 0
    s._bfReserved2 = 0
    s._bcPlanes = 1
    s._bcSize = 12
    s._bcBitCount = 24
    s._bfOffBits = 26
    s._bcWidth = width
    s._bcHeight = height
    s._bfSize = 26+s._bcWidth*3*s._bcHeight
    s.clear()


  def clear(s):
    s._graphics = [(0,0,0)]*s._bcWidth*s._bcHeight


  def setPixel(s, x, y, color):
    if isinstance(color, tuple):
      if x<0 or y<0 or x>s._bcWidth-1 or y>s._bcHeight-1:
        raise ValueError('Coords out of range')
      if len(color) != 3:
        raise ValueError('Color must be a tuple of 3 elems')
      s._graphics[y*s._bcWidth+x] = (color[2], color[1], color[0])
    else:
      raise ValueError('Color must be a tuple of 3 elems')


  def write(s, file):
    with open(file, 'wb') as f:
      f.write(pack('<HLHHL', 
                   s._bfType, 
                   s._bfSize, 
                   s._bfReserved1, 
                   s._bfReserved2, 
                   s._bfOffBits)) # Writing BITMAPFILEHEADER
      f.write(pack('<LHHHH', 
                   s._bcSize, 
                   s._bcWidth, 
                   s._bcHeight, 
                   s._bcPlanes, 
                   s._bcBitCount)) # Writing BITMAPINFO
      for px in s._graphics:
        f.write(pack('<BBB', *px))
      for i in range((4 - ((s._bcWidth*3) % 4)) % 4):
        f.write(pack('B', 0))
          


def main():
  side = 520
  b = Bitmap(side, side)
  for j in range(0, side):
    b.setPixel(j, j, (255, 0, 0))
    b.setPixel(j, side-j-1, (255, 0, 0))
    b.setPixel(j, 0, (255, 0, 0))
    b.setPixel(j, side-1, (255, 0, 0))
    b.setPixel(0, j, (255, 0, 0))
    b.setPixel(side-1, j, (255, 0, 0))
  b.write('file.bmp')


if __name__ == '__main__':
  main()
Leverett answered 9/5, 2018 at 19:34 Comment(4)
The writing code is probably not the most efficient in the world, but it does the job.Sere
The padding calculation is incorrect; it should be (4 - ((s._bcWidth*3) % 4)) % 4). For some reason my edit was rejected.Sere
@Sere , I've just fixed it, but as I see now, reading my code, padding code is totally wrong because it goes after all pixels, but has to be after every single row of pixels. I was writing this code ~2 years ago, when was just learning programming.Leverett
This solution is nice but only works, if side is a multiple of 4. I guess the % 4 needs revision.Bonney
G
3

You have to use Python's struct module to create the binary headers the BMP file will need. Keep the image data itself in a bytearrayobject - bytearray is a little known native python data type that can behave like C strings: have mutable bytes which accept unsigned numbers from 0-255 in each position, still can be printed and used as a string (as an argument to file.write, for example).

Here is a small program that uses struct and other tools to create an image and write it as a TGA file, in pure Python, just as you want to do: http://www.python.org.br/wiki/ImagemTGA (it does not make use of bytearrays, but python array module instead (which is also interesting)

Gulden answered 4/1, 2012 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.