#!/usr/bin/env python # -*- coding: utf-8 -*- # # bmp24to14.py # -- updated 27 JUN 2022 # -- changed header type to BITMAPV3INFOHEADER # * allows 16-bit 565 bitmap files to work properly in Windows # -- added output type arguement: -b for bitmat, -r for raw # # Copyright 2022 JonnyMac # # Convert 24-bit bmp file to 16-bit (5:6:5) bitmap or raw files for OLEDs # -- python3 bmp24to16.py BITMAP24.BMP {-b|-r} # # Output file will have _565 appended to name # import sys import os import ctypes H_SIZE = 70 def list2int(l): result = 0 adj = 1 for x in range(0, len(l)): result = result + (l[x] * adj) adj = adj << 8 return result def rgb24to565(c): # c = 0xRRGGBB result = (c & 0xF80000) >> 8 # red result |= (c & 0x00FC00) >> 5 # green result |= (c & 0x0000F8) >> 3 # blue return result def make_header(w, h): # make bitmap header hdr = [0] * H_SIZE hdr[0] = ord('B') hdr[1] = ord('M') i = w * h * 2 + H_SIZE # total file size for j in range(4): hdr[2+j] = i & 0xFF i >>= 8 hdr[10] = H_SIZE # File + Info Size hdr[14] = H_SIZE-14 # Info Size i = w for j in range(4): hdr[18+j] = i & 0xFF i >>= 8 i = -h # flag top-to-bottom for j in range(4): hdr[22+j] = i & 0xFF i >>= 8 hdr[26] = 1 # 1 plane hdr[28] = 16 # 16 bits/pixel hdr[30] = 3 # 5:6:5 color i = w * h * 2 # bytes in image data for j in range(4): hdr[34+j] = i & 0xFF i >>= 8 i = 0xF800 # red mask for j in range(2): hdr[54+j] = i & 0xFF i >>= 8 i = 0x07E0 # green mask for j in range(2): hdr[58+j] = i & 0xFF i >>= 8 i = 0x001F # blue mask for j in range(2): hdr[62+j] = i & 0xFF i >>= 8 return hdr def process_file(args): infile = args[1].strip() print(f"Input file....... {infile}") parts = infile.split('.') if not os.path.exists(infile): print("\nError: Could not open input file.") return -2 if args[2] == "-b": outfile = parts[0] + "_565.bmp" else: outfile = parts[0] + "_565.raw" # read file; convert to list fi = open(infile, 'br') idata = fi.read(os.path.getsize(infile)) fi.close idata = list(idata) # get details from current header ftype = chr(idata[0]) + chr(idata[1]) print(f"File Type........ {ftype}") fsize = list2int(idata[2:6]) print(f"File Size........ {fsize}") ioffset = list2int(idata[10:14]) # input file data offset print(f"Data Offset...... {ioffset}") width = list2int(idata[18:22]) print(f"Image Width...... {width}") height = list2int(idata[22:26]) height = ctypes.c_long(height).value # convert to signed print(f"Image Height..... {height}") bpp = list2int(idata[28:30]) print(f"Bits/Pixel....... {bpp}") # check for issues if width > 96 or height > 64: print("\nError: Image size.") return -3 if height < 0: print("\nError: Input file is top-to-bottom.") return -4 if bpp != 24: if bpp == 16: print("\nFile is 16bpp -- not converted") return -5 else: print("\nError: Color not 24bpp") return -6 # convert file try: fo = open(outfile, 'bw') except: print("\nError: Could not create output file.") return -7 if args[2] == "-b": newhdr = make_header(width, height) for i in range(H_SIZE): # write new header j = newhdr[i] fo.write(j.to_bytes(1, 'little')) newsize = width * height * 2 + H_SIZE else: newsize = width * height * 2 # convert pixels from 24 bits (rgb) to 16 bits (565) # -- rgb file is stored bottom to top # -- 565 file is stored top-to-bottom for line in range(height-1, -1, -1): # reverse to top-to-bottom i = ioffset + (line * width * 3) # starting index of line for col in range(width): c565 = rgb24to565(list2int(idata[i:i+3])) # convert color fo.write(c565.to_bytes(2, 'little')) # write it i += 3 # add column bytes fo.close() print(f"\nOutput file...... {outfile}") print(f"New File Size.... {newsize}") print(f"New Bits/Pixel... 16 (5:6:5)") print("\nDone.") return 0 def main(args): if len(args) != 3: print(f"Error: Invalid number of arguments ({len(args)})") print("-- Use: python3 bmp24to16.py BITMAP24.BMP {-b|-r}") return -1 if args[2] != "-b" and args[2] != "-r": print(f"Error: Invalid output file type arguement ({args[2]})") print("-- Use: python3 bmp24to16.py BITMAP24.BMP {-b|-r}") return -1 return process_file(args) if __name__ == '__main__': sys.exit(main(sys.argv))