Skip to content

Instantly share code, notes, and snippets.

@MZachmann
Last active February 6, 2022 23:48
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MZachmann/cd64267ede0f3011e55bd94d33c2ca80 to your computer and use it in GitHub Desktop.
Save MZachmann/cd64267ede0f3011e55bd94d33c2ca80 to your computer and use it in GitHub Desktop.
Python Code For Bitmap Font Translation
# this is a set of functions to convert from a bitmap image and fnt to
# C++ compatible data streams for use by LargeFont
import png
# This requires a variable named font, which is the font name font='Arial11'
Basepath = '/users/mark/my documents/'
font = 'Arial11'
ClipPng(font)
FntToInfo(font)
# -------------------------------------------------------
# read the png file, crop it and write it out as hex
# remove the black lines top and bottom
# crop any black vertical byte-lines from the right
# then write the output as hex with one line header
# -------------------------------------------------------
def ClipPng(font) :
img = png.Reader(Basepath + font + '_0.png')
height,width,imgmap,imgmeta = img.asDirect()
imgdata = [pi for pi in imgmap] # pixel data as array of arrays of pixel values (bytes)
bitdata = []
# convert pixel bytes into bit data
imglen = len(imgdata)
for i in range(imglen) :
bitline = []
bitval = 0x80
bitout = 0
for j in range(len(imgdata[i])) :
if(imgdata[i][j]) :
bitout = bitout | bitval
bitval = bitval >> 1
if(0 == bitval) :
bitline.append(bitout)
bitval = 0x80
bitout = 0
# leftover data?
if(bitval != 0x80) :
bitline.append(bitout)
bitdata.append(bitline)
#
# now bitdata is the imgdata but as bits with the last byte rounded up
fndx = -1;
# crop top and bottom that have no data
for i in range(imglen) :
x = [im for im in bitdata[i] if im != 0] # find non-zero elements
if(len(x)>0) :
fndx = i
break
#
for i in range(imglen) :
x = [im for im in bitdata[imglen-i-1] if im != 0] # find non-zero elements
if(len(x)>0) :
fndendx = imglen-i
break
#
imgfnd = bitdata[fndx:fndendx] # these lines have data
# find the byte to clip at on the right. keep the left
imglen = len(imgfnd)
maxx = 0;
for i in range(imglen) :
rowlen = len(imgfnd[i])
for j in range(rowlen) :
if(imgfnd[i][rowlen-j-1] != 0) :
if(maxx < rowlen-j) :
maxx = rowlen-j
break
#
# clip on the right and create a flat stream of bytes
imgstream = []
for i in range(imglen) :
imgstream.extend(imgfnd[i][0:maxx])
#
# finally we have the clipped data, write it out to file as hex data
outp = open(Basepath + font + '_0.hex', 'w')
outp.write('// Image Size: ' + str(maxx) + 'bytes x ' + str(imglen) + 'lines\n')
outp.write('{\n')
idh = ['0x'+'%02x' % x + ',' for x in imgstream]
for i in range(len(idh)-1) :
outp.write(idh[i])
if(0 == (1+i)%8) :
outp.write('\n')
# write last one without comma
ido = '0x'+'%02x' % imgstream[len(idh)-1]
outp.write(ido)
outp.write('\n};\n')
outp.close()
# -------------------------------------------------------
# convert the fnt file to a list of structures (.info)
# -------------------------------------------------------
def FntToInfo(font) :
textdesc = open(Basepath + font + '.fnt','r')
textdout = open(Basepath + font + '.info', 'w')
dlines = textdesc.readlines()
textdesc.close()
dlines = dlines[4::] # remove header stuff
textdout.write('{ ')
for i in range(len(dlines)) :
x = dlines[i].split('=') # one line split by equals
textdout.write('{ ' + x[1].split(' ')[0] + ', ')
textdout.write(x[2].split(' ')[0] + ', ')
textdout.write(x[4].split(' ')[0] + ', ')
textdout.write(x[6].split(' ')[0] + ', ')
textdout.write(x[8].split(' ')[0] + '}, \n')
#
textdout.write('{ 0,0,0,0,0} }\n')
textdout.close()
#include "application.h"
// this typeface has a list of descriptors and a list of pixel data (bytes)
#include "FontArial11.h"
// THIS IS JUST A PIECE OF THE FILE FOR DEMO PURPOSES. Note the ... below.
// Arial 11 support (actually 16 high)
static struct CharacterInfo Arial11Infos[] =
{ { 33, 676, 1, 2, 5},
{ 36, 519, 7, 1, 9},
{ 37, 73, 12, 1, 14},
{ 40, 666, 3, 1, 5},
{ 41, 670, 3, 1, 5},
{ 42, 625, 5, 0, 6},
{ 43, 551, 7, 1, 9},
...
{ 153, 46, 13, 2, 12},
{ 169, 86, 12, 0, 12},
{ 186, 631, 5, 0, 5},
{ 188, 99, 12, 1, 13},
{ 189, 60, 12, 1, 13},
{ 190, 32, 13, 0, 13},
{ 0,0,0,0,0}
};
// Image Size: 86bytes x 16lines
static uint8_t Arial11Pixels[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
...
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x03,0x0a,0x00,0x00
};
// bitmap is 688x15
LargeFont* CreateFontArial11()
{
struct FontInfo fif = {
Arial11Infos, //struct CharacterInfo* CharacterDescriptors;
Arial11Pixels, //uint8_t* PixelData;
16, //int Height; // number of scan lines
86 //int Width; // width in bytes of the data stream
};
return new LargeFont(&fif);
}
#ifndef FONT_ARIAL11_H
#define FONT_ARIAL11_H
#include "LargeFont.h"
LargeFont* CreateFontArial11(); // this constructor
#endif
// ---------------------------------------------------------------------
// to get a large font...
// first use bitmap font generator (bmfont64) from http://www.angelcode.com/products/bmfont/
// select the chars you want and create a long bitmap (2048x512). Airal, Ansi, 48px high, no kerning, render from truetype
// export the glyph into the A field (8 bits) as png
// doing Export to bitmap will create two files. One metadata i use the fnt suffix for and one glyph data as png
// then run the methods in BmptoHex.py to create data structures from the png and the fnt output
// essentially convert the metadata to a list of CharacterDescriptors
// and crop the bitmap top/bottom/right then print it as hex
// ---------------------------------------------------------------------
// These methods are pretty simple:
// (a) construct the font and then create a 256 character lookup table
// (b) get a pixel from the character glyph
#include "LargeFont.h"
static bool diagnoseFont = false;
// print to the serial device for debugging
static void Serialptf(const char* view)
{
if(diagnoseFont)
{
Serial.println(view);
}
}
// Constructor
LargeFont::LargeFont(const struct FontInfo* pFif)
{
CharacterDescriptors = pFif->CharacterDescriptors;
PixelData = pFif->PixelData;
Width = pFif->Width;
Height = pFif->Height;
CreateCharLookup();
Serialptf("Created Char Lookup A");
}
// not really used by the code
LargeFont::LargeFont(int width, int height, struct CharacterInfo* chardesc, uint8_t* Pixels)
{
CharacterDescriptors = chardesc;
PixelData = Pixels;
Width = width;
Height = height;
CreateCharLookup();
Serialptf("Created Char Lookup B");
}
// for debugging print a CharacterInfo verbose
void PrettyDescriptor(CharacterInfo* pinfo)
{
if(diagnoseFont)
{
char js[220];
sprintf(js, "Descriptor: %c Xpos=%d, Width=%d, Offset=%d, Advance=%d", pinfo->Thechar, pinfo->Xpos, (int)pinfo->Width, (int)pinfo->Offset, (int)pinfo->Advance);
Serialptf(js);
}
}
// create the 256 element lookup table for the CharacterInfo descriptor list (which is sparse)
void LargeFont::CreateCharLookup()
{
Serialptf("Creating Char Lookup");
int i;
for(i=0; i<256; i++)
{
int j = 0;
// the one with Thechar==0 is the end of the vector and the default
while( CharacterDescriptors[j].Thechar != 0)
{
if(i == CharacterDescriptors[j].Thechar)
{
if(diagnoseFont)
{
char js[220];
sprintf(js, "Found descriptor for %c at %d", (char)i, (int)j);
PrettyDescriptor(CharacterDescriptors+j);
Serialptf(js);
}
break;
}
j++;
}
CharacterLookup[i] = &CharacterDescriptors[j];
}
}
// for debugging print to serial the pixel request
void LargeFont::PrintGetPixel(unsigned char c, bool value, int x, int y)
{
if(diagnoseFont)
{
char rs[200];
sprintf(rs, "Calling getpixel: %c at %d:%d was %c", c, x, y, value ? '1' : '0');
Serialptf(rs);
}
}
// Get a pixel for this character c at (x,y)
bool LargeFont::GetPixel(unsigned char c, uint8_t x, uint8_t y)
{
bool rslt = false;
CharacterInfo* cinfo = CharacterLookup[c];
PrettyDescriptor(cinfo);
int dx = x - cinfo->Offset;
int fontWidth = Width; // width in bytes
// now we are within the actual pixel data
if(dx >= 0 && dx < cinfo->Width)
{
int xoff = dx + cinfo->Xpos; // relative to entire bitmap
int byteoff = xoff/8; // which byte of the data
int xremain = xoff - byteoff * 8; // pixels left over
uint8_t bvalue = PixelData[byteoff + y*fontWidth];
if(diagnoseFont)
{
char spr[200];
sprintf(spr, "Pixel data: char=%c at %d,%d xval=%d 0x%02x offset %d at %d remain %d", c, (int)x, (int)y, dx, bvalue, (byteoff + y*fontWidth), xoff, xremain);
Serialptf(spr);
}
if(xremain == 0)
{
rslt = 0x80 & bvalue;
}
else
{
rslt = (0x80 >> xremain) & bvalue;
}
}
PrintGetPixel(c, rslt, x, y);
return rslt;
}
// Get a pixel line for this character c at (x,y) optimized
bool LargeFont::GetPixelLine(unsigned char* pc, unsigned char c, uint8_t y)
{
CharacterInfo* cinfo = CharacterLookup[c];
// int dx = x - cinfo->Offset;
// now we are within the actual pixel data
int cwidth = cinfo->Width;
int xoff = cinfo->Xpos;
int byteoff = xoff / 8;
uint8_t xremain = 0x80 >> (xoff - byteoff * 8);
byteoff += y * Width; // move to nth scan line
for(int dx = 0; dx < cwidth; dx++)
{
// int xoff = dx + cinfo->Xpos; // relative to entire bitmap
// int byteoff = xoff/8; // which byte of the data
// int xremain = xoff - byteoff * 8; // pixels left over
pc[dx] = xremain & PixelData[byteoff];
xremain = xremain >> 1;
if(xremain == 0)
{
xremain = 0x80;
byteoff++;
}
}
return cwidth;
}
int LargeFont::GetWidth(unsigned char c)
{
CharacterInfo* cinfo = CharacterLookup[c];
return cinfo->Width; // how much to move for each character
}
int LargeFont::GetOffset(unsigned char c)
{
CharacterInfo* cinfo = CharacterLookup[c];
return cinfo->Offset; // how much to move for each character
}
int LargeFont::GetAdvance(unsigned char c)
{
CharacterInfo* cinfo = CharacterLookup[c];
return cinfo->Advance; // how much to move for each character
}
int LargeFont::GetHeight()
{
return Height;
}
#ifndef _LARGEFONT_H
#define _LARGEFONT_H
#include "application.h"
// for each character here's what we care about
struct CharacterInfo
{
char Thechar; // the character
int Xpos; // pixel where it starts in the image
unsigned char Width; // width of char glyph in pixels
unsigned char Offset; // left blank space in pixels when drawing it
unsigned char Advance; // total width of char in pixels when printed
};
// this is the structure used to initialize the large font
struct FontInfo
{
struct CharacterInfo* CharacterDescriptors; // array of character descriptors for this font. terminated with Thechar==0 entry
uint8_t* PixelData; // a stream of the pixel data
int Height; // number of scan lines
int Width; // width in bytes of the data stream
};
// This draws the font on screen
class LargeFont
{
private:
// the FontInfo information
struct CharacterInfo* CharacterDescriptors;
uint8_t* PixelData;
int Height; // number of scan lines
int Width; // width in bytes of the data stream
public:
bool GetPixel(unsigned char c, uint8_t x, uint8_t y); // read a pixel at x,y for character c
void PrintGetPixel(unsigned char c, bool value, int x, int y); // for diagnostics print the pixel value
int GetWidth(unsigned char c); // return character width in pixels
int GetHeight(); // get height of font (fixed)
LargeFont(int Width, int Height, struct CharacterInfo* chardesc, uint8_t* Pixels);
LargeFont(const struct FontInfo* pFif);
private:
struct CharacterInfo* CharacterLookup[256]; // lookup table for each character's info
void CreateCharLookup(); // on construction build the lookup table
};
#endif
// only changed GFX code for display
// write a string
size_t Adafruit_GFX::write(uint8_t c) {
if (c == '\n') {
cursor_y += _myFont->GetHeight();
cursor_x = 0;
} else if (c == '\r') {
// skip em
} else {
int ww = _myFont->GetWidth(c);
if( ww > 0)
{
int off = _myFont->GetOffset(c);
drawChar(off+cursor_x, cursor_y, c, textcolor, textbgcolor, textsize);
cursor_x += 1 + _myFont->GetAdvance(c);
}
else
{
cursor_x += _myFont->GetWidth('n'); // use n-space for empty char
}
if (wrap && (cursor_x > (_width - _myFont->GetWidth('@'))))
{
cursor_y += _myFont->GetHeight();
cursor_x = 0;
}
}
return 1;
}
static unsigned char charbufr[100]; // where we put the pixels
// Draw a character
void Adafruit_GFX::drawChar(int16_t x, int16_t y, unsigned char c,
uint16_t color, uint16_t bg, uint8_t size)
{
int cWidth = _myFont->GetWidth(c);
int cHeight = _myFont->GetHeight();
if((x >= _width) || // Clip right
(y >= _height) || // Clip bottom
((x + cWidth * size - 1) < 0) || // Clip left
((y + cHeight * size - 1) < 0)) // Clip top
return;
for (int8_t j = 0; j<cHeight; j++)
{
_myFont->GetPixelLine(charbufr, c, j);
if(size == 1)
{
for (int8_t i=0; i<cWidth; i++ )
{
if (charbufr[i])
{
drawPixel(x+i, y+j, color);
}
else if (bg != color)
{
drawPixel(x+i, y+j, bg);
}
}
}
else
{
for (int8_t i=0; i<cWidth; i++ )
{
if (charbufr[i])
{
fillRect(x+(i*size), y+(j*size), size, size, color);
}
else if (bg != color)
{
fillRect(x+i*size, y+j*size, size, size, bg);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment