diff options
Diffstat (limited to 'docs/release/scripts/build/png2bdc.py')
-rw-r--r-- | docs/release/scripts/build/png2bdc.py | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/docs/release/scripts/build/png2bdc.py b/docs/release/scripts/build/png2bdc.py new file mode 100644 index 00000000000..a1be5c552d0 --- /dev/null +++ b/docs/release/scripts/build/png2bdc.py @@ -0,0 +1,391 @@ +#!/usr/bin/env python +## +## license:BSD-3-Clause +## copyright-holders:Aaron Giles, Andrew Gardner +## **************************************************************************** +## +## png2bdc.c +## +## Super-simple PNG to BDC file generator +## +## **************************************************************************** +## +## Format of PNG data: +## +## Multiple rows of characters. A black pixel means "on". All other colors +## mean "off". Each row looks like this: +## +## * 8888 *** * +## * 4444 * * ** +## * 2222 * * * +## * 1111 * * * +## * * * * +## ** *** *** +## * +## * +## +## ****** **** +## +## The column of pixels on the left-hand side (column 0) indicates the +## character cell height. This column must be present on each row and +## the height must be consistent for each row. +## +## Protruding one pixel into column 1 is the baseline indicator. There +## should only be one row with a pixel in column 1 for each line, and +## that pixel row index should be consistent for each row. +## +## In columns 2-5 are a 4-hex-digit starting character index number. This +## is encoded as binary value. Each column is 4 pixels tall and represents +## one binary digit. The character index number is the unicode character +## number of the first character encoded in this row; subsequent +## characters in the row are at increasing character indices. +## +## Starting in column 6 and beyond are the actual character bitmaps. +## Below them, in the second row after the last row of the character, +## is a solid line that indicates the width of the character, and also +## where the character bitmap begins and ends. +## +## *************************************************************************** + +## +## Python note: +## This is a near-literal translation of the original C++ code. As such there +## are some very non-pythonic things done throughout. The conversion was done +## this way so as to insure compatibility as much as possible given the small +## number of test cases. +## + +import os +import png +import sys + +if sys.version_info >= (3,): + def b2p(v): + return bytes([v]) +else: + def b2p(v): + return chr(v) + + +######################################## +## Helper classes +######################################## +class RenderFontChar: + """ + Contains information about a single character in a font. + """ + + def __init__(self): + """ + """ + self.width = 0 # width from this character to the next + self.xOffs = 0 # X offset from baseline to top,left of bitmap + self.yOffs = 0 # Y offset from baseline to top,left of bitmap + self.bmWidth = 0 # width of bitmap + self.bmHeight = 0 # height of bitmap + self.bitmap = None # pointer to the bitmap containing the raw data + + +class RenderFont: + """ + Contains information about a font + """ + + def __init__(self): + self.height = 0 # height of the font, from ascent to descent + self.yOffs = 0 # y offset from baseline to descent + self.defChar = -1 # default character for glyphs not present + self.chars = list() # array of characters + for i in range(0, 65536): + self.chars.append(RenderFontChar()) + + + +######################################## +## Helper functions +######################################## +def pixelIsSet(value): + return (value & 0xffffff) == 0 + + +def renderFontSaveCached(font, filename, length64, hash32): + """ + """ + fp = open(filename, "wb") + if not fp: + return 1 + + # Write the header + numChars = 0 + for c in font.chars: + if c.width > 0: + numChars += 1 + + CACHED_CHAR_SIZE = 16 + CACHED_HEADER_SIZE = 32 + + try: + fp.write(b'b') + fp.write(b'd') + fp.write(b'c') + fp.write(b'f') + fp.write(b'n') + fp.write(b't') + fp.write(b2p(1)) + fp.write(b2p(0)) + fp.write(b2p(length64 >> 56 & 0xff)) + fp.write(b2p(length64 >> 48 & 0xff)) + fp.write(b2p(length64 >> 40 & 0xff)) + fp.write(b2p(length64 >> 32 & 0xff)) + fp.write(b2p(length64 >> 24 & 0xff)) + fp.write(b2p(length64 >> 16 & 0xff)) + fp.write(b2p(length64 >> 8 & 0xff)) + fp.write(b2p(length64 >> 0 & 0xff)) + fp.write(b2p(hash32 >> 24 & 0xff)) + fp.write(b2p(hash32 >> 16 & 0xff)) + fp.write(b2p(hash32 >> 8 & 0xff)) + fp.write(b2p(hash32 >> 0 & 0xff)) + fp.write(b2p(numChars >> 24 & 0xff)) + fp.write(b2p(numChars >> 16 & 0xff)) + fp.write(b2p(numChars >> 8 & 0xff)) + fp.write(b2p(numChars >> 0 & 0xff)) + fp.write(b2p(font.height >> 8 & 0xff)) + fp.write(b2p(font.height >> 0 & 0xff)) + fp.write(b2p(font.yOffs >> 8 & 0xff)) + fp.write(b2p(font.yOffs >> 0 & 0xff)) + fp.write(b2p(font.defChar >> 24 & 0xff)) + fp.write(b2p(font.defChar >> 16 & 0xff)) + fp.write(b2p(font.defChar >> 8 & 0xff)) + fp.write(b2p(font.defChar >> 0 & 0xff)) + + # Write a blank table at first (?) + charTable = [0]*(numChars * CACHED_CHAR_SIZE) + for i in range(numChars * CACHED_CHAR_SIZE): + fp.write(b2p(charTable[i])) + + # Loop over all characters + tableIndex = 0 + + for i in range(len(font.chars)): + c = font.chars[i] + if c.width == 0: + continue + + if c.bitmap: + dBuffer = list() + accum = 0 + accbit = 7 + + # Bit-encode the character data + for y in range(0, c.bmHeight): + src = None + desty = y + font.height + font.yOffs - c.yOffs - c.bmHeight + if desty >= 0 and desty < font.height: + src = c.bitmap[desty] + for x in range(0, c.bmWidth): + if src is not None and src[x] != 0: + accum |= 1 << accbit + accbit -= 1 + if accbit+1 == 0: + dBuffer.append(accum) + accum = 0 + accbit = 7 + + # Flush any extra + if accbit != 7: + dBuffer.append(accum) + + # Write the data + for j in range(len(dBuffer)): + fp.write(b2p(dBuffer[j])) + + destIndex = tableIndex * CACHED_CHAR_SIZE + charTable[destIndex + 0] = i >> 24 & 0xff + charTable[destIndex + 1] = i >> 16 & 0xff + charTable[destIndex + 2] = i >> 8 & 0xff + charTable[destIndex + 3] = i >> 0 & 0xff + charTable[destIndex + 4] = c.width >> 8 & 0xff + charTable[destIndex + 5] = c.width >> 0 & 0xff + charTable[destIndex + 8] = c.xOffs >> 8 & 0xff + charTable[destIndex + 9] = c.xOffs >> 0 & 0xff + charTable[destIndex + 10] = c.yOffs >> 8 & 0xff + charTable[destIndex + 11] = c.yOffs >> 0 & 0xff + charTable[destIndex + 12] = c.bmWidth >> 8 & 0xff + charTable[destIndex + 13] = c.bmWidth >> 0 & 0xff + charTable[destIndex + 14] = c.bmHeight >> 8 & 0xff + charTable[destIndex + 15] = c.bmHeight >> 0 & 0xff + tableIndex += 1 + + # Seek back to the beginning and rewrite the table + fp.seek(CACHED_HEADER_SIZE, 0) + for i in range(numChars * CACHED_CHAR_SIZE): + fp.write(b2p(charTable[i])) + + fp.close() + return 0 + + except: + print(sys.exc_info[1]) + return 1 + + +def bitmapToChars(pngObject, font): + """ + Convert a bitmap to characters in the given font + """ + # Just cache the bitmap into a list of lists since random access is paramount + bitmap = list() + width = pngObject.asRGBA8()[0] + height = pngObject.asRGBA8()[1] + rowGenerator = pngObject.asRGBA8()[2] + for row in rowGenerator: + cRow = list() + irpd = iter(row) + for r,g,b,a in zip(irpd, irpd, irpd, irpd): + cRow.append(a << 24 | r << 16 | g << 8 | b) + bitmap.append(cRow) + + rowStart = 0 + while rowStart < height: + # Find the top of the row + for i in range(rowStart, height): + if pixelIsSet(bitmap[rowStart][0]): + break + rowStart += 1 + if rowStart >= height: + break + + # Find the bottom of the row + rowEnd = rowStart + 1 + for i in range(rowEnd, height): + if not pixelIsSet(bitmap[rowEnd][0]): + rowEnd -= 1 + break + rowEnd += 1 + + # Find the baseline + baseline = rowStart + for i in range(rowStart, rowEnd+1): + if pixelIsSet(bitmap[baseline][1]): + break + baseline += 1 + if baseline > rowEnd: + sys.stderr.write("No baseline found between rows %d-%d\n" % (rowStart, rowEnd)) + break + + # Set or confirm the height + if font.height == 0: + font.height = rowEnd - rowStart + 1 + font.yOffs = baseline - rowEnd + else: + if font.height != (rowEnd - rowStart + 1): + sys.stderr.write("Inconsistent font height at rows %d-%d\n" % (rowStart, rowEnd)) + break + if font.yOffs != (baseline - rowEnd): + sys.stderr.write("Inconsistent baseline at rows %d-%d\n" % (rowStart, rowEnd)) + break + + # decode the starting character + chStart = 0 + for x in range(0, 4): + for y in range(0, 4): + chStart = (chStart << 1) | pixelIsSet(bitmap[rowStart+y][2+x]) + + # Print debug info + # print("Row %d-%d, baseline %d, character start %X" % (rowStart, rowEnd, baseline, chStart)) + + # scan the column to find characters + colStart = 0 + while colStart < width: + ch = RenderFontChar() + + # Find the start of the character + for i in range(colStart, width): + if pixelIsSet(bitmap[rowEnd+2][colStart]): + break + colStart += 1 + if colStart >= width: + break + + # Find the end of the character + colEnd = colStart + 1 + for i in range(colEnd, width): + if not pixelIsSet(bitmap[rowEnd+2][colEnd]): + colEnd -= 1 + break + colEnd += 1 + + # Skip char which code is already registered + if ch.width <= 0: + # Print debug info + # print " Character %X - width = %d" % (chStart, colEnd - colStart + 1) + + # Plot the character + ch.bitmap = list() + for y in range(rowStart, rowEnd+1): + ch.bitmap.append(list()) + for x in range(colStart, colEnd+1): + if pixelIsSet(bitmap[y][x]): + ch.bitmap[-1].append(0xffffffff) + else: + ch.bitmap[-1].append(0x00000000) + + # Set the character parameters + ch.width = colEnd - colStart + 1 + ch.xOffs = 0 + ch.yOffs = font.yOffs + ch.bmWidth = len(ch.bitmap[0]) + ch.bmHeight = len(ch.bitmap) + + # Insert the character into the list + font.chars[chStart] = ch + + # Next character + chStart += 1 + colStart = colEnd + 1 + + # Next row + rowStart = rowEnd + 1 + + # Return non-zero if we errored + return rowStart < height + + + +######################################## +## Main +######################################## +def main(): + if len(sys.argv) < 3: + sys.stderr.write("Usage:\n%s <input.png> [<input2.png> [...]] <output.bdc>\n" % sys.argv[0]) + return 1 + bdcName = sys.argv[-1] + + font = RenderFont() + for i in range(1, len(sys.argv)-1): + filename = sys.argv[i] + if not os.path.exists(filename): + sys.stderr.write("Error attempting to open PNG file.\n") + return 1 + + pngObject = png.Reader(filename) + try: + pngObject.validate_signature() + except: + sys.stderr.write("Error reading PNG file.\n") + return 1 + + error = bitmapToChars(pngObject, font) + if error: + return 1 + + error = renderFontSaveCached(font, bdcName, 0, 0) + return error + + + +######################################## +## Program entry point +######################################## +if __name__ == "__main__": + sys.exit(main()) |