Basic glyph compression

This commit is contained in:
Dave Allie 2026-01-05 22:54:09 +11:00
parent afe9672156
commit cb3e08e73c
No known key found for this signature in database
GPG Key ID: F2FDDB3AD8D0276F
57 changed files with 206074 additions and 247943 deletions

View File

@ -5,6 +5,7 @@
#include <cstdint>
/// Font data stored PER GLYPH
#pragma pack(push, 1)
typedef struct {
uint8_t width; ///< Bitmap dimensions in pixels
uint8_t height; ///< Bitmap dimensions in pixels
@ -13,7 +14,9 @@ typedef struct {
int16_t top; ///< Y dist from cursor pos to UL corner
uint16_t dataLength; ///< Size of the font data.
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
bool compressed;
} EpdGlyph;
#pragma pack(pop)
/// Glyph interval structure
typedef struct {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ parser.add_argument("--2bit", dest="is2Bit", action="store_true", help="generate
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
args = parser.parse_args()
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "compressed", "code_point"])
font_stack = [freetype.Face(f) for f in args.fontstack]
is2Bit = args.is2Bit
@ -124,7 +124,6 @@ def load_glyph(code_point):
face.load_glyph(glyph_index, freetype.FT_LOAD_RENDER)
return face
face_index += 1
print(f"code point {code_point} ({hex(code_point)}) not found in font stack!", file=sys.stderr)
return None
unmerged_intervals = sorted(intervals + add_ints)
@ -242,6 +241,12 @@ for i_start, i_end in intervals:
# Build output data
packed = bytes(pixels)
compressed = zlib.compress(packed)
is_compressed = len(compressed) < len(packed) * 0.9
# Use compressed data only if it's at least 10% smaller
if is_compressed:
packed = compressed
glyph = GlyphProps(
width = bitmap.width,
height = bitmap.rows,
@ -250,6 +255,7 @@ for i_start, i_end in intervals:
top = face.glyph.bitmap_top,
data_length = len(packed),
data_offset = total_size,
compressed = 'true' if is_compressed else 'false',
code_point = code_point,
)
total_size += len(packed)

View File

@ -1,6 +1,33 @@
#include "GfxRenderer.h"
#include <Utf8.h>
#include <miniz.h>
namespace {
bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) {
// Setup inflator
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
if (!inflator) {
Serial.printf("[%lu] [ZIP] Failed to allocate memory for inflator\n", millis());
return false;
}
memset(inflator, 0, sizeof(tinfl_decompressor));
tinfl_init(inflator);
size_t inBytes = deflatedSize;
size_t outBytes = inflatedSize;
const tinfl_status status = tinfl_decompress(inflator, inputBuf, &inBytes, nullptr, outputBuf, &outBytes,
TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
free(inflator);
if (status != TINFL_STATUS_DONE) {
Serial.printf("[%lu] [ZIP] tinfl_decompress() failed with status %d\n", millis(), status);
return false;
}
return true;
}
} // namespace
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
@ -616,52 +643,82 @@ void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp,
}
const int is2Bit = fontFamily.getData(style)->is2Bit;
const uint32_t offset = glyph->dataOffset;
// const uint32_t offset = glyph->dataOffset;
// const uint32_t dataLength = glyph->dataLength;
const uint8_t width = glyph->width;
const uint8_t height = glyph->height;
const int left = glyph->left;
const size_t outputDataSize = is2Bit ? ((width * height + 3) / 4) : ((width * height + 7) / 8);
const uint8_t* bitmap = nullptr;
bitmap = &fontFamily.getData(style)->bitmap[offset];
if (outputDataSize != glyph->dataLength && !glyph->compressed) {
Serial.printf("[%lu] [GFX] Glyph bitmap size mismatch for codepoint %d (expected %zu, got %d)\n", millis(), cp,
outputDataSize, glyph->dataLength);
}
if (bitmap != nullptr) {
for (int glyphY = 0; glyphY < height; glyphY++) {
const int screenY = *y - glyph->top + glyphY;
for (int glyphX = 0; glyphX < width; glyphX++) {
const int pixelPosition = glyphY * width + glyphX;
const int screenX = *x + left + glyphX;
uint8_t* bitmapData = nullptr;
const uint8_t* bitmap;
if (is2Bit) {
const uint8_t byte = bitmap[pixelPosition / 4];
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
// the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black
// we swap this to better match the way images and screen think about colors:
// 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
if (glyph->compressed) {
bitmapData = static_cast<uint8_t*>(malloc(outputDataSize));
if (!bitmapData) {
Serial.printf("[%lu] [GFX] Failed to allocate memory for glyph bitmap for codepoint %d\n", millis(), cp);
return;
}
if (renderMode == BW && bmpVal < 3) {
// Black (also paints over the grays in BW mode)
drawPixel(screenX, screenY, pixelState);
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
// Light gray (also mark the MSB if it's going to be a dark gray too)
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
// Dark gray
drawPixel(screenX, screenY, false);
}
} else {
const uint8_t byte = bitmap[pixelPosition / 8];
const uint8_t bit_index = 7 - (pixelPosition % 8);
const auto success = inflateOneShot(&fontFamily.getData(style)->bitmap[glyph->dataOffset], glyph->dataLength,
bitmapData, outputDataSize);
if (!success) {
Serial.printf("[%lu] [GFX] Failed to inflate glyph bitmap for codepoint %d\n", millis(), cp);
free(bitmapData);
*x += glyph->advanceX;
return;
}
if ((byte >> bit_index) & 1) {
drawPixel(screenX, screenY, pixelState);
}
bitmap = bitmapData;
} else {
bitmap = &fontFamily.getData(style)->bitmap[glyph->dataOffset];
}
for (int glyphY = 0; glyphY < height; glyphY++) {
const int screenY = *y - glyph->top + glyphY;
for (int glyphX = 0; glyphX < width; glyphX++) {
const int pixelPosition = glyphY * width + glyphX;
const int screenX = *x + left + glyphX;
if (is2Bit) {
const uint8_t byte = bitmap[pixelPosition / 4];
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
// the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black
// we swap this to better match the way images and screen think about colors:
// 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
if (renderMode == BW && bmpVal < 3) {
// Black (also paints over the grays in BW mode)
drawPixel(screenX, screenY, pixelState);
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
// Light gray (also mark the MSB if it's going to be a dark gray too)
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
drawPixel(screenX, screenY, false);
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
// Dark gray
drawPixel(screenX, screenY, false);
}
} else {
const uint8_t byte = bitmap[pixelPosition / 8];
const uint8_t bit_index = 7 - (pixelPosition % 8);
if ((byte >> bit_index) & 1) {
drawPixel(screenX, screenY, pixelState);
}
}
}
}
if (bitmapData) {
free(bitmapData);
}
*x += glyph->advanceX;
}

View File

@ -4,6 +4,7 @@
#include <SDCardManager.h>
#include <miniz.h>
namespace {
bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) {
// Setup inflator
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
@ -27,6 +28,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t*
return true;
}
} // namespace
bool ZipFile::loadAllFileStatSlims() {
const bool wasOpen = isOpen();