#9517

FreeType: bake color glyphs (COLR/COLRv1/sbix) as BGRA instead of an empty bitmap

Opens-celleswants to merge intofrom

Problem

When ImGuiFreeTypeLoaderFlags_LoadColor is set, color fonts (COLR / COLRv1, sbix, CBDT) do not render through the FreeType builder: the glyph bakes as an empty bitmap and AddText draws nothing. Color emoji (e.g. a Twemoji Mozilla COLRv0 font at U+1F600) is the most visible victim.

Root cause

ImGui_ImplFreeType_FontSrcData::InitFont adds FT_LOAD_COLOR to the FreeType LoadFlags but not FT_LOAD_RENDER, so FT_Load_Glyph leaves the glyph unrendered. ImGui_ImplFreeType_FontBakedLoadGlyph then unconditionally calls:

FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL);

FT_RENDER_MODE_NORMAL renders the outline only. For pure-COLR glyphs (no traditional outline, only color layers) the result is an empty bitmap (w == 0 && h == 0), is_visible is false, no atlas rect is packed, and the glyph never rasterizes. Even for COLR glyphs that do have a base outline, the color layers are discarded, so the emoji renders monochrome instead of color.

Fix

Two small changes in misc/freetype/imgui_freetype.cpp:

  1. In InitFont, when LoadColor is set, also add FT_LOAD_RENDER to LoadFlags. FreeType then auto-renders during FT_Load_Glyph: color glyphs come back as a FT_PIXEL_MODE_BGRA bitmap with slot->format == FT_GLYPH_FORMAT_BITMAP.
  2. In FontBakedLoadGlyph, only call FT_Render_Glyph when the glyph is not already a bitmap (slot->format != FT_GLYPH_FORMAT_BITMAP). This avoids re-rendering an already-rendered color bitmap with FT_RENDER_MODE_NORMAL, which would discard the BGRA pixels.

Monochrome fonts unaffected

Fonts without color layers (the common case) ignore FT_LOAD_COLOR; slot->format stays FT_GLYPH_FORMAT_OUTLINE after FT_Load_Glyph, so the existing FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL) path runs exactly as before. No behavior change for ASCII / CJK / etc.

MCVE (minimal, pastable into an example app)

Build any example with IMGUI_ENABLE_FREETYPE defined (so the FreeType builder is used). Drop this in the example's main.cpp right after the ImGui context is created (point COLOR_FONT_PATH at a COLR/sbix color font, e.g. Twemoji Mozilla COLRv0, or Apple Color Emoji):

#define IMGUI_ENABLE_FREETYPE  // in your build, alongside linking FreeType
#include "imgui.h"
// ...
{
    // After ImGui::CreateContext():
    ImFontConfig cfg;
    cfg.FontLoaderFlags = ImGuiFreeTypeLoaderFlags_LoadColor; // ImGuiFreeTypeBuilderFlags_LoadColor on older branches
    static const ImWchar32 ranges[] = { 0x1F600, 0x1F64F, 0x2764, 0x2764, 0 }; // 😀..🙂 + ❤
    io.Fonts->AddFontFromFileTTF(COLOR_FONT_PATH, 32.0f, &cfg, ranges);
}

then in your UI:

ImGui::Text("\xF0\x9F\x98\x80 \xE2\x9D\xA4"); // "😀 ❤" -- UTF-8 literals for portability
  • before this PR: the emoji render blank (empty bitmap baked, AddText draws nothing);
  • after this PR: they render as colored bitmaps (BGRA atlas texture).

You can confirm the bake with a quick check after the first frame:

ImFontBaked* baked = io.Fonts->GetFont(0)->GetFontBaked(io.Fonts->GetFont(0)->LegacySize);
if (const ImFontGlyph* g = baked ? baked->FindGlyph((ImWchar)0x1F600) : nullptr)
    ImGui::Text("😀 Colored=%d Visible=%d %dx%d", g->Colored, g->Visible, (int)(g->X1 - g->X0), (int)(g->Y1 - g->Y0));

Validation

Built cimgui with IMGUI_ENABLE_FREETYPE + IMGUI_USE_WCHAR32 against FreeType 2.13.4 and loaded a Twemoji Mozilla COLRv0 font with LoadColor:

  • before: FT_Load_Glyph(U+2764, FT_LOAD_COLOR|FT_LOAD_RENDER) → 0x0 bitmap via the old code path → ❤️/😀 render blank;
  • after: BGRA bitmap (pixel_mode == FT_PIXEL_MODE_BGRA), out_glyph->Colored = true, AddText renders the color glyph.

The patch compiles cleanly against master (clang, -DIMGUI_ENABLE_FREETYPE, FreeType 2.13.4 headers).

A downstream TUI cell-grid renderer (ManyUI / ManyUICImGui, ImGui 1.92 docking via cimgui-pack) renders ❤️ ☝️ 😀 in color with this change where it previously rendered the □ placeholder.

AI-assisted.