From 9393f05fdc5e4990ebafd7001aae9d2726a964d2 Mon Sep 17 00:00:00 2001 From: Lucy2003 <53362310+Lucy2003@users.noreply.github.com> Date: Fri, 16 Jul 2021 13:07:06 +0800 Subject: [PATCH] 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> --- Extensions/Smooth_font.cpp | 135 +++++++++-- Extensions/Smooth_font.h | 5 +- .../Compress_font/ReadMe.md | 43 ++++ .../Compress_font/vlwcompress.c | 209 ++++++++++++++++++ 4 files changed, 372 insertions(+), 20 deletions(-) create mode 100644 Tools/Create_Smooth_Font/Compress_font/ReadMe.md create mode 100644 Tools/Create_Smooth_Font/Compress_font/vlwcompress.c diff --git a/Extensions/Smooth_font.cpp b/Extensions/Smooth_font.cpp index fa94c59..5597c2d 100644 --- a/Extensions/Smooth_font.cpp +++ b/Extensions/Smooth_font.cpp @@ -126,7 +126,7 @@ void TFT_eSPI::loadFont(String fontName, bool flash) gFont.gArray = (const uint8_t*)fontPtr; 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 readInt32(); // discard gFont.ascent = (uint16_t)readInt32(); // top of "d" @@ -154,6 +154,10 @@ void TFT_eSPI::loadMetrics(void) { uint32_t headerPtr = 24; uint32_t bitmapPtr = headerPtr + gFont.gCount * 28; + if(gFont.c4bpp) + { + bitmapPtr = headerPtr + gFont.gCount * 8; + } #if defined (ESP32) && defined (CONFIG_SPIRAM_SUPPORT) if ( psramFound() ) @@ -191,19 +195,29 @@ void TFT_eSPI::loadMetrics(void) while (gNum < gFont.gCount) { - gUnicode[gNum] = (uint16_t)readInt32(); // Unicode code point value - gHeight[gNum] = (uint8_t)readInt32(); // Height of glyph - gWidth[gNum] = (uint8_t)readInt32(); // Width of glyph - gxAdvance[gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor - gdY[gNum] = (int16_t)readInt32(); // y delta from baseline - gdX[gNum] = (int8_t)readInt32(); // x delta from cursor - readInt32(); // ignored - + if(!gFont.c4bpp) + { + gUnicode[gNum] = (uint16_t)readInt32(); // Unicode code point value + gHeight[gNum] = (uint8_t)readInt32(); // Height of glyph + gWidth[gNum] = (uint8_t)readInt32(); // Width of glyph + gxAdvance[gNum] = (uint8_t)readInt32(); // xAdvance - to move x cursor + 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(", 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(", 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 // 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... @@ -229,14 +243,22 @@ void TFT_eSPI::loadMetrics(void) { gFont.maxDescent = gHeight[gNum] - gdY[gNum]; #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 } } gBitmap[gNum] = bitmapPtr; - - bitmapPtr += gWidth[gNum] * gHeight[gNum]; + if(gFont.c4bpp) + { + bitmapPtr += ((gWidth[gNum] >> 1) + (gWidth[gNum] & 1)) * gHeight[gNum]; + } + else + { + bitmapPtr += gWidth[gNum] * gHeight[gNum]; + } gNum++; yield(); @@ -333,6 +355,50 @@ uint32_t TFT_eSPI::readInt32(void) 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 @@ -408,7 +474,8 @@ void TFT_eSPI::drawGlyph(uint16_t code) int16_t xs = cx; 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 @@ -420,13 +487,27 @@ void TFT_eSPI::drawGlyph(uint16_t code) if (fs_font) { 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"); } else { 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 //Serial.println("Not SPIFFS"); } @@ -435,10 +516,26 @@ void TFT_eSPI::drawGlyph(uint16_t code) for (int x = 0; x < gWidth[gNum]; x++) { #ifdef FONT_FS_AVAILABLE - if (fs_font) pixel = pbuffer[x]; - else + if (fs_font) + { + 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 - 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) { diff --git a/Extensions/Smooth_font.h b/Extensions/Smooth_font.h index ee2a77b..78bf2ea 100644 --- a/Extensions/Smooth_font.h +++ b/Extensions/Smooth_font.h @@ -27,9 +27,10 @@ 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 maxDescent; // Maximum descent found in font + bool c4bpp; } 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) 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); uint32_t readInt32(void); + uint16_t readInt16(void); + uint8_t readInt8(void); uint8_t* fontPtr = nullptr; diff --git a/Tools/Create_Smooth_Font/Compress_font/ReadMe.md b/Tools/Create_Smooth_Font/Compress_font/ReadMe.md new file mode 100644 index 0000000..e7a5d6b --- /dev/null +++ b/Tools/Create_Smooth_Font/Compress_font/ReadMe.md @@ -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) diff --git a/Tools/Create_Smooth_Font/Compress_font/vlwcompress.c b/Tools/Create_Smooth_Font/Compress_font/vlwcompress.c new file mode 100644 index 0000000..e98db0d --- /dev/null +++ b/Tools/Create_Smooth_Font/Compress_font/vlwcompress.c @@ -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 +#include +#include + +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