Handle downscaled LCD glyphs with a dedicated resize path before atlas upload. This preserves row stride and RGB coverage, fixing corrupted icon-font glyphs at small sizes.

Add a golden-image regression test covering scaled subpixel glyphs from the Nonicons font.
This commit is contained in:
Martín Lucas Golini
2026-07-26 00:23:24 -03:00
parent 521e2af2e2
commit 0bf2b89970
3 changed files with 73 additions and 13 deletions
+43 -13
View File
@@ -1621,20 +1621,50 @@ Glyph FontTrueType::loadGlyphByIndex( Uint32 index, unsigned int characterSize,
destHeight = dest.getHeight() + 2 * padding;
}
} else if ( bitmap.pixel_mode == FT_PIXEL_MODE_LCD ) {
for ( int y = padding; y < height - padding; ++y ) {
for ( int x = padding; x < width - padding; ++x ) {
const std::size_t index = ( x + y * width ) * 4;
const Uint8* px = &pixels[( x - padding ) * 3];
mPixelBuffer[index + 0] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[0] : 255;
mPixelBuffer[index + 1] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[1] : 255;
mPixelBuffer[index + 2] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[2] : 255;
mPixelBuffer[index + 3] =
(Uint8)( ( (int)px[0] + (int)px[1] + (int)px[2] ) / 3.f );
if ( scale < 1.f ) {
for ( int y = 0; y < height; ++y ) {
for ( int x = 0; x < width; ++x ) {
const std::size_t index = ( x + y * width ) * 4;
const Uint8* px = &pixels[x * 3];
mPixelBuffer[index + 0] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[0] : 255;
mPixelBuffer[index + 1] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[1] : 255;
mPixelBuffer[index + 2] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[2] : 255;
mPixelBuffer[index + 3] =
(Uint8)( ( (int)px[0] + (int)px[1] + (int)px[2] ) / 3.f );
}
pixels += bitmap.pitch;
}
Image dest( &mPixelBuffer[0], width, height, 4 );
dest.avoidFreeImage( true );
dest.scale( scale );
dest.avoidFreeImage( true );
pixelPtr = dest.getPixels();
glyph.bounds.Left *= scale;
glyph.bounds.Right *= scale;
glyph.bounds.Top *= scale;
glyph.bounds.Bottom *= scale;
destWidth = dest.getWidth() + 2 * padding;
destHeight = dest.getHeight() + 2 * padding;
} else {
for ( int y = padding; y < height - padding; ++y ) {
for ( int x = padding; x < width - padding; ++x ) {
const std::size_t index = ( x + y * width ) * 4;
const Uint8* px = &pixels[( x - padding ) * 3];
mPixelBuffer[index + 0] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[0] : 255;
mPixelBuffer[index + 1] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[1] : 255;
mPixelBuffer[index + 2] =
glyph.renderMode == GlyphRenderMode::Subpixel ? px[2] : 255;
mPixelBuffer[index + 3] =
(Uint8)( ( (int)px[0] + (int)px[1] + (int)px[2] ) / 3.f );
}
pixels += bitmap.pitch;
}
pixels += bitmap.pitch;
}
} else {
if ( scale < 1.f ) {
@@ -165,6 +165,36 @@ UTEST( FontRendering, subpixelCoverageCompositesPerChannel ) {
"Subpixel text did not update a transparent target's alpha" );
}
UTEST( FontRendering, scaledSubpixelGlyphAtlas ) {
UIApplication app(
WindowSettings( 256, 64, "eepp - Scaled Subpixel Glyph Atlas", VisualTestWindowStyle,
WindowBackend::Default, 32 ),
UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) );
ResourceScope& scope = *app.getUI()->getResourceScope();
FontTrueTypePtr font = FontTrueType::New( "ScaledSubpixelNonicons", scope );
ASSERT_TRUE( font->loadFromFile( Sys::getProcessPath() + "../assets/fonts/nonicons.ttf" ) );
font->setAntialiasing( FontAntialiasing::Subpixel );
font->setIsEmojiFont( true );
EE::Window::Window* window = app.getWindow();
window->setClearColor( Color( 40, 44, 52 ) );
window->clear();
const std::array<Uint32, 8> codePoints = { 61718, 61719, 61720, 61743,
61752, 61775, 61789, 61799 };
Float x = 8.f;
for ( Uint32 codePoint : codePoints ) {
GlyphDrawable* glyph = font->getGlyphDrawable( codePoint, 18 );
ASSERT_TRUE( glyph );
ASSERT_EQ( GlyphRenderMode::Subpixel, glyph->getGlyphRenderMode() );
const Sizef size = glyph->getPixelsSize();
glyph->setColor( Color::White );
glyph->draw( { std::trunc( x + ( 24.f - size.getWidth() ) * 0.5f ),
std::trunc( ( 64.f - size.getHeight() ) * 0.5f ) } );
x += 30.f;
}
compareImages( utest_state, utest_result, window, "eepp-scaled-subpixel-glyph-atlas" );
}
UTEST( FontRendering, loadingFontFamilyDoesNotCreateTexturePages ) {
UIApplication app(
WindowSettings( 320, 240, "eepp - Font Family Metrics Test", WindowStyle::Default,