solution_248b.py

#!/usr/bin/python3
# ===============================================================
# read a BMP file and paint it into a graphics window
#
# The graphics.py module is slow. That is OK because
# this is an exercise in learning "stuff".
# ===============================================================

import graphics as gr
import user_interface as ui

# ---------------------------------------------------------------
# ---- create graphics window
# ---------------------------------------------------------------

def create_graphics_window(width,height):
    win = gr.GraphWin('BMP Image',width,height)
    win.setBackground('white')
    return win
                                         
# ---------------------------------------------------------------
# ---- open BMP file and read it directly into a byte array
# ---------------------------------------------------------------
    
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

# ---------------------------------------------------------------
# ---- 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

# ---------------------------------------------------------------
# ---- copy pixels to the graphics window
# ---------------------------------------------------------------

def copy_image_to_window(win,width,height,offset,bary):

    # ---- byte array index padding for the end of each row
    padding = calculate_row_padding(width)

    print(f'padding      = {padding}')

    # ---- byte array index
    byt_idx = offset

    # ---- image is upside down and should be reversed
    # ---- thus the -1 increment
    for y in range(height-1,0,-1):

        for x in range(width):

            # ---- RGB colors

            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')

            # ---- plot (draw) pixel

            color = gr.color_rgb(r,g,b)

            win.plotPixel(x,y,color)

            # ---- next pixel
            
            byt_idx += 3

        # ---- end of row - add padding to get
        # ---- the start of next row

        byt_idx += padding

    return

# ---------------------------------------------------------------
# ---- main
# ---------------------------------------------------------------

if __name__ == '__main__':

    ##file_path = 'bmp_all_red.bmp'         # 25 x 27
    file_path = 'mona_lisa_small_a.bmp'     # 100 x 149
    ##file_path = 'mona_lisa_small_b.bmp'   # 149 x 100

    bmp_bytes = load_bmp_to_array(file_path)

    image_height = int.from_bytes(bmp_bytes[22:26],'little')
    image_size   = int.from_bytes(bmp_bytes[34:38],'little')
    image_offset = int.from_bytes(bmp_bytes[10:14],'little')
    image_width  = int.from_bytes(bmp_bytes[18:22],'little')

    print()
    print(f'{len(bmp_bytes)} from file {file_path}')
    print()
    print(f'image offset = {image_offset}')
    print(f'image width  = {image_width}')
    print(f'image height = {image_height}')
    print(f'image size   = {image_size}')
        
    win = create_graphics_window(image_width,image_height)

    copy_image_to_window(win,image_width,image_height,
                         image_offset,bmp_bytes)

    ui.pause()
    print()