#!/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()