Smooth Big Text with Hints
9th December 2024
This article describes a simple way to add automatic smoothing and hinting to bitmapped characters, so you can use a standard 6x8 pixel character set to generate smooth double-sized characters on a graphics display:
Left: Double-sized characters plotted on a graphics display.
Right: The versions smoothed and hinted using the routine in this article.
Introduction
There are several low-cost colour TFT displays available that are ideal for displaying instrument readings and other data, but because of their high resolution, text in the standard 6x8 pixel character set is often too small to read comfortably. One solution is to use a character set defined with larger characters, but this would use a lot of program memory. For example, a typical 96-character definition with 6x8 pixels takes 576 bytes [1]. An equivalent character set with 12x16 pixel characters would take 2304 bytes.
Smooth big text
My earlier article Smooth Big Text described a simpler solution, which automatically smooths scaled-up characters from the standard 6x8 character set to generate the appearance of a higher-resolution 12x16 pixel font. It works surprisingly well, apart for a small number of characters where the automatic algorithm generates a step where a human designer would make a smooth transition. Although not very noticeable, I decided to address this problem for a new project I'm currently working on.
Hinting
This improvement to the original algorithm allows you to define "hints" that add pixels to the result created by the automatic algorithm to give a perfectly smooth appearance. I've called them hints by analogy with the hints in TrueType fonts which are used to improve the legibility of the automatically-generated bitmaps.
By examining all 96 characters created by the automatic algorithm I found that at most two pixels needed to be added to create a perfectly smooth character, so I've allowed up to two hints per character. This increases the total size of a 96-character font to 672 bytes, still well below the total that would be needed to define a 12x16 character font explicitly.
The hinting is needed for the following characters: '3', '4', '@', 'M', 'N', 'R', 'g', 'h', 'r', and 'z':
Left: The smoothed characters without hinting.
Right: The versions hinted using the routine in this article.
Application
The smoothing and hinting described in this article is designed as an addition to the character plotting in my Compact TFT Graphics Library. This is a graphics library for colour TFT displays using standard SPI calls, allowing it to be used on a wide variety of platforms. The examples here are shown on a 1.8" 128x160 SPI TFT display available from AliExpress [2].
It should be easy to modify it for other graphics libraries, or you could apply the hinting to the version for OLED displays in my earlier article.
How it works
Smoothing
The smoothing works by checking whether one of the following two situations A or B occur anywhere in the double-resolution character:
If they do, two extra pixels are added as shown in the second diagram in each pair.
Hinting
The hinting works by plotting up to two additional pixels at specified positions in the character cell:
Each hint is defined by a byte in which the upper four bits give the x coordinate of the pixel, and the lower four bits the y coordinate of the pixel. The coordinates are measured from 0,0 in the bottom left corner of the character cell. A byte 0x00 signifies that no hint is needed.
The program
The following PlotChar() routine replaces the equivalent routine in the Compact TFT Graphics Library, and performs smoothing and hinting on the characters as they are plotted:
void PlotChar (char c) { int colour; SendCommand2(CASET, yoff+ypos, yoff+ypos+8*scale-1); SendCommand2(RASET, xoff+xpos, xoff+xpos+6*scale-1); SendCommand(RAMWR); uint8_t col0 = pgm_read_byte(&CharMap[c-32][0]); uint16_t col0L, col0R, col1L, col1R; col0L = Stretch(col0); col0R = col0L; for (uint8_t col = 1 ; col < 5; col++) { uint8_t col1 = pgm_read_byte(&CharMap[c-32][col]); col1L = Stretch(col1); col1R = col1L; if (scale == 1) WriteColumn(col0); // Smoothing else { if (smooth) { for (int i=6; i>=0; i--) { for (int j=1; j<3; j++) { if (((col0>>i & 0b11) == (3-j)) && ((col1>>i & 0b11) == j)) { col0R = col0R | 1<<((i*2)+j); col1L = col1L | 1<<((i*2)+3-j); } } } } WriteColumns(col0L, col0R); col0L = col1L; col0R = col1R; } col0 = col1; } if (scale == 1) WriteColumn(col0); else { WriteColumns(col0L, col0R); if (hints) { uint8_t hint = pgm_read_byte(&CharMap[c-32][5]); if (hint) PlotPoint((hint>>4)+xpos, (hint & 0x0f)+ypos); hint = pgm_read_byte(&CharMap[c-32][6]); if (hint) PlotPoint((hint>>4)+xpos, (hint & 0x0f)+ypos); } } xpos = xpos + 6*scale; }
The global variable scale allows you to select what size of character to plot. Setting it to 1 gives normal, 6x8 pixel characters, and setting it to 2 gives double-sized characters, 12x16 pixels.
The global variable smooth allows you to select whether to smooth the double-sized characters. Setting it to false gives blocky characters, or true gives the smoothed characters described in this article. It has no effect if Scale is 1.
The global variable hints allows you to select whether to apply hints to the double-sized characters. Setting it to false omits the hints, or true gives the hinted characters.
The usual settings will be:
uint8_t scale = 2; bool smooth = true; bool hints = true;
Stretch
This version of PlotChar() uses this routine Stretch() to double the pixels in a column of the bitmap:
uint16_t Stretch (uint16_t x) { x = (x & 0xF0)<<4 | (x & 0x0F); x = (x<<2 | x) & 0x3333; x = (x<<1 | x) & 0x5555; return x<<1 | x; }
It also uses WriteColumn() to write a write a single 8-pixel column of the character bitmap, and WriteColumns() to write the two adjacent 16-pixel columns:
void WriteColumn (uint8_t bits) { uint16_t colour; digitalWrite(cs, LOW); for (uint8_t i=0; i<8; i++) { if (bits>>(7-i) & 1) colour = fore; else colour = back; SPI.transfer(colour>>8); SPI.transfer(colour & 0xFF); } digitalWrite(cs, HIGH); } void WriteColumns (uint16_t left, uint16_t right) { WriteColumn(left>>8); WriteColumn(left); WriteColumn(right>>8); WriteColumn(right); }
Operation
The PlotChar() routine plots the character a column of pixels at a time, left to right. The key lines that actually do the smoothing are:
for (int j=1; j<3; j++) { if (((col0>>i & 0b11) == (3-j)) && ((col1>>i & 0b11) == j)) { col0R = col0R | 1<<((i*2)+j); col1L = col1L | 1<<((i*2)+3-j); } }
The variables col0 and col1 are two successive 8-pixel high columns in the normal-resolution character, and col0L, col0R, col1L, and col1R are the 16-pixel high left and right versions of these in the double-resolution character.
Resources
Here's the modified version of the 6x8 character-set definition, with hints, together with the definition of PlotChar() and the associated routines to implement the text smoothing and hinting: Smooth Big Text with Hints Program.
Use this by replacing the character set definition and PlotChar() in the TFT library defined here: Compact TFT Graphics Library Program.
- ^ Or 480 bytes if you omit the 6th column which is usually zero to give a gap between characters.
- ^ 1.8" 128x160 SPI TFT display on AliExpress.
blog comments powered by Disqus