Creating new fonts for Pokitto - part 2 - exporting from font editor to a C file

Inverted meaning reversed?

If so, just throwing this function around all the values should suffice:

// Variables in constexpr functions are a C++14 feature.
// This could be rewritten to work in C++11 where variables
// in constexpr functions aren't allowed,
// but it would be a lot harder to read.
#if defined(__cplusplus) && (__cplusplus >= 201402L)
constexpr
#endif
unsigned char reverse_bits(unsigned char value)
{
	// Swap nibbles
	const unsigned char value0 = (((value & 0xF0) >> 4) | ((value & 0x0F) << 4));

	// Swap pairs
	const unsigned char value1 = (((value0 & 0xCC) >> 2) | ((value0 & 0x33) << 2));
	
	// Swap bits
	const unsigned char value2 = (((value1 & 0xAA) >> 1) | ((value1 & 0x55) << 1));
	
	return value2;
}

If the array itself is marked constexpr, then it’s (more or less) guaranteed that this function will be evaluated at compile time.

1 Like

Imho, waste of resources to reverse bits at runtime.

Would be better to make a python script to do it

Ah. I just.now read your proposal properly

Edit: no you cant do this because existing fonts would break. You’d have to implement this on the print level of PokittoLib

Surely it only needs to be done for the fonts in which the reversed bits are a problem, not all fonts?

If that’s not many, a simple regex replace from a text editor ought do the job easily.

(0x[0-9A-Za-z]+)bit_reverse($1)

It’s not as robust as writing a proper script, but if anyone wants a duct-tape solution for the time being, it should suffice. (I expect someone using Linux could turn that into a bash script that uses grep.)


@HomineLudens, does the following work for you?
(Assuming you’ve still got that old code from several comments ago.)

(I made this exactly as described, in about 30 seconds using a text editor’s regex replace tool.)

@Pharap your code have a typo (reverse_bits vs bit_reverse :smiley: ) but seems not to fix the problem:

So I made a test creating smaller font as suggested by @lucentbeam , and yes with small fonts all works as expected.

2 Likes

I am playing all the right notes, but not necessarily in the right order.

Considering how tired I was, I’m surprised that’s all that’s wrong with it to be honest.

They should all be reverse_bits since starting with a verb usually makes more sense. I’ve updated the gist.

Then there’s probably more to it than the bits being reversed.

I had a quick look at the code and from what I can tell, assuming what I’m looking at is the right function, there’s actually supposed to be 5 starter bytes: width, height, 2 that bufferChar is skipping over and then the ‘width in bytes’, whatever that means.

But that’s as far as I’ll be looking into it tonight.

1 Like

@HomineLudens Did you resolve this finally? I might need this also for implementing the outline fonts.

Unfortunately no, I can just confirm the problem rise when font height is above some value. The font format have some quite of oddities that make it quite hard to understand how it works.

What is that value?

Anything above 8 I suppose. I just make a couple of test with a big (over 12 pixel height) that didn’t work and the try with a small one (5 pixel if I’m not wrong) that instead worked.

ok, 8 pixels migth be enough in lo-res mode.

Just for the record… :slight_smile:
my favourite font / image to c/c++ converter: About project | LCD Image Converter
Everything can be configured in this app, extreme flexible!

1 Like

I’ve been using this python3 script (originally by @drummyfish, some modifications by me) to make custom fonts. The script takes a .png charmap and prints the font data which you can copy to your my_font.h file.

fontconv.py
#!/usr/bin/env python3

# Convert bitmap font to array. Usage:
#
# python3 fontconv.py charmap.png [only caps] [monospace width]
#
# original author: Miloslav Ciz
# modified by: jpfli
# license: CC0 1.0

import sys
from PIL import Image

# Configuration
firstChar = ord(' ')
lastChar = ord('\x7f')
lastCharCaps = ord(']')
indent = 4

# Command line parameters
if len(sys.argv) < 2:
  print("Usage: "+sys.argv[0]+" charmap.png [only caps] [monospace width]")
  exit(0)
onlyCaps = len(sys.argv) >= 3 and int(sys.argv[2]) != 0
monospaceWidth = 0 if len(sys.argv) < 4 else int(sys.argv[3])


image = Image.open(sys.argv[1])
pixels = image.load()

numChars = 1 + (lastCharCaps if onlyCaps else lastChar) - firstChar
charDiv = (image.size[0] + 1) // numChars
fontH = image.size[1]
fontHRound = ((fontH + 7)//8)*8

startCol = charDiv
endCol = 0

charArray = []
for i in range(numChars):
  columns = []
  for x in range(charDiv):
    bitcol = [0]*fontHRound
    for y in range(fontH):
      bitcol[y] = 1 if pixels[i*charDiv + x, y][0] == 0 else 0

    if sum(bitcol) != 0:
      if x + 1 > endCol:
        endCol = x + 1
      if x < startCol:
        startCol = x

    column = []
    while len(bitcol) >= 8:
      column.append(sum([bitcol[j]<<j for j in range(8)]))
      bitcol = bitcol[8:]
    columns.append(column)

  charArray.append(columns)

fontW = monospaceWidth if monospaceWidth > 0 else endCol - startCol

print(' '*indent + str(fontW) + ", " + str(fontH) + ", " + str(firstChar) + ", " + ('1' if onlyCaps else '0') + ", // width, height, start char, only caps")

for i in range(len(charArray)):
  columns = charArray[i]
  if len(columns) < fontW:
    columns += [[0]]*(fontW - len(columns))

  charW = monospaceWidth
  if monospaceWidth > 0:
    # strip empty columns from left side
    columns = columns[startCol:] + [[0]*(fontHRound//8)]*startCol
  else:
    nonZeroIndices = [index for index,value in enumerate([sum(col) for col in columns]) if value != 0]
    if len(nonZeroIndices) > 0:
      tmp = columns
      # strip empty columns from left side
      columns = columns[nonZeroIndices[0]:] + [[0]*(fontHRound//8)]*nonZeroIndices[0]
      charW = 1 + nonZeroIndices[-1] - nonZeroIndices[0]
    else:
      charW = fontW//2

  line = ""
  for x in range(fontW):
    for col in columns[x]:
      line += "0x" + format(col, '02X') + ", "

  line = "0x" + format(charW, '02X') + ", " + line
  line += " // " + str(firstChar + i) + ": '" + chr(firstChar + i) + "'"

  print(' '*indent + line)

Example charmap:
font-charmap


What comes to the problem with font height, I took a look in PokittoLib and there is a height limit of 16 pixels in the draw function bufferChar(x, y, index) (in PokittoFrameBuffer.cpp). But if you are having problems even with smaller font heights then there is some other problem.

Anyway, below is a version of the bufferChar(x, y, index) function that can handle any font height.

PokittoFrameBuffer.cpp bufferChar(x, y, index) function
int Display::bufferChar(int16_t x, int16_t y, uint16_t index){
    uint8_t w = font[0];
    uint8_t h = font[1];

    uint8_t hbytes=((h + 7)>>3); //GLCD fonts are arranged w+1 times h/8 bytes

    const uint8_t* bitmap = font + 4 + index*(1 + w*hbytes); //add an offset to the pointer

    int8_t i, j;
    uint8_t charW = *bitmap; //first byte of char is char width
    ++bitmap;
    // GLCD fonts are arranged LSB = topmost pixel of char, so its easy to just shift through the column
    uint32_t bitcolumn; //16 bits for 2x8 bit high characters

    if( fontSize != 2 ) fontSize = 1;

    void (*drawPixelFG)(int16_t,int16_t, uint8_t) = &Display::drawPixelNOP;
    void (*drawPixelBG)(int16_t,int16_t, uint8_t) = &Display::drawPixelNOP;
    if( x>=0 && y >= 0 && x+w*fontSize<width && y+(h+1)*fontSize<height ){
	if( color != invisiblecolor )
	    drawPixelFG = &Display::drawPixelRaw;
	if( bgcolor != invisiblecolor )
	    drawPixelBG = &Display::drawPixelRaw;
    }else{
	if( color != invisiblecolor )
	    drawPixelFG = &Display::drawPixel;
	if( bgcolor != invisiblecolor )
	    drawPixelBG = &Display::drawPixel;
    }

    void (*drawPixel[])(int16_t,int16_t, uint8_t) = {drawPixelBG, drawPixelFG};
    uint8_t colors[] = {static_cast<uint8_t>(bgcolor), static_cast<uint8_t>(color)};

#if PROJ_ARDUBOY > 0
#else
    if( fontSize != 2 ){
#endif

        for (i = 0; i < charW; ++i) {
            for(uint32_t byteNum = 0; byteNum < hbytes; ++byteNum) {
                bitcolumn = *bitmap;
                ++bitmap;
                uint32_t endRow = (8 + 8*byteNum < h) ? (8 + 8*byteNum) : h;
                for (j = 8*byteNum; j < endRow; ++j) { // was j<=h
                    uint8_t c = colors[ bitcolumn & 1 ];

#if PROJ_ARDUBOY > 0
                    drawPixel[ bitcolumn&1 ](x, y + 7 - j,c);
#elif PROJ_SUPPORT_FONTROTATION > 0
                    // if font flipping & rotation is allowed - do not slow down old programs!
                    if (flipFontVertical) {
                        drawPixel[ bitcolumn&1 ](x, y + h - j,c);
                    } else {
                        drawPixel[ bitcolumn&1 ](x, y + j,c);
                    }
#else
                    // "Normal" case
                    drawPixel[ bitcolumn&1 ](x, y + j,c);
#endif // PROJ_ARDUBOY
                    bitcolumn>>=1;
                }
            }
#if PROJ_SUPPORT_FONTROTATION > 0
            if (flipFontVertical) x--;
            else x++;
#else
            x++;
#endif
        }

#if PROJ_SUPPORT_FONTROTATION > 0
        if (flipFontVertical) return -charW-adjustCharStep;
        else return charW+adjustCharStep; // for character stepping
#else
        return charW+adjustCharStep;
#endif


#if PROJ_ARDUBOY > 0
#else
    }else{

	for (i = 0; i < charW; ++i) {
            for(uint32_t byteNum = 0; byteNum < hbytes; ++byteNum) {
                bitcolumn = *bitmap;
                ++bitmap;
                uint32_t endRow = (8 + 8*byteNum < h) ? (8 + 8*byteNum) : h;
                for (j = 8*byteNum; j < endRow; ++j) { // was j<=h
                    uint8_t c = colors[ bitcolumn & 1 ];

                    drawPixel[ bitcolumn&1 ](x + (i<<1)  , y + (j<<1), c);
                    drawPixel[ bitcolumn&1 ](x + (i<<1)+1, y + (j<<1), c);
                    drawPixel[ bitcolumn&1 ](x + (i<<1)  , y + (j<<1)+1, c);
                    drawPixel[ bitcolumn&1 ](x + (i<<1)+1, y + (j<<1)+1, c);
                    bitcolumn>>=1;
                }
	    }
	}

	return (charW+adjustCharStep)<<1;

    }
#endif // PROJ_ARDUBOY

}

With that fix, I tried the text outline method mentioned in another thread using a 50px high font on top of a 52px high outline font.

bigfont
Source code: bigfont.zip (32.1 KB)

3 Likes

Thanks! That is exactly what I need :slight_smile:

1 Like

That is a good improvement. Do you want to make a PR to PokittoLib?

How do I do that?

There’s same issue with the draw function used by TAS mode. That needs to be checked too.

You need to use Git for that. I can also do it if you send the changes to me.
Edit: PR=“pull request”

Added a PR against PokittoLib. It contains the forn fixes by @jpfli

  • support for big fonts (bigger than 16x16 pixels) for direct, buffered, and TAS modes
  • fixed font clipping in TAS mode at bottom and right screen edges
  • fixed fonnt clipping at the left edge
4 Likes

Merged, thanks @Hanski and @jpfli

1 Like

I updated fontconv.py script in my earlier post and added an example charmap image.

The script now ignores empty space between characters. I also moved some less frequently used command line parameters as variables inside the script file.

Fixed fontconv.py script in the earlier post:

  • Changed i to j as i is already used in the upper loop:
    column = []
    while len(bitcol) >= 8:
          column.append(sum([bitcol[j]<<j for j in range(8)]))  # Changed i to j
          bitcol = bitcol[8:]
2 Likes