Reading binary files

Now that we're producing beautiful Mandelbrot images, we should see about reading those BMPs back with Python. We're not going to write a full-blown BMP reader, although that would be an interesting exercise. We'll just make a simple function to determine the image dimension in pixels from a BMP file. We'll add the code into bmp.py:

def dimensions(filename):
"""Determine the dimensions in pixels of a BMP image.

Args:
filename: The filename of a BMP file.

Returns:
A tuple containing two integers with the width
and height in pixels.

Raises:
ValueError: If the file was not a BMP file.
OSError: If there was a problem reading the file.
"""

with open(filename, 'rb') as f:
magic = f.read(2)
if magic != b'BM':
raise ValueError("{} is not a BMP file".format(filename))

f.seek(18)
width_bytes = f.read(4)
height_bytes = f.read(4)

return (_bytes_to_int32(width_bytes),
_bytes_to_int32(height_bytes))

Of course, we use a with-statement to manage the file, so we don't have to worry about it being properly closed. Inside the with-block we perform a simple validation check by looking for the two first magic bytes that we expect in a BMP file. If they’re not present, we raise a ValueError which will, of course, cause the context manager to close the file.

Looking back at our BMP writer, we can determine that the image dimensions are stored exactly 18 bytes from the beginning of the file. We seek() to that location and use the read() method to read two chunks of four bytes each for the two 32-bit integers which represent the dimensions. Because we opened the file in binary mode, read() returns a bytes object. We pass each of these two bytes objects to another implementation detail function called _bytes_to_int32() which assembles them back into an integer. The two integers, representing image width and height, are returned as a tuple.

The _bytes_to_int32() function uses << (bitwise left-shift) and | (bitwise-or), together with indexing of the bytes object, to reassemble the integer. Note that indexing into a bytes object returns an integer:

def _bytes_to_int32(b):
"""Convert a bytes object containing four bytes into an integer."""
return b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24)

If we use our new reader code, we can see that it does indeed read the correct values:

>>> bmp.dimensions("mandel.bmp")
(448, 256)
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset