248 lines
8.3 KiB
Python
248 lines
8.3 KiB
Python
|
#!/usr/bin/env python
|
||
|
|
||
|
##@file bmp2hex.py
|
||
|
# @ingroup util
|
||
|
# A script for converting a 1-bit bitmap to HEX for use in an Arduino sketch.
|
||
|
#
|
||
|
# The BMP format is well publicized. The byte order of the actual bitmap is a
|
||
|
# little unusual. The image is stored bottom to top, left to right. In addition,
|
||
|
# The pixel rows are rounded to DWORDS which are 4 bytes long. SO, to convert this
|
||
|
# to left to right, top to bottom, no byte padding. We have to do some calculations
|
||
|
# as we loop through the rows and bytes of the image. See below for more
|
||
|
#
|
||
|
# Usage:
|
||
|
# >>>bmp2hex.py [-i] [-r] [-n] [-d] [-x] [-w <bytes>] [-b <size-bytes>] <infile> <tablename>
|
||
|
#
|
||
|
# @param infile The file to convert.
|
||
|
# @param tablename The name of the table to create
|
||
|
# @param raw "-r", bitmap written as raw table [optional]
|
||
|
# @param invert "-i", to invert image pixel colors [optional]
|
||
|
# @param tablewidth "-w <bytes>, The number of characters for each row of the output table [optional]
|
||
|
# @param sizebytes "-b <bytes>, Bytes = 0, 1, or 2. 0 = auto. 1 = 1-byte for sizes. 2 = 2-byte sizes (big endian) [optional]
|
||
|
# @param named "-n", use a names structure [optional]
|
||
|
## @param double "-d", use double bytes rather than single ones [optional]
|
||
|
# @param xbm "-x", use XBM format (bits reversed in byte) [optional]
|
||
|
# @param version "-v", returns version number
|
||
|
#
|
||
|
# @author Robert Gallup 2016-02
|
||
|
#
|
||
|
# Author: Robert Gallup (bg@robertgallup.com)
|
||
|
# License: MIT Opensource License
|
||
|
#
|
||
|
# Copyright 2016-2018 Robert Gallup
|
||
|
#
|
||
|
|
||
|
import sys, array, os, textwrap, math, random, argparse
|
||
|
|
||
|
class DEFAULTS(object):
|
||
|
STRUCTURE_NAME = 'GFXMeta'
|
||
|
VERSION = '2.3.4'
|
||
|
|
||
|
def main ():
|
||
|
|
||
|
# Default parameters
|
||
|
infile = ""
|
||
|
tablename = ""
|
||
|
tablewidth = 16
|
||
|
sizebytes = 0
|
||
|
invert = False
|
||
|
raw = False
|
||
|
named = False
|
||
|
double = False
|
||
|
xbm = False
|
||
|
version = False
|
||
|
|
||
|
# Set up parser and handle arguments
|
||
|
parser = argparse.ArgumentParser()
|
||
|
# parser.add_argument ("infile", help="The BMP file(s) to convert", type=argparse.FileType('r'), nargs='+', default=['-'])
|
||
|
parser.add_argument ("infile", help="The BMP file(s) to convert", type=argparse.FileType('r'), nargs='*', default=['-'])
|
||
|
parser.add_argument ("-r", "--raw", help="Outputs all data in raw table format", action="store_true")
|
||
|
parser.add_argument ("-i", "--invert", help="Inverts bitmap pixels", action="store_true")
|
||
|
parser.add_argument ("-w", "--width", help="Output table width in hex bytes [default: 16]", type=int)
|
||
|
parser.add_argument ("-b", "--bytes", help="Byte width of BMP sizes: 0=auto, 1, or 2 (big endian) [default: 0]", type=int)
|
||
|
parser.add_argument ("-n", "--named", help="Uses named structure (" + DEFAULTS.STRUCTURE_NAME + ") for data", action="store_true")
|
||
|
# parser.add_argument ("-d", "--double", help="Defines data in 'words' rather than bytes", action="store_true")
|
||
|
parser.add_argument ("-x", "--xbm", help="Uses XBM bit order (low order bit is first pixel of byte)", action="store_true")
|
||
|
parser.add_argument ("-v", "--version", help="Returns the current bmp2hex version", action="store_true")
|
||
|
args = parser.parse_args()
|
||
|
|
||
|
# Required arguments
|
||
|
infile = args.infile
|
||
|
|
||
|
# Options
|
||
|
if args.raw:
|
||
|
raw = args.raw
|
||
|
if args.invert:
|
||
|
invert = args.invert
|
||
|
if args.width:
|
||
|
tablewidth = args.width
|
||
|
if args.bytes:
|
||
|
sizebytes = args.bytes % 3
|
||
|
if args.named:
|
||
|
named = args.named
|
||
|
# if args.double:
|
||
|
# double = args.double
|
||
|
double = False
|
||
|
if args.xbm:
|
||
|
xbm = args.xbm
|
||
|
if args.version:
|
||
|
print ('// bmp2hex version ' + DEFAULTS.VERSION)
|
||
|
|
||
|
# Output named structure, if requested
|
||
|
if (named):
|
||
|
print ('struct ' + DEFAULTS.STRUCTURE_NAME + ' {')
|
||
|
print (' unsigned int width;')
|
||
|
print (' unsigned int height;')
|
||
|
print (' unsigned int bitDepth;')
|
||
|
print (' int baseline;')
|
||
|
print (' ' + getDoubleType(double)[0] + 'pixel_data;')
|
||
|
print ('};')
|
||
|
print ('')
|
||
|
|
||
|
# Do the work
|
||
|
for f in args.infile:
|
||
|
if f == '-':
|
||
|
sys.exit()
|
||
|
bmp2hex(f.name, tablewidth, sizebytes, invert, raw, named, double, xbm)
|
||
|
|
||
|
# Utility function. Return a long int from array (little endian)
|
||
|
def getLONG(a, n):
|
||
|
return (a[n+3] * (2**24)) + (a[n+2] * (2**16)) + (a[n+1] * (2**8)) + (a[n])
|
||
|
|
||
|
# Utility function. Return an int from array (little endian)
|
||
|
def getINT(a, n):
|
||
|
return ((a[n+1] * (2**8)) + (a[n]))
|
||
|
|
||
|
# Reverses pixels in byte
|
||
|
def reflect(a):
|
||
|
r = 0
|
||
|
for i in range(8):
|
||
|
r <<= 1
|
||
|
r |= (a & 0x01)
|
||
|
a >>= 1
|
||
|
return (r)
|
||
|
|
||
|
# Returns as a tuple, the data type and length for double versus short data types
|
||
|
def getDoubleType (d):
|
||
|
if d:
|
||
|
dType = 'uint16_t' + ' *'
|
||
|
dLen = 2
|
||
|
else:
|
||
|
dType = 'uint8_t' + ' *'
|
||
|
dLen = 1
|
||
|
|
||
|
return (dType, dLen)
|
||
|
|
||
|
|
||
|
# Main conversion function
|
||
|
def bmp2hex(infile, tablewidth, sizebytes, invert, raw, named, double, xbm):
|
||
|
|
||
|
# Set up some variables to handle the "-d" option
|
||
|
(pixelDataType, dataByteLength) = getDoubleType(double)
|
||
|
|
||
|
# Set the table name to the uppercase root of the file name
|
||
|
tablename = os.path.splitext(infile)[0].upper()
|
||
|
|
||
|
# Convert tablewidth to characters from hex bytes
|
||
|
tablewidth = int(tablewidth) * 6
|
||
|
|
||
|
# Initilize output buffer
|
||
|
outstring = ''
|
||
|
|
||
|
# Open File
|
||
|
fin = open(os.path.expanduser(infile), "rb")
|
||
|
uint8_tstoread = os.path.getsize(os.path.expanduser(infile))
|
||
|
valuesfromfile = array.array('B')
|
||
|
try:
|
||
|
valuesfromfile.fromfile(fin, uint8_tstoread)
|
||
|
finally:
|
||
|
fin.close()
|
||
|
|
||
|
# Get bytes from file
|
||
|
values=valuesfromfile.tolist()
|
||
|
|
||
|
# Exit if it's not a Windows BMP
|
||
|
if ((values[0] != 0x42) or (values[1] != 0x4D)):
|
||
|
sys.exit ("Error: Unsupported BMP format. Make sure your file is a Windows BMP.")
|
||
|
|
||
|
# Calculate width, heigth
|
||
|
dataOffset = getLONG(values, 10) # Offset to image data
|
||
|
pixelWidth = getLONG(values, 18) # Width of image
|
||
|
pixelHeight = getLONG(values, 22) # Height of image
|
||
|
bitDepth = getINT (values, 28) # Bits per pixel
|
||
|
dataSize = getLONG(values, 34) # Size of raw data
|
||
|
|
||
|
# Calculate line width in bytes and padded byte width (each row is padded to 4-byte multiples)
|
||
|
byteWidth = int(math.ceil(float(pixelWidth * bitDepth)/8.0))
|
||
|
paddedWidth = int(math.ceil(float(byteWidth)/4.0)*4.0)
|
||
|
|
||
|
# For auto (sizebytes = 0), set sizebytes to 1 or 2, depending on size of the bitmap
|
||
|
if (sizebytes==0):
|
||
|
if (pixelWidth>255) or (pixelHeight>255):
|
||
|
sizebytes = 2
|
||
|
else:
|
||
|
sizebytes = 1
|
||
|
|
||
|
# The invert byte is set based on the invert command line flag (but, the logic is reversed for 1-bit files)
|
||
|
invertbyte = 0xFF if invert else 0x00
|
||
|
if (bitDepth == 1):
|
||
|
invertbyte = invertbyte ^ 0xFF
|
||
|
|
||
|
# Output the hex table declaration
|
||
|
# With "raw" output, output just an array of chars
|
||
|
if (raw):
|
||
|
# Output the data declaration
|
||
|
print ('PROGMEM unsigned char const ' + tablename + ' [] = {')
|
||
|
|
||
|
# Output the size of the BMP
|
||
|
if (not (sizebytes%2)):
|
||
|
print ("{0:#04X}".format((pixelWidth>>8) & 0xFF) + ", " + "{0:#04X}".format(pixelWidth & 0xFF) + ", " + \
|
||
|
"{0:#04X}".format((pixelHeight>>8) & 0xFF) + ", " + "{0:#04X}".format(pixelHeight & 0xFF) + ",")
|
||
|
else:
|
||
|
print ("{0:#04X}".format(pixelWidth & 0xFF) + ", " + "{0:#04X}".format(pixelHeight & 0xFF) + ",")
|
||
|
|
||
|
elif (named):
|
||
|
print ('PROGMEM ' + getDoubleType(double)[0] + ' const ' + tablename + '_PIXELS[] = {')
|
||
|
|
||
|
elif (xbm):
|
||
|
print ('#define ' + tablename + '_width ' + str(pixelWidth))
|
||
|
print ('#define ' + tablename + '_height ' + str(pixelHeight))
|
||
|
print ('PROGMEM ' + getDoubleType(double)[0] + ' const ' + tablename + '_bits[] = {')
|
||
|
|
||
|
else:
|
||
|
print ('PROGMEM const struct {')
|
||
|
print (' unsigned int width;')
|
||
|
print (' unsigned int height;')
|
||
|
print (' unsigned int bitDepth;')
|
||
|
print (' ' + pixelDataType + 'pixel_data[{0}];'.format(byteWidth * pixelHeight / dataByteLength))
|
||
|
print ('} ' + tablename + ' = {')
|
||
|
print ('{0}, {1}, {2}, {{'.format(pixelWidth, pixelHeight, bitDepth))
|
||
|
|
||
|
# Generate HEX bytes for pixel data in output buffer
|
||
|
try:
|
||
|
for i in range(pixelHeight):
|
||
|
for j in range (byteWidth):
|
||
|
ndx = dataOffset + ((pixelHeight-1-i) * paddedWidth) + j
|
||
|
v = values[ndx] ^ invertbyte
|
||
|
if (xbm):
|
||
|
v = reflect(v)
|
||
|
# print ("{0:#04x}".format(v))
|
||
|
outstring += "{0:#04x}".format(v) + ", "
|
||
|
|
||
|
# Wrap the output buffer. Print. Then, finish.
|
||
|
finally:
|
||
|
outstring = textwrap.fill(outstring[:-2], tablewidth)
|
||
|
print (outstring)
|
||
|
|
||
|
if (named):
|
||
|
print ('};')
|
||
|
print (DEFAULTS.STRUCTURE_NAME + ' const ' + tablename + ' = {{{0}, {1}, {2}, 0, '.format(pixelWidth, pixelHeight, bitDepth) + \
|
||
|
pixelDataType + tablename + "_PIXELS};\n\n")
|
||
|
else:
|
||
|
if (not (raw or xbm)):
|
||
|
print ("}")
|
||
|
print ("};")
|
||
|
|
||
|
|
||
|
# Only run if launched from commandline
|
||
|
if __name__ == '__main__': main()
|