252 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			252 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Python
		
	
	
	
| '''
 | |
| 
 | |
|     This script takes in a bitmap and outputs a text file that is a
 | |
|     byte array used in Arduino files.
 | |
| 
 | |
|     It is loosely based on Spark Fun's bmp2array script.
 | |
| 
 | |
|     You'll need python 3.6 (the original use Python 2.7)
 | |
| 
 | |
|     usage: python fourbitbmp2array.py [-v] star.bmp [-o myfile.c]
 | |
|     
 | |
|     Create the bmp file in Gimp by :
 | |
| 
 | |
|     . Remove the alpha channel (if it has one) Layer -> Transparency -> Remove Alpha Channel
 | |
|     . Set the mode to indexed.  Image -> Mode -> Indexed...
 | |
|     . Select Generate optimum palette with 16 colors (max)
 | |
|     . Export the file with a .bmp extension. Options are:
 | |
|         . Run-Length Encoded: not selected
 | |
|         . Compatibility Options: "Do not write color space information" not selected
 | |
|         . There are no Advanced Options available with these settings
 | |
| 
 | |
| 
 | |
|     
 | |
| 
 | |
| '''
 | |
| 
 | |
| import sys
 | |
| import struct
 | |
| import math
 | |
| import argparse
 | |
| import os
 | |
| 
 | |
| debug = None
 | |
| 
 | |
| def debugOut(s):
 | |
|     if debug:
 | |
|         print(s)
 | |
| 
 | |
| # look at arguments
 | |
| parser = argparse.ArgumentParser(description="Convert bmp file to C array")
 | |
| parser.add_argument("-v", "--verbose", help="debug output", action="store_true")
 | |
| parser.add_argument("input", help="input file name")
 | |
| parser.add_argument("-o", "--output", help="output file name")
 | |
| args = parser.parse_args()
 | |
| 
 | |
| if not os.path.exists(args.input):
 | |
|     parser.print_help()
 | |
|     print("The input file {} does not exist".format(args.input))
 | |
|     sys.exit(1)
 | |
| 
 | |
| if args.output == None:
 | |
|     output = os.path.basename(args.input).replace(".bmp", ".c")
 | |
| else:
 | |
|     output = args.output
 | |
| 
 | |
| debug = args.verbose
 | |
| 
 | |
| try:
 | |
|     #Open our input file which is defined by the first commandline argument
 | |
|     #then dump it into a list of bytes
 | |
|     infile = open(args.input,"rb") #b is for binary
 | |
|     contents = bytearray(infile.read())
 | |
|     infile.close()
 | |
| except:
 | |
|     print("could not read input file {}".format(args.input))
 | |
|     sys.exit(1)
 | |
| 
 | |
| # first two bytes should be "BM"
 | |
| upto = 2
 | |
| #Get the size of this image
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| fileSize = struct.unpack("I", bytearray(data))
 | |
| 
 | |
| upto += 4
 | |
| # four bytes are reserved
 | |
| 
 | |
| upto += 4
 | |
| 
 | |
| debugOut("Size of file: {}".format(fileSize[0]))
 | |
| 
 | |
| #Get the header offset amount
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| offset = struct.unpack("I", bytearray(data))
 | |
| 
 | |
| debugOut("Offset: {}".format(offset[0]))
 | |
| upto += 4
 | |
| 
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| headersize = struct.unpack("I", bytearray(data))
 | |
| headerLength = headersize[0]
 | |
| startOfDefinitions = headerLength + upto
 | |
| debugOut("header size: {}, up to {}, startOfDefinitions {}".format(headersize[0], upto, startOfDefinitions))
 | |
| upto += 4
 | |
| 
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("width: {}".format(t[0]))
 | |
| width = t[0]
 | |
| 
 | |
| upto += 4
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("height: {}".format(t[0]))
 | |
| height = t[0]
 | |
| 
 | |
| # 26
 | |
| upto += 4
 | |
| 
 | |
| data = struct.pack("BB", contents[upto], contents[upto+1])
 | |
| t = struct.unpack("H", bytearray(data))
 | |
| debugOut("planes: {}".format(t[0]))
 | |
| 
 | |
| upto = upto + 2
 | |
| data = struct.pack("BB", contents[upto], contents[upto+1])
 | |
| t = struct.unpack("H", bytearray(data))
 | |
| debugOut("bits per pixel: {}".format(t[0]))
 | |
| bitsPerPixel = t[0]
 | |
| 
 | |
| upto = upto + 2
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("biCompression: {}".format(t[0]))
 | |
| 
 | |
| upto = upto + 4
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("biSizeImage: {}".format(t[0]))
 | |
| 
 | |
| upto = upto + 4
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("biXPelsPerMeter: {}".format(t[0]))
 | |
| 
 | |
| upto = upto + 4
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("biYPelsPerMeter: {}".format(t[0]))
 | |
| 
 | |
| upto = upto + 4
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("biClrUsed: {}".format(t[0]))
 | |
| colorsUsed = t
 | |
| 
 | |
| upto = upto + 4
 | |
| data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
| t = struct.unpack("I", bytearray(data))
 | |
| debugOut("biClrImportant: {}".format(t[0]))
 | |
| 
 | |
| upto += 4
 | |
| 
 | |
| debugOut("Upto: {} Number of colors used: {} definitions start at: {}".format(upto, colorsUsed[0],  startOfDefinitions))
 | |
| 
 | |
| #Create color definition array and init the array of color values
 | |
| colorIndex = [] #(colorsUsed[0])
 | |
| for i in range(colorsUsed[0]):
 | |
|     colorIndex.append(0)
 | |
| 
 | |
| #Assign the colors to the array.  upto = 54
 | |
| # startOfDefinitions = upto
 | |
| for i in range(colorsUsed[0]):
 | |
|     upto =  startOfDefinitions + (i * 4)
 | |
|     blue = contents[upto]
 | |
|     green = contents[upto + 1]
 | |
|     red = contents[upto + 2]
 | |
|     # ignore the alpha channel.
 | |
| 
 | |
|     # data = struct.pack("BBBB", contents[upto], contents[upto+1], contents[upto+2], contents[upto+3])
 | |
|     # t = struct.unpack("I", bytearray(data))
 | |
|     # colorIndex[i] = t[0]
 | |
| 
 | |
|     colorIndex[i] = (((red & 0xf8)<<8) + ((green & 0xfc)<<3)+(blue>>3))
 | |
|     debugOut("color at index {0} is {1:04x}, (r,g,b,a) = ({2:02x}, {3:02x}, {4:02x}, {5:02x})".format(i,  colorIndex[i], red, green, blue, contents[upto+3]))
 | |
| 
 | |
| #debugOut(the color definitions
 | |
| # for i in range(colorsUsed[0]):    
 | |
| #     print hex(colorIndex[i])
 | |
| 
 | |
| # perfect, except upside down.
 | |
| 
 | |
| #Make a string to hold the output of our script
 | |
| arraySize = (len(contents) - offset[0]) 
 | |
| outputString = "/* This was generated using a script based on the SparkFun BMPtoArray python script" + '\n'
 | |
| outputString += " See https://github.com/sparkfun/BMPtoArray for more info */" + '\n\n'
 | |
| outputString += "static const uint16_t palette[" + str(colorsUsed[0]) + "] = {";
 | |
| for i in range(colorsUsed[0]): 
 | |
|     # print hexlify(colorIndex[i])
 | |
|     if i % 4 == 0:
 | |
|         outputString += "\n\t"
 | |
|     outputString += "0x{:04x}, ".format(colorIndex[i])
 | |
| 
 | |
| outputString = outputString[:-2]
 | |
| outputString += "\n};\n\n"
 | |
| outputString += "// width is " + str(width) + ", height is " + str(height) + "\n"
 | |
| outputString += "static const uint8_t myGraphic[" + str(arraySize) + "] PROGMEM = {" + '\n'
 | |
| 
 | |
| if bitsPerPixel != 4:
 | |
|     print("Expected 4 bits per pixel; found {}".format(bitsPerPixel))
 | |
|     sys.exit(1)
 | |
|     
 | |
| #Start converting spots to values
 | |
| #Start at the offset and go to the end of the file
 | |
| dropLastNumber = True #(width % 4) == 2 or (width % 4) == 1
 | |
| paddedWidth = int(math.ceil(bitsPerPixel * width / 32.0) * 4)
 | |
| debugOut("array range is {} {} len(contents) is {} paddedWidth is {} width is {}".format(offset[0], fileSize[0], len(contents), paddedWidth, width))
 | |
| 
 | |
| r = 0
 | |
| width = int(width / 2)
 | |
| #for i in range(offset[0], fileSize[0]):                 # close but image is upside down.  Each row is correct but need to swap columns.
 | |
| #for i in range(fileSize[0], offset[0], -1):
 | |
| 
 | |
| for col in range(height-1, -1, -1):
 | |
|     i = 0
 | |
|     for row in range(width):
 | |
|         colorCode1 = contents[row + col*paddedWidth + offset[0]]  
 | |
| 
 | |
|         if r > 0 and r % width == 0:
 | |
|             i = 0
 | |
|             outputString += '\n\n'
 | |
|         elif (i + 1) % 12 == 0 :
 | |
|             outputString += '\n'
 | |
|             i = 0
 | |
|         
 | |
|         #debugOut("cell ({0}, {1})".format(row, col)
 | |
| 
 | |
|         r = r + 1
 | |
|         i = i + 1
 | |
|         outputString += "0x{:02x}, ".format(colorCode1)
 | |
| 
 | |
| 
 | |
|     
 | |
| #Once we've reached the end of our input string, pull the last two
 | |
| #characters off (the last comma and space) since we don't need
 | |
| #them. Top it off with a closing bracket and a semicolon.
 | |
| outputString = outputString[:-2]
 | |
| outputString += "};"
 | |
| 
 | |
| try:
 | |
|     #Write the output string to our output file
 | |
|     outfile = open(output, "w")
 | |
|     outfile.write(outputString)
 | |
|     outfile.close()
 | |
| except:
 | |
|     print("could not write output to file {}".format(output))
 | |
|     sys.exit(1)
 | |
| 
 | |
| debugOut("{} complete".format(output))
 | |
| debugOut("Copy and paste this array into a image.h or other header file")
 | |
| 
 | |
| if not debug:
 | |
|     print("Completed; the output is in {}".format(output))
 |