First init.
This commit is contained in:
315
libraries/mcu-renderer/tools/fontconv/encode.cpp
Normal file
315
libraries/mcu-renderer/tools/fontconv/encode.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user