Add anti-aliased font rendering with dithering

Replace solid black rendering of anti-aliased font edges with period-2 dithering patterns. Full coverage pixels render as solid ink, while partial coverage pixels use checkerboard patterns (50% for dark grey, 25% for light grey) matching fillRectDither behavior. This prevents edge pixels from bolding to black on BW displays and produces proper anti-aliasing for 2-bit fonts.
This commit is contained in:
Justin Mitchell
2026-07-05 16:06:56 -04:00
parent 3e1dd31e53
commit 62448b3c08
9 changed files with 17947 additions and 11245 deletions
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
+9
View File
@@ -99,3 +99,12 @@ ruby -rdigest -e 'puts [
"./notosans_8_regular.h",
].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
))"
echo ""
echo "// Font ID 0 is reserved as the \"not found\" sentinel."
echo "// Guard against any hash accidentally producing 0."
for id in NOTOSERIF_12 NOTOSERIF_14 NOTOSERIF_16 NOTOSERIF_18 \
NOTOSANS_12 NOTOSANS_14 NOTOSANS_16 NOTOSANS_18 \
UI_10 UI_12 SMALL; do
echo "static_assert(${id}_FONT_ID != 0, \"Font ID collision with sentinel\");"
done
+5 -2
View File
@@ -42,8 +42,11 @@ for size in ${UI_FONT_SIZES[@]}; do
# (fontstack is ordered by descending priority).
viet_path="../builtinFonts/source/Ubuntu/Ubuntu-Vietnamese-${style}.ttf"
output_path="../builtinFonts/${font_name}.h"
# 2-bit like the reader fonts: full coverage renders solid in BW mode and
# partial edge coverage dithers (GfxRenderer renderCharImpl), so UI text is
# anti-aliased on 1-bit panels and blends properly in grayscale passes.
python fontconvert.py $font_name $size $font_path $hebrew_path $viet_path \
--additional-intervals 0x05D0,0x05EA > $output_path
--2bit --additional-intervals 0x05D0,0x05EA > $output_path
echo "Generated $output_path"
done
done
@@ -51,7 +54,7 @@ done
python fontconvert.py notosans_8_regular 8 \
../builtinFonts/source/NotoSans/NotoSans-Regular.ttf \
../builtinFonts/source/NotoSansHebrew/NotoSansHebrew-Regular.ttf \
--additional-intervals 0x05D0,0x05EA > ../builtinFonts/notosans_8_regular.h
--2bit --additional-intervals 0x05D0,0x05EA > ../builtinFonts/notosans_8_regular.h
echo ""
echo "Running compression verification..."
+9 -2
View File
@@ -282,8 +282,15 @@ static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode
const uint8_t bmpVal = 3 - ((byte >> bit_index) & 0x3);
if (renderMode == GfxRenderer::BW && bmpVal < 3) {
// Black (also paints over the grays in BW mode)
renderer.drawPixel(screenX, screenY, pixelState);
// Full coverage is solid ink; partial (anti-aliased edge) coverage
// dithers with the same period-2 patterns as fillRectDither
// (dark grey = 50% checkerboard, light grey = 25%), so 2-bit fonts
// render anti-aliased on BW passes instead of bolding every edge
// pixel to black. Skipped pixels leave the background untouched.
if (bmpVal == 0 || (bmpVal == 1 && ((screenX + screenY) & 1) == 0) ||
(bmpVal == 2 && (screenX & 1) == 0 && (screenY & 1) == 0)) {
renderer.drawPixel(screenX, screenY, pixelState);
}
} else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
// Light gray (also mark the MSB if it's going to be a dark gray too)
// Dedicated X3 gray LUTs now provide proper 4-level gray on both devices
+3 -3
View File
@@ -9,9 +9,9 @@
#define NOTOSANS_14_FONT_ID (-1589315735)
#define NOTOSANS_16_FONT_ID (1669013660)
#define NOTOSANS_18_FONT_ID (37077304)
#define UI_10_FONT_ID (22918846)
#define UI_12_FONT_ID (1635686837)
#define SMALL_FONT_ID (674098198)
#define UI_10_FONT_ID (-1901121527)
#define UI_12_FONT_ID (577549789)
#define SMALL_FONT_ID (-1925899166)
// Font ID 0 is reserved as the "not found" sentinel.
// Guard against any hash accidentally producing 0.