#!/usr/bin/python3 # =============================================================== # interpret/display PNG file header information # =============================================================== # --------------------------------------------------------------- # ---- 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 # --------------------------------------------------------------- # ---- PNG color type # --------------------------------------------------------------- def png_color_type(type:int) -> str: match type: case 0: return 'Grayscale' case 2: return 'Truecolor' case 3: return 'Indexed' case 4: return 'Grayscale and alpha' case 6: return 'Truecolor and alpha' return 'unknown' # --------------------------------------------------------------- # ---- PNG pixel_format (color type,channels) # --------------------------------------------------------------- def png_pixel_format(pixfmt:int) -> str: match pixfmt: case 0: return ('Indexed',1) case 1: return ('Grayscale',1) case 2: return ('Grayscale and alpha',2) case 3: return ('Truecolor and alpha',4) return ('unknown',0) # --------------------------------------------------------------- # ---- crc check # --------------------------------------------------------------- def crc_check(bary:bytearray) -> bool: return True # --------------------------------------------------------------- # ---- IHDR header data # --------------------------------------------------------------- def IHDR_header_data(bary:bytearray) -> None: pass # --------------------------------------------------------------- # ---- sRGB header data # --------------------------------------------------------------- def sRGB_header_data(bary:bytearray) -> None: pass # --------------------------------------------------------------- # ---- gAMA header data # --------------------------------------------------------------- def gAMA_header_data(bary:bytearray) -> None: pass # --------------------------------------------------------------- # ---- PHYs header data # --------------------------------------------------------------- def PHYs_header_data(bary:bytearray) -> None: pass # --------------------------------------------------------------- # ---- IDAT header data # --------------------------------------------------------------- def IDAT_header_data(bary:bytearray) -> None: pass # --------------------------------------------------------------- # ---- IEND header data # --------------------------------------------------------------- def IEND_header_data(bary:bytearray) -> None: pass # --------------------------------------------------------------- # ---- display PNG file header and chunks # --------------------------------------------------------------- def display_png_header_and_chunks(file_path,png_bytes:str) -> None: print('---- PNG Header -------------------------------') byt1 = int.from_bytes(png_bytes[0:1],'little') format = png_bytes[1:4].decode() line_end1 = int.from_bytes(png_bytes[4:5],'little') line_end2 = int.from_bytes(png_bytes[5:6],'little') dos_eof_char = int.from_bytes(png_bytes[6:7],'little') unix_lf = int.from_bytes(png_bytes[7:8],'little') print(f'file = {file_path}') print(f'file size = {len(png_bytes)}') print(f'byt1 = {byt1:02x} (hex)') print(f'format = {format}') print(f'line end = {line_end1:02X} {line_end2:02X}') print(f'DOS EOL char = {dos_eof_char:02X}') print(f'Unix LF = {unix_lf:02X}') c_offset = 8 while c_offset < len(png_bytes): print('---- chunk ------------------------------------') start = c_offset + 0 end = c_offset + 4 c_len = int.from_bytes(png_bytes[start:end],'big') start = end end = end + 4 c_type = png_bytes[start:end].decode() start = end end = end + c_len c_data = png_bytes[start:end] start = end end = end + 4 c_crc = int.from_bytes(png_bytes[start:end],'big') print(f'chunk len = {c_len} (data length)') print(f'chunk type = {c_type}') print(f'chunk crc = {c_crc}') c_offset = end # --------------------------------------------------------------- # ---- open PNG 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, # ---- to read the first 1000 bytes? # ---- # ---- def load_PNG_to_array(PNG_file_path): # ---- f = open(PNG_file_path,'rb') # ---- png_bytes = f.read(1000) # ---- close(f) # ---- return png_bytes # ---- # --------------------------------------------------------------- def load_png_to_array(png_file_path:str,) -> bytearray: with open(png_file_path,'rb') as file: png_bytes = file.read() return png_bytes # --------------------------------------------------------------- # ---- main # --------------------------------------------------------------- if __name__ == '__main__': png_file_paths = [ 'mona_lisa_small_a.png' ] for file_path in png_file_paths: print() png_bytes = load_png_to_array(file_path) display_png_header_and_chunks(file_path,png_bytes) print()