#!/usr/bin/python3
# ===============================================================
# interpret/display BMP file header information
# and a few pixels
# ===============================================================
# ---------------------------------------------------------------
# ---- display raw bytes
# ---------------------------------------------------------------
def display_raw_bytes(offset:int,length:int,
bary:bytearray) -> None:
line_cnt = 0 # line byte count
line_max = 10 # max bytes per line
idx = offset # byte array index
while True:
if idx > offset+length:
if idx != 0: print()
break
print(f'{bary[idx]:#x}, ',end='')
idx += 1
if line_cnt > line_max:
print()
line_cnt = 0
continue
line_cnt += 1
# ---------------------------------------------------------------
# ---- display 3 byte pixel
# ---------------------------------------------------------------
def display_3_byte_pixel(r:int,g:int,b:int,idx=None)-> None:
if idx is None:
print(f'r=x{r:02x}, g=x{g:02x}, b=x{b:02x}')
else:
print(f'byte[{idx:03}] r=x{r:02x}, g=x{g:02x}, b=x{b:02x}')
# ---------------------------------------------------------------
# ---- calculate row padding
# ----
# ---- Note: The pixel format is defined by the DIB header or
# ---- extra bit masks. Each row in the Pixel array is
# ---- padded to a multiple of 4 bytes in size.
# ---------------------------------------------------------------
def calculate_row_padding(width):
match (width * 3) % 4:
case 3:
return 1
case 2:
return 2
case 1:
return 3
return 0
# ---------------------------------------------------------------
# ---- display 3 byte (24 bit) pixels
# ----
# ---- Note: The pixel format is defined by the DIB header or
# ---- extra bit masks. Each row in the Pixel array is
# ---- padded to a multiple of 4 bytes in size.
# ----
# ---- input:
# ---- offset offset to the first pixel
# ---- count number if pixels todisplay
# ---- width image width (pixels)
# ---- bary BMP byte array
# ----
# ---------------------------------------------------------------
def display_24bit_pixels(offset:int, count:int, width:int,
bary:bytearray)-> None:
byt_idx = offset # byte array index
row_pix_cnt = 0 # pixels displayed per row count
total_pix_cnt = 0 # total pixels displayed
# byte array index padding for the end of each row
padding = calculate_row_padding(width)
i = 0
count = 54
while total_pix_cnt < count:
# ---- display RGB pixel values
b = int.from_bytes(bary[byt_idx :byt_idx+1],'little')
g = int.from_bytes(bary[byt_idx+1:byt_idx+2],'little')
r = int.from_bytes(bary[byt_idx+2:byt_idx+3],'little')
i += 1
print(f'[{i:02}] ',end='')
display_3_byte_pixel(r,g,b,byt_idx)
total_pix_cnt += 1
row_pix_cnt += 1
byt_idx += 3
# --- end of row?
if row_pix_cnt >= width:
i = 0
row_pix_cnt = 0
byt_idx += padding
print(f'add padding ({padding}) ' +
f'idx={byt_idx} cnt={row_pix_cnt}')
print()
print(f'total_pix_count={total_pix_cnt} count={count}')
# ---------------------------------------------------------------
# ---- DIB header name
# ---------------------------------------------------------------
def dib_header_name(dib_header_size:int) -> str:
match dib_header_size:
case 12:
return 'BITMAPCOREHEADER'
case 64:
return 'OS22XBITMAPHEADER'
case 16:
return 'OS22XBITMAPHEADER'
case 40:
return 'BITMAPINFOHEADER'
case 52:
return 'BITMAPV2INFOHEADER'
case 56:
return 'BITMAPV3INFOHEADER'
case 108:
return 'BITMAPV4HEADER'
case 124:
return 'BITMAPV5HEADER'
return 'unknown'
# ---------------------------------------------------------------
# ---- DIB bits per pixel (palette entry information)
# ---------------------------------------------------------------
def dib_bits_per_pixel(bits_per_pixel:int) -> str:
match bits_per_pixel:
case 1:
return 'monochrome palette. NumColors = 1'
case 4:
return '4bit palletized. NumColors = 16'
case 8:
return '8bit palletized. NumColors = 256'
case 16:
return '16bit RGB. NumColors = 65536'
case 24:
return '24bit RGB. NumColors = 16M'
return 'unknown'
# ---------------------------------------------------------------
# ---- DIB compression method
# ---------------------------------------------------------------
def dib_compression_method(compression_method:int) -> str:
match compression_method:
case 0:
return 'BI_RGB - no compression'
case 1:
return 'BI_RLE8 8bit RLE encoding'
case 2:
return 'BI_RLE4 4bit RLE encoding'
return 'unknown'
# ---------------------------------------------------------------
# ---- DIB important colors
# ---------------------------------------------------------------
def dib_important_colors(important_colors:int) -> str:
if important_colors == 0: return 'all'
return f'{important_colors} important colors'
# ---------------------------------------------------------------
# ---- display BMP file header
# ----
# ---- the common DIB format is the BITMAPINFOHEADER header
# ---------------------------------------------------------------
def display_bmp_header(file_path,bmp_bytes:str) -> None:
print('---- BMP Header -------------------------------')
char1 = chr(int.from_bytes(bmp_bytes[0:1],'little'))
char2 = chr(int.from_bytes(bmp_bytes[1:2],'little'))
width = int.from_bytes(bmp_bytes[18:22],'little')
height = int.from_bytes(bmp_bytes[22:26],'little')
file_size = int.from_bytes(bmp_bytes[2:6],'little')
offset = int.from_bytes(bmp_bytes[10:14],'little')
print(f'file = {file_path}')
print(f'file type = {char1}{char2}')
print(f'width = {width}')
print(f'height = {height}')
print(f'file size = {file_size}')
print(f'pixels offset = {offset}')
print('---- DIB Header -------------------------------')
dib_header_size = int.from_bytes(bmp_bytes[14:18],
'little')
dib_width = int.from_bytes(bmp_bytes[18:22],
'little')
dib_height = int.from_bytes(bmp_bytes[22:26],
'little')
color_planes = int.from_bytes(bmp_bytes[26:28],
'little')
bits_per_pixel = int.from_bytes(bmp_bytes[28:30],
'little')
compression_method = int.from_bytes(bmp_bytes[30:34],
'little')
raw_image_size = int.from_bytes(bmp_bytes[34:38],
'little')
horizontal_resolution = int.from_bytes(bmp_bytes[38:42],
'little',signed=True)
vertical_resolution = int.from_bytes(bmp_bytes[42:46],
'little',signed=True)
number_of_colors = int.from_bytes(bmp_bytes[46:50],
'little')
important_colors = int.from_bytes(bmp_bytes[50:54],
'little')
compression = dib_compression_method(compression_method)
header_name = dib_header_name(dib_header_size)
pixel_bits = dib_bits_per_pixel(bits_per_pixel)
num_important_colors = dib_important_colors(important_colors)
print(f'DIB header size = {dib_header_size}')
print(f'DIB header name = {header_name}')
print(f'DIB width = {dib_width}')
print(f'DIB height = {dib_height}')
print(f'color planes = {color_planes}')
print(f'bits per pixel = {bits_per_pixel}')
print(f' {pixel_bits}')
print(f'compression method = {compression_method}')
print(f' {compression}')
print(f'raw image size = {raw_image_size}')
print(f'horizontal res = {horizontal_resolution}')
print(f'vertical res = {vertical_resolution}')
print(f'number of colors = {number_of_colors}')
print(f'important colors = {important_colors}')
print(f' {num_important_colors}')
if bits_per_pixel < 8:
print('---- Color Table ------------------------------')
print('bits per pixel ({bits_per_pixel}) < 8')
red_intensity = int.from_bytes(bmp_bytes[55:56],'little')
green_intensity = int.from_bytes(bmp_bytes[56:57],'little')
blue_intensity = int.from_bytes(bmp_bytes[57:58],'little')
print(f'red intensity {red_intensity}')
print(f'green intensity {green_intensity}')
print(f'blue intensity {blue_intensity}')
# ---------------------------------------------------------------
# ---- open bmp and read it directly into a byte array
# ----
# ---- It is not necessary to read in the complete file
# ---- if you are only accessing the headers. For example,
# ---- read the first 1000 bytes?
# ----
# ---- def load_bmp_to_array(bmp_file_path):
# ---- f = open(bmp_file_path,'rb')
# ---- bmp_bytes = f.read(1000)
# ---- close(f)
# ---- return bmp_bytes
# ----
# ---------------------------------------------------------------
def load_bmp_to_array(bmp_file_path:str) -> bytearray:
with open(bmp_file_path,'rb') as file:
bmp_bytes = file.read()
return bmp_bytes
# ---------------------------------------------------------------
# ---- main
# ---------------------------------------------------------------
if __name__ == '__main__':
bmp_file_paths = [ 'bmp_all_red.bmp',
##'mona_lisa_1.bmp', 'mona_lisa_original.bmp',
##'bmp_test_file_1.bmp','bmp_test_file_2.bmp',
##'bmp_test_file_3.bmp'
]
for file_path in bmp_file_paths:
print()
bmp_bytes = load_bmp_to_array(file_path)
display_bmp_header(file_path,bmp_bytes)
# ---- assumption: no compression and 24 bit pixels
offset = int.from_bytes(bmp_bytes[10:14],'little')
height = int.from_bytes(bmp_bytes[22:26],'little')
width = int.from_bytes(bmp_bytes[18:22],'little')
pixels_to_display = 30
print()
print(f'---- raw bytes offset={offset}')
print()
display_raw_bytes(offset,pixels_to_display,bmp_bytes)
print()
print(f'---- RGB Pixels offset={offset}')
print()
display_24bit_pixels(offset,pixels_to_display,width,bmp_bytes)
print()