Add support for using 4bits mode smooth fonts

By default,the create_font create a 8 bits/pixel fonts,but now it can be
transform into 4 bits/pixel and used on ESP8266/ESP32.I have tested it on
ESP32,both SPIFFS and flash array.

Main modifications :
1. add readInt16() and readInt8() functions;
2. add a flag c4bpp to struct gFont.
3. adjust 4bits mode display.
4. add a tool made by pure C to convert font files.

The API is compatible with previous.Compressed font format is described in
tools/create_smooth_font/compress_font/readme.md


Signed-off-by: Lucy2003 <53362310+Lucy2003@users.noreply.github.com>
This commit is contained in:
Lucy2003 2021-07-16 13:07:06 +08:00
parent 58f457ba97
commit 9393f05fdc
4 changed files with 372 additions and 20 deletions

View File

@ -126,7 +126,7 @@ void TFT_eSPI::loadFont(String fontName, bool flash)
gFont.gArray = (const uint8_t*)fontPtr; gFont.gArray = (const uint8_t*)fontPtr;
gFont.gCount = (uint16_t)readInt32(); // glyph count in file gFont.gCount = (uint16_t)readInt32(); // glyph count in file
readInt32(); // vlw encoder version - discard gFont.c4bpp = (readInt32() == 0xC); // vlw encoder version,0xC for 4bpp version
gFont.yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels gFont.yAdvance = (uint16_t)readInt32(); // Font size in points, not pixels
readInt32(); // discard readInt32(); // discard
gFont.ascent = (uint16_t)readInt32(); // top of "d" gFont.ascent = (uint16_t)readInt32(); // top of "d"
@ -154,6 +154,10 @@ void TFT_eSPI::loadMetrics(void)
{ {
uint32_t headerPtr = 24; uint32_t headerPtr = 24;
uint32_t bitmapPtr = headerPtr + gFont.gCount * 28; uint32_t bitmapPtr = headerPtr + gFont.gCount * 28;
if(gFont.c4bpp)
{
bitmapPtr = headerPtr + gFont.gCount * 8;
}
#if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT) #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT)
if ( psramFound() ) if ( psramFound() )
@ -191,19 +195,29 @@ void TFT_eSPI::loadMetrics(void)
while (gNum < gFont.gCount) while (gNum < gFont.gCount)
{ {
gUnicode[gNum] = (uint16_t)readInt32(); // Unicode code point value if(!gFont.c4bpp)
gHeight[gNum] = (uint8_t)readInt32(); // Height of glyph {
gWidth[gNum] = (uint8_t)readInt32(); // Width of glyph gUnicode[gNum] = (uint16_t)readInt32(); // Unicode code point value
gxAdvance[gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor gHeight[gNum] = (uint8_t)readInt32(); // Height of glyph
gdY[gNum] = (int16_t)readInt32(); // y delta from baseline gWidth[gNum] = (uint8_t)readInt32(); // Width of glyph
gdX[gNum] = (int8_t)readInt32(); // x delta from cursor gxAdvance[gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor
readInt32(); // ignored gdY[gNum] = (int16_t)readInt32(); // y delta from baseline
gdX[gNum] = (int8_t)readInt32(); // x delta from cursor
readInt32(); // ignored
}else{
gUnicode[gNum] = (uint16_t)readInt16(); // Unicode code point value
gHeight[gNum] = (uint8_t)readInt8(); // Height of glyph
gWidth[gNum] = (uint8_t)readInt8(); // Width of glyph
gxAdvance[gNum] = (uint8_t)readInt8(); // xAdvance - to move x cursor
gdY[gNum] = (int16_t)readInt16(); // y delta from baseline
gdX[gNum] = (int8_t)readInt8(); // x delta from cursor
}
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gHeight = "); Serial.println(gHeight[gNum]); //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gHeight = "); Serial.println(gHeight[gNum]);
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gWidth = "); Serial.println(gWidth[gNum]); //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gWidth = "); Serial.println(gWidth[gNum]);
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gxAdvance = "); Serial.println(gxAdvance[gNum]); //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gxAdvance = "); Serial.println(gxAdvance[gNum]);
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gdY = "); Serial.println(gdY[gNum]); //Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gdY = "); Serial.println(gdY[gNum]);
//Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", gdX = "); Serial.println(gdX[gNum]);
//Serial.println("");
// Different glyph sets have different ascent values not always based on "d", so we could get // Different glyph sets have different ascent values not always based on "d", so we could get
// the maximum glyph ascent by checking all characters. BUT this method can generate bad values // the maximum glyph ascent by checking all characters. BUT this method can generate bad values
// for non-existant glyphs, so we will reply on processing for the value and disable this code for now... // for non-existant glyphs, so we will reply on processing for the value and disable this code for now...
@ -229,14 +243,22 @@ void TFT_eSPI::loadMetrics(void)
{ {
gFont.maxDescent = gHeight[gNum] - gdY[gNum]; gFont.maxDescent = gHeight[gNum] - gdY[gNum];
#ifdef SHOW_ASCENT_DESCENT #ifdef SHOW_ASCENT_DESCENT
Serial.print("Unicode = 0x"); Serial.print(gUnicode[gNum], HEX); Serial.print(", maxDescent = "); Serial.println(gHeight[gNum] - gdY[gNum]); Serial.print("Unicode = 0x");
Serial.print(gUnicode[gNum], HEX); Serial.print(", maxDescent = ");
Serial.println(gHeight[gNum] - gdY[gNum]);
#endif #endif
} }
} }
gBitmap[gNum] = bitmapPtr; gBitmap[gNum] = bitmapPtr;
if(gFont.c4bpp)
bitmapPtr += gWidth[gNum] * gHeight[gNum]; {
bitmapPtr += ((gWidth[gNum] >> 1) + (gWidth[gNum] & 1)) * gHeight[gNum];
}
else
{
bitmapPtr += gWidth[gNum] * gHeight[gNum];
}
gNum++; gNum++;
yield(); yield();
@ -333,6 +355,50 @@ uint32_t TFT_eSPI::readInt32(void)
return val; return val;
} }
/***************************************************************************************
** Function name: readInt16
** Description: Get a 16 bit integer from the font file
*************************************************************************************x*/
uint16_t TFT_eSPI::readInt16(void)
{
uint16_t val = 0;
#ifdef FONT_FS_AVAILABLE
if (fs_font) {
val |= fontFile.read() << 8;
val |= fontFile.read();
}
else
#endif
{
val |= pgm_read_byte(fontPtr++) << 8;
val |= pgm_read_byte(fontPtr++);
}
return val;
}
/***************************************************************************************
** Function name: readInt8
** Description: Get a 8 bit integer from the font file
*************************************************************************************x*/
uint8_t TFT_eSPI::readInt8(void)
{
uint8_t val = 0;
#ifdef FONT_FS_AVAILABLE
if (fs_font) {
val = fontFile.read();
}
else
#endif
{
val = pgm_read_byte(fontPtr++);
}
return val;
}
/*************************************************************************************** /***************************************************************************************
** Function name: getUnicodeIndex ** Function name: getUnicodeIndex
@ -408,7 +474,8 @@ void TFT_eSPI::drawGlyph(uint16_t code)
int16_t xs = cx; int16_t xs = cx;
uint32_t dl = 0; uint32_t dl = 0;
uint8_t pixel; uint8_t pixel = 0;
uint8_t pixel_before = 0;
startWrite(); // Avoid slow ESP32 transaction overhead for every pixel startWrite(); // Avoid slow ESP32 transaction overhead for every pixel
@ -420,13 +487,27 @@ void TFT_eSPI::drawGlyph(uint16_t code)
if (fs_font) { if (fs_font) {
if (spiffs) if (spiffs)
{ {
fontFile.read(pbuffer, gWidth[gNum]); if(gFont.c4bpp)
{
fontFile.read(pbuffer, (gWidth[gNum] >> 1) + (gWidth[gNum] & 1));
}
else
{
fontFile.read(pbuffer, gWidth[gNum]);
}
//Serial.println("SPIFFS"); //Serial.println("SPIFFS");
} }
else else
{ {
endWrite(); // Release SPI for SD card transaction endWrite(); // Release SPI for SD card transaction
fontFile.read(pbuffer, gWidth[gNum]); if(gFont.c4bpp)
{
fontFile.read(pbuffer, (gWidth[gNum] >> 1) + (gWidth[gNum] & 1));
}
else
{
fontFile.read(pbuffer, gWidth[gNum]);
}
startWrite(); // Re-start SPI for TFT transaction startWrite(); // Re-start SPI for TFT transaction
//Serial.println("Not SPIFFS"); //Serial.println("Not SPIFFS");
} }
@ -435,10 +516,26 @@ void TFT_eSPI::drawGlyph(uint16_t code)
for (int x = 0; x < gWidth[gNum]; x++) for (int x = 0; x < gWidth[gNum]; x++)
{ {
#ifdef FONT_FS_AVAILABLE #ifdef FONT_FS_AVAILABLE
if (fs_font) pixel = pbuffer[x]; if (fs_font)
else {
pixel = (!gFont.c4bpp ? pbuffer[x] : pbuffer[(x >> 1) + (x & 1)]);
pixel_before = (!gFont.c4bpp || x == 0 ? 0 : pbuffer[((x-1) >> 1) + ((x - 1) & 1)]);
}
else{
#endif #endif
pixel = pgm_read_byte(gPtr + gBitmap[gNum] + x + gWidth[gNum] * y); pixel = (!gFont.c4bpp ? pgm_read_byte(gPtr + gBitmap[gNum] + x + gWidth[gNum] * y)
: pgm_read_byte( gPtr + gBitmap[gNum] + (x >> 1) + (x & 1) +
((gWidth[gNum] >> 1) + (gWidth[gNum] & 1)) * y));
pixel_before = (!gFont.c4bpp || x == 0 ? 0 : pgm_read_byte( gPtr + gBitmap[gNum] + ((x -1) >> 1) +
((x -1) & 1) + ((gWidth[gNum] >> 1) + (gWidth[gNum] & 1)) * y));
#ifdef FONT_FS_AVAILABLE
}
#endif
//Prepare real pixel data for 4bpp mode
if(gFont.c4bpp)
{
pixel = (!(x & 1) ? (pixel & 0xf0) : ((pixel_before & 0x0f) << 4));
}
if (pixel) if (pixel)
{ {

View File

@ -27,9 +27,10 @@
int16_t descent; // Offset to bottom of 'p', other characters may have a larger descent int16_t descent; // Offset to bottom of 'p', other characters may have a larger descent
uint16_t maxAscent; // Maximum ascent found in font uint16_t maxAscent; // Maximum ascent found in font
uint16_t maxDescent; // Maximum descent found in font uint16_t maxDescent; // Maximum descent found in font
bool c4bpp;
} fontMetrics; } fontMetrics;
fontMetrics gFont = { nullptr, 0, 0, 0, 0, 0, 0, 0 }; fontMetrics gFont = { nullptr, 0, 0, 0, 0, 0, 0, 0 ,false };
// These are for the metrics for each individual glyph (so we don't need to seek this in file and waste time) // These are for the metrics for each individual glyph (so we don't need to seek this in file and waste time)
uint16_t* gUnicode = NULL; //UTF-16 code, the codes are searched so do not need to be sequential uint16_t* gUnicode = NULL; //UTF-16 code, the codes are searched so do not need to be sequential
@ -56,6 +57,8 @@ fontMetrics gFont = { nullptr, 0, 0, 0, 0, 0, 0, 0 };
void loadMetrics(void); void loadMetrics(void);
uint32_t readInt32(void); uint32_t readInt32(void);
uint16_t readInt16(void);
uint8_t readInt8(void);
uint8_t* fontPtr = nullptr; uint8_t* fontPtr = nullptr;

View File

@ -0,0 +1,43 @@
### VLW Compress for TFT_eSPI
Used to transform 8bit VLW file into 4bit VLW file with no padding support.
**Advantage**: It will save at least 50% space.
**Limitation**: Fonts that height over 255 are not being generated properly.
To Compile,run the command below:
Windows(MinGW needed): `gcc vlwcompress.c -o vlwcompress.exe`
Linux (GCC needed): `gcc vlwcompress.c -o vlwcompress`
#### Compressed Font format
Header of vlw file is the same as uncompressed;it comprises 6 uint32_t parameters (24 bytes total):
1. The gCount (number of character glyphs)
2. A version number (0xB = 11 for uncompressed,0XC = 12 for compressed 4bit)
3. The font size (in points, not pixels)
4. Deprecated mboxY parameter (typically set to 0)
5. Ascent in pixels from baseline to top of "d"
6. Descent in pixels from baseline to bottom of "p"
Next are gCount sets of values for each glyph(8 bytes):
1. (16 bits) Glyph Unicode
2. (8 bits) Height of bitmap bounding box
3. (8 bits) Width of bitmap bounding box
4. (8 bits) gxAdvance for cursor (setWidth in Processing)
5. (16 bits) dY = distance from cursor baseline to top of glyph bitmap (signed value +ve = up)
6. (8 bits) dX = distance from cursor to left side of glyph bitmap (signed value -ve = left)
The bitmaps start next at 24 + (8 * gCount) bytes from the start of the file.
Each pixel is 0.5 byte, an 4 bit Alpha value which represents the transparency from
0xF foreground colour, 0x0 background.When a line consist of odd number of pixels,the last byte
for the last pixel of the line will be filled with 0 as low 4 bits.
4 bits alpha value will be generated to 8 bits before drawing.
After the bitmaps is (Not modified):
1 byte for font name string length (excludes null)
a zero terminated character string giving the font name
1 byte for Postscript name string length
a zero/one terminated character string giving the font name
last byte is 0 for non-anti-aliased and 1 for anti-aliased (smoothed)

View File

@ -0,0 +1,209 @@
/*
VLW Compress v0.0.1 16/7/21
Used to transform 8bit VLW file into 4bit VLW file with no padding support.
Note: The program is made by pure C ;it has no dependence.
License:See license in root directory.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
FILE* fin;
FILE* fout;
struct {
uint32_t gCount;
uint32_t gVer; //0xB(11) = unCompressed,0xC(12)=compressed.
uint32_t fSize;
uint32_t mboxY;
uint32_t ascent;
uint32_t descent;
uint8_t* gX;
uint8_t* gY;
} fInfo;
uint32_t readu32(){
uint32_t temp=0;
uint8_t tmp[4]={0};
fread(tmp,1,4,fin);
temp |= tmp[0] << 24;
temp |= tmp[1] << 16;
temp |= tmp[2] << 8;
temp |= tmp[3] << 0;
return temp;
}
uint8_t readu8(){
uint8_t temp[1];
fread(temp,1,1,fin);
return temp[0];
}
int writeu32(uint32_t in){
uint8_t tmp[4]={0};
tmp[0]=in>>24;
tmp[1]=(in & 0x00ff0000 )>>16;
tmp[2]=(in & 0x0000ff00 )>>8;
tmp[3]=(in & 0x000000ff );
if(fwrite(tmp,1,4,fout)==4){
return 0;
}
return 1;
}
int writeu16(uint16_t in){
uint8_t tmp[2]={0};
tmp[0]=in>>8;
tmp[1]=(in & 0x00ff );
if(fwrite(tmp,1,2,fout)==2){
return 0;
}
return 1;
}
int writeu8(uint8_t in){
if(fwrite(&in,1,1,fout)==1){
return 0;
}
return 1;
}
uint8_t combine2u8(uint8_t H,uint8_t L){
return (H & 0xf0 ) | (L >> 4); //All get High bits and combine
}
int getFontInfo(){
fInfo.gCount=readu32();
fInfo.gVer=readu32();
fInfo.fSize=readu32();
fInfo.mboxY=readu32();
fInfo.ascent=readu32();
fInfo.descent=readu32();
return 0;
}
int writeHeader(){
writeu32(fInfo.gCount);
writeu32(12ULL);
writeu32(fInfo.fSize);
writeu32(fInfo.mboxY);
writeu32(fInfo.ascent);
writeu32(fInfo.descent);
return 0;
}
int compressCharsTable(){
for(uint32_t i=0;i<fInfo.gCount;i++){
uint16_t unicode=(uint16_t)readu32();
uint8_t bh=(uint8_t)readu32();
uint8_t bw=(uint8_t)readu32();
uint8_t ga=(uint8_t)readu32();
int16_t dy=(int16_t)readu32();
int8_t dx=(int8_t)readu32();
readu32(); //ignore padding
fInfo.gX[i]=bw;
fInfo.gY[i]=bh;
writeu16(unicode);
writeu8(bh);
writeu8(bw);
writeu8(ga);
writeu16(dy);
writeu8(dx);
}
return 0;
}
int compressCharsBitmap(){
for(uint32_t i=0;i<fInfo.gCount;i++){
if((fInfo.gX[i] & 1) !=0){//not fix 2
for(uint8_t j=0;j<fInfo.gY[i];j++){
for(uint8_t k=0;k<fInfo.gX[i]-1;k+=2){
uint8_t t1=(uint8_t)readu8();
uint8_t t2=(uint8_t)readu8();
writeu8(combine2u8(t1,t2));
}
//process the last pixel of each line with 0 as low bits
uint8_t t1=(uint8_t)readu8();
writeu8(combine2u8(t1,0));
}
}else{
for(uint16_t j=0;j<fInfo.gX[i] * fInfo.gY[i];j+=2){
uint8_t t1=(uint8_t)readu8();
uint8_t t2=(uint8_t)readu8();
writeu8(combine2u8(t1,t2));
}
}
}
}
int main(int argc,char* argv[]){
printf("\n=== VLWCompress - 4bpp by Lucy2003 v0.0.1 === \n\n");
if(argc<=2){
printf(" usage: %s [input_path] [output_path]\n\n",argv[0]);
return 0;
}
printf("Input file: %s\nOutput file: %s\n",argv[1],argv[2]);
fin=fopen(argv[1],"rb");
if(fin == NULL){
printf("\nError: Can't open input file. \n\n");
return 1;
}
fseek(fin,0,SEEK_SET);
fout=fopen(argv[2],"w+b");
if(fout==NULL){
printf("\nError: Can't open output file. \n\n");
return 2;
}
getFontInfo();
printf("Font file info:\n");
printf(" Char count: %d\n",fInfo.gCount);
printf(" Font version: %d\n",fInfo.gVer);
printf(" Font size: %d Bytes\n",fInfo.fSize);
printf(" mboxY: %d\n ascent:%d\n descent:%d\n",fInfo.mboxY,fInfo.ascent,fInfo.descent);
if(fInfo.gVer==12){
printf("\nError: Input file already compressed.\n\n");
return 3;
}
if(fInfo.gVer!=11){
printf("\nError: Unsupport font version.\n\n");
return 4;
}
fInfo.gX=NULL;
fInfo.gY=NULL;
fInfo.gX=(uint8_t*)calloc(fInfo.gCount,sizeof(uint8_t));
fInfo.gY=(uint8_t*)calloc(fInfo.gCount,sizeof(uint8_t));
if(fInfo.gX==NULL || fInfo.gY==NULL){
printf("\nError: Malloc memory failed.\n\n");
return 5;
}
//Compress
writeHeader();
compressCharsTable();
compressCharsBitmap();
free(fInfo.gX);
free(fInfo.gY);
while(!feof(fin)){
writeu8(readu8());
}
fclose(fin);
fclose(fout);
printf("\nCompress Font successfully!\n\n");
return 0;
}