Files
arduino/libraries/mcu-renderer/tools/fontconv/encode.cpp
2025-10-12 09:13:56 +02:00

316 lines
10 KiB
C++

/*
* MCU renderer fontconv
* Font encoder
*
* (C) 2023 Gissio
*
* License: MIT
*/
#include <cstring>
#include <iostream>
#include "encode.h"
using namespace std;
struct EncoderSettings
{
uint32_t boundingBoxLeftBitNum;
uint32_t boundingBoxBottomBitNum;
uint32_t boundingBoxWidthBitNum;
uint32_t boundingBoxHeightBitNum;
uint32_t advanceBitNum;
uint32_t pixelBitNum;
uint32_t runLengthBlackBitNum;
uint32_t runLengthWhiteBitNum;
};
static uint32_t getBitNum(int32_t value)
{
if (value < 0)
value = -value - 1;
uint32_t bitNum = 0;
while (value > 0)
{
bitNum++;
value >>= 1;
}
return bitNum;
}
static uint32_t getBitNum(MinMax &minMax)
{
uint32_t bitNumA = getBitNum(minMax.min);
uint32_t bitNumB = getBitNum(minMax.max);
return (bitNumA > bitNumB) ? bitNumA : bitNumB;
}
static void encodeGlyph(Glyph &glyph,
EncoderSettings &encoderSettings,
BitWriter &bitstream)
{
uint8_t bitshift = (8 - encoderSettings.pixelBitNum);
uint32_t whiteValue = (1 << encoderSettings.pixelBitNum) - 1;
vector<BitWriter> symbols;
// Run-length encoding
uint32_t symbolIndex = 0;
while (symbolIndex < glyph.bitmap.size())
{
uint8_t value = glyph.bitmap[symbolIndex++] >> bitshift;
BitWriter symbol;
if (!value ||
(value == whiteValue))
{
uint32_t runLength = 1;
while (symbolIndex < glyph.bitmap.size())
{
uint8_t newValue = glyph.bitmap[symbolIndex] >> bitshift;
if (value != newValue)
break;
symbolIndex++;
runLength++;
}
symbol.writeFixedEncodedValue(value,
encoderSettings.pixelBitNum);
if (!value)
symbol.writeRiceEncodedValue(runLength,
encoderSettings.runLengthBlackBitNum);
else
symbol.writeRiceEncodedValue(runLength - 1,
encoderSettings.runLengthWhiteBitNum);
}
else
symbol.writeFixedEncodedValue(value,
encoderSettings.pixelBitNum);
symbols.push_back(symbol);
}
// Remove repetitions
for (int32_t symbolIndex = 0;
symbolIndex < symbols.size();
symbolIndex++)
{
// Search repetitions
uint32_t bestRepeatLength;
uint32_t bestRepeatNum = 1;
uint32_t bestRepeatBitNum = 0;
for (uint32_t repeatLength = 2;
repeatLength < glyph.width;
repeatLength++)
{
uint32_t source_index = symbolIndex;
uint32_t dest_index = symbolIndex + repeatLength;
uint32_t repeatNum = 1;
uint32_t repeatBitNum = 0;
uint32_t bitNum = 0;
while (dest_index < symbols.size())
{
if (symbols[source_index] != symbols[dest_index])
break;
bitNum += symbols[source_index].getBitNum();
source_index++;
dest_index++;
if (source_index == (symbolIndex + repeatLength))
{
source_index = symbolIndex;
repeatNum++;
repeatBitNum = bitNum;
}
}
if (repeatBitNum > bestRepeatBitNum)
{
bestRepeatLength = repeatLength;
bestRepeatNum = repeatNum;
bestRepeatBitNum = repeatBitNum;
}
}
// Replace repetitions
if (bestRepeatNum > 1)
{
uint32_t encodedRepeatLength = bestRepeatLength - 2;
uint32_t encodedRepeatNum = bestRepeatNum - 2;
BitWriter symbol;
symbol.writeFixedEncodedValue(0, encoderSettings.pixelBitNum);
symbol.writeRiceEncodedValue(0, encoderSettings.runLengthBlackBitNum);
symbol.writeUnaryEncodedValue(encodedRepeatLength);
symbol.writeUnaryEncodedValue(encodedRepeatNum);
uint32_t compressedBitNum = symbol.getBitNum();
if (compressedBitNum < bestRepeatBitNum)
{
symbols.erase(symbols.begin() + symbolIndex +
bestRepeatLength,
symbols.begin() + symbolIndex +
bestRepeatLength * bestRepeatNum);
symbols.insert(symbols.begin() + symbolIndex,
symbol);
symbolIndex += bestRepeatLength;
}
}
}
// Write glyph data
BitWriter glyphBitstream;
glyphBitstream.writeFixedEncodedValue(glyph.left,
encoderSettings.boundingBoxLeftBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.bottom,
encoderSettings.boundingBoxBottomBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.width,
encoderSettings.boundingBoxWidthBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.height,
encoderSettings.boundingBoxHeightBitNum);
glyphBitstream.writeFixedEncodedValue(glyph.advance,
encoderSettings.advanceBitNum);
for (BitWriter &symbol : symbols)
glyphBitstream.write(symbol);
bitstream.writeVariableLengthWord(glyphBitstream.data.size());
bitstream.write(glyphBitstream);
}
void encodeFont(Font &font,
uint32_t pixelBitNum,
BitWriter &encodedFont)
{
// Analysis
uint32_t bestRunLengthBlackBitNum = UINT32_MAX;
uint32_t bestRunLengthWhiteBitNum = UINT32_MAX;
uint32_t bestEncodedSize = UINT32_MAX;
for (uint32_t runLengthBlackBitNum = 0;
runLengthBlackBitNum < 8;
runLengthBlackBitNum++)
{
for (uint32_t runLengthWhiteBitNum = 0;
runLengthWhiteBitNum < 8;
runLengthWhiteBitNum++)
{
// Get encoder settings
EncoderSettings encoderSettings;
encoderSettings.boundingBoxLeftBitNum =
getBitNum(font.boundingBoxLeftMinMax) + 1;
encoderSettings.boundingBoxBottomBitNum =
getBitNum(font.boundingBoxBottomMinMax) + 1;
encoderSettings.boundingBoxWidthBitNum =
getBitNum(font.boundingBoxWidthMinMax);
encoderSettings.boundingBoxHeightBitNum =
getBitNum(font.boundingBoxHeightMinMax);
encoderSettings.advanceBitNum =
getBitNum(font.advanceMinMax);
encoderSettings.pixelBitNum = pixelBitNum;
encoderSettings.runLengthBlackBitNum = runLengthBlackBitNum;
encoderSettings.runLengthWhiteBitNum = runLengthWhiteBitNum;
// Write header
BitWriter bitstream;
bitstream.writeShort(font.capHeight); // Font cap height (of uppercase letter A)
bitstream.writeShort(font.ascent); // Font ascent (from baseline to top of line)
bitstream.writeShort(font.descent); // Font descent (from baseline to bottom of line)
bitstream.writeShort(font.boundingBoxLeft); // Font bounding box left
bitstream.writeShort(font.boundingBoxBottom); // Font bounding box bottom
bitstream.writeShort(font.boundingBoxWidth); // Font bounding box width
bitstream.writeShort(font.boundingBoxHeight); // Font bounding box height
bitstream.writeByte(encoderSettings.boundingBoxLeftBitNum); // Bounding box left number of bits
bitstream.writeByte(encoderSettings.boundingBoxBottomBitNum); // Bounding box bottom number of bits
bitstream.writeByte(encoderSettings.boundingBoxWidthBitNum); // Bounding box width number of bits
bitstream.writeByte(encoderSettings.boundingBoxHeightBitNum); // Bounding box height number of bits
bitstream.writeByte(encoderSettings.advanceBitNum); // Advance number of bits
bitstream.writeByte(pixelBitNum); // Pixel number of bits
bitstream.writeByte(encoderSettings.runLengthBlackBitNum); // Repeat black number of bits
bitstream.writeByte(encoderSettings.runLengthWhiteBitNum); // Repeat white number of bits
// Write glyphs
BitWriter blockBitstream;
Charcode blockCharcode;
Charcode nextCharcode = -1;
for (auto &entry : font.glyphs)
{
Charcode charcode = entry.first;
Glyph &glyph = entry.second;
// Encode block
if ((charcode != nextCharcode) ||
(charcode == 'A') ||
(charcode == 'a'))
{
if (nextCharcode != -1)
{
bitstream.writeVariableLengthWord(blockBitstream.data.size());
bitstream.writeVariableLengthWord(blockCharcode);
bitstream.write(blockBitstream);
blockBitstream.clear();
}
blockCharcode = charcode;
}
nextCharcode = charcode + 1;
// Encode glyph
encodeGlyph(glyph, encoderSettings, blockBitstream);
}
// Last block
bitstream.writeVariableLengthWord(blockBitstream.data.size());
bitstream.writeVariableLengthWord(blockCharcode);
bitstream.write(blockBitstream);
bitstream.writeVariableLengthWord(0);
// Update best
if (bitstream.getBitNum() < bestEncodedSize)
{
bestRunLengthBlackBitNum = runLengthBlackBitNum;
bestRunLengthWhiteBitNum = runLengthWhiteBitNum;
bestEncodedSize = bitstream.getBitNum();
encodedFont = bitstream;
}
}
}
}