14

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.

Greenonline
  • 1,330
  • 8
  • 23
  • 31
Taras Bilynskyi
  • 1,403
  • 3
  • 10
  • 9

4 Answers4

13

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.

LionKimbro
  • 777
  • 6
  • 16
9

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)

Joel Sjögren
  • 2,010
  • 1
  • 13
  • 12
Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
6

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()
Elazar Leibovich
  • 32,750
  • 33
  • 122
  • 169
Pavel Shishmarev
  • 1,335
  • 9
  • 24
  • The writing code is probably not the most efficient in the world, but it does the job. – minexew May 31 '18 at 18:40
  • The padding calculation is incorrect; it should be `(4 - ((s._bcWidth*3) % 4)) % 4)`. For some reason my edit was rejected. – minexew Sep 14 '18 at 12:15
  • @minexew , 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. – Pavel Shishmarev Sep 23 '19 at 01:13
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)

jsbueno
  • 99,910
  • 10
  • 151
  • 209