# Converts digits.png to C code
#
# Image requirements:
#   White pixels are considered "lit" on the OLED, black not. Height must be a
#   multiple of 8.
#
#   Each digit is assumed to be connected and needs to be separated by at least one vertical
#   white line. The maximum character width is used for all characters. AAll characters are
#   center-aligned to the maximum width.
#
#   The output is arranged left-to-right, top-to-bottom, where each byte represents a column
#   of 8 pixels. This corresponds to the page addressing mode used by the SSD1306xled library.
#
# Library requirements:
#   Pillow (pip install pillow)
#
from __future__ import print_function
from PIL import Image
import math


InputFile = "digits.png"
OutputHeaderFile = "..\src\digits.h"
OutputCodeFile = "..\src\digits.c"


# Open image and convert to black and white
image = Image.open(InputFile).convert("L")
width, height = image.size

if height % 8 != 0:
  print("Error: height must be a multiple of 8")
  exit(1)

pixels = image.load()



def isEmptyColumn(x):
  for y in range(height):
    if pixels[x, y] != 0:
      return False

  return True


def getColumnValue(x, y):
  value = 0

  for yPos in range(8):
    if pixels[x, y + yPos] != 0:
      value = value | (1<<yPos)

  return value


digits = []
maxWidth = 0



# Scan for start and end positions
x = 0
while x < width:
  # Skip empty lines at the start
  while x < width and isEmptyColumn(x):
    x += 1

  if x >= width:
    break

  startX = x
  while x < width and not isEmptyColumn(x):
    x += 1

  digitWidth = x - startX
  if digitWidth > maxWidth:
    maxWidth = digitWidth

  digits.append({ "start": startX, "stop": x, "width": digitWidth })
  x += 1


if len(digits) != 10:
  print("Error: expected 10 digits, found", len(digits))
  exit(2)

rows = int(math.ceil(height / 8))

print("Found 10 digits")
print("  character width    :", maxWidth)
print("  rows per character :", rows)
print("  total bytes        :", rows * maxWidth * 10)


output = open(OutputHeaderFile, "w")
output.write("#ifndef __Digits\n")
output.write("#define __Digits\n")
output.write("\n")
output.write("#include <avr/pgmspace.h>\n")
output.write("\n")
output.write("#define DigitWidth " + str(maxWidth) + "\n")
output.write("#define DigitHeight " + str(height) + "\n")
output.write("#define DigitRows " + str(rows) + "\n")
output.write("#define DigitBytesPerChar " + str(rows * maxWidth) + "\n")
output.write("\n\n\n")
output.write("extern const uint8_t Digits[] PROGMEM;\n")
output.write("\n")
output.write("#endif\n")
output.close();


output = open(OutputCodeFile, "w")
output.write("#include \"digits.h\"\n")
output.write("\n")
output.write("const uint8_t Digits[] PROGMEM = {\n")


def outputColumn(value, isLast):
  output.write("0x{:02x}".format(value))

  if not isLast:
    output.write(", ")


# Generate byte array for each digit
for digitIndex in range(len(digits)):
  digit = digits[digitIndex]
  output.write("  // " + str(digitIndex) + "\n")

  paddingLeft = int(math.floor((maxWidth - digit["width"]) / 2))
  paddingRight = maxWidth - digit["width"] - paddingLeft


  for rowIndex in range(rows):
    lastRow = digitIndex == len(digits) - 1 and rowIndex == rows - 1
    output.write("  ")

    # Add padding to center align the character
    for paddingIndex in range(paddingLeft):
      outputColumn(0, False)

    # Output one column at a time
    for x in range(digit["start"], digit["stop"]):
      outputColumn(getColumnValue(x, rowIndex * 8), lastRow and paddingRight == 0 and x == digit["stop"] - 1)

    for paddingIndex in range(paddingRight):
      outputColumn(0, lastRow and paddingIndex == paddingRight - 1)

    output.write("\n")
  output.write("\n")
output.write("};\n")
output.close()

print("")
print("Wrote output to", OutputHeaderFile, "and", OutputCodeFile)