imgui_freetype: Add support for colored glyphs. Font: add support for untinted glyphs (#3369)

Amend 9499afd with missing static inline.
This commit is contained in:
ocornut 2021-01-28 17:26:41 +01:00
parent 24aa6654df
commit 24be26e00e
6 changed files with 44 additions and 30 deletions

View File

@ -53,10 +53,13 @@ Other Changes:
other than compiling misc/freetype/imgui_freetype.cpp and linking with FreeType. other than compiling misc/freetype/imgui_freetype.cpp and linking with FreeType.
- Use '#define IMGUI_ENABLE_STB_TRUETYPE' if you somehow need the stb_truetype rasterizer to be - Use '#define IMGUI_ENABLE_STB_TRUETYPE' if you somehow need the stb_truetype rasterizer to be
compiled in along with the FreeType one, otherwise it is enabled by default. compiled in along with the FreeType one, otherwise it is enabled by default.
- imgui_freetype: Added support for colored glyphs as supported by Freetype 2.10+ (for .ttf using CPAL/COLR
tables only). Enable the ImGuiFreeTypeBuilderFlags_LoadColor on a given font. Atlas always output directly
as RGBA8 in this situation. Likely to make sense with IMGUI_USE_WCHAR32. (#3369) [@pshurgal]
- Fonts: Fixed CalcTextSize() width rounding so it behaves more like a ceil. This is in order for text wrapping - Fonts: Fixed CalcTextSize() width rounding so it behaves more like a ceil. This is in order for text wrapping
to have enough space when provided width precisely calculated with CalcTextSize().x. (#3776) to have enough space when provided width precisely calculated with CalcTextSize().x. (#3776)
Note that the rounding of either positions and widths are technically undesirable (e.g. #3437, #791) but Note that the rounding of either positions and widths are technically undesirable (e.g. #3437, #791) but
variety of code is currently on it so we are first fixing current behavior before we'll eventually chhnge it. variety of code is currently on it so we are first fixing current behavior before we'll eventually change it.
- ImDrawList: Fixed AddCircle()/AddCircleFilled() with (rad > 0.0f && rad < 1.0f && num_segments == 0). (#3738) - ImDrawList: Fixed AddCircle()/AddCircleFilled() with (rad > 0.0f && rad < 1.0f && num_segments == 0). (#3738)
Would lead to a buffer read overflow. Would lead to a buffer read overflow.
- Backends: Win32: Dynamically loading XInput DLL instead of linking with it, facilite compiling with - Backends: Win32: Dynamically loading XInput DLL instead of linking with it, facilite compiling with

View File

@ -2504,8 +2504,9 @@ struct ImFontConfig
// (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this) // (Note: some language parsers may fail to convert the 31+1 bitfield members, in this case maybe drop store a single u32 or we can rework this)
struct ImFontGlyph struct ImFontGlyph
{ {
unsigned int Codepoint : 31; // 0x0000..0xFFFF unsigned int Colored : 1; // Flag to indicate glyph is colored and should generally ignore tinting (make it usable with no shift on little-endian as this is used in loops)
unsigned int Visible : 1; // Flag to allow early out when rendering unsigned int Visible : 1; // Flag to indicate glyph has no visible pixels (e.g. space). Allow early out when rendering.
unsigned int Codepoint : 30; // 0x0000..0x10FFFF
float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in) float AdvanceX; // Distance to next character (= data from font + ImFontConfig::GlyphExtraSpacing.x baked in)
float X0, Y0, X1, Y1; // Glyph corners float X0, Y0, X1, Y1; // Glyph corners
float U0, V0, U1, V1; // Texture coordinates float U0, V0, U1, V1; // Texture coordinates

View File

@ -3065,6 +3065,7 @@ void ImFont::AddGlyph(const ImFontConfig* cfg, ImWchar codepoint, float x0, floa
ImFontGlyph& glyph = Glyphs.back(); ImFontGlyph& glyph = Glyphs.back();
glyph.Codepoint = (unsigned int)codepoint; glyph.Codepoint = (unsigned int)codepoint;
glyph.Visible = (x0 != x1) && (y0 != y1); glyph.Visible = (x0 != x1) && (y0 != y1);
glyph.Colored = false;
glyph.X0 = x0; glyph.X0 = x0;
glyph.Y0 = y0; glyph.Y0 = y0;
glyph.X1 = x1; glyph.X1 = x1;
@ -3315,6 +3316,8 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col
const ImFontGlyph* glyph = FindGlyph(c); const ImFontGlyph* glyph = FindGlyph(c);
if (!glyph || !glyph->Visible) if (!glyph || !glyph->Visible)
return; return;
if (glyph->Colored)
col |= ~IM_COL32_A_MASK;
float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f; float scale = (size >= 0.0f) ? (size / FontSize) : 1.0f;
pos.x = IM_FLOOR(pos.x); pos.x = IM_FLOOR(pos.x);
pos.y = IM_FLOOR(pos.y); pos.y = IM_FLOOR(pos.y);
@ -3377,6 +3380,8 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col
ImDrawIdx* idx_write = draw_list->_IdxWritePtr; ImDrawIdx* idx_write = draw_list->_IdxWritePtr;
unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx;
const ImU32 col_untinted = col | ~IM_COL32_A_MASK;
while (s < text_end) while (s < text_end)
{ {
if (word_wrap_enabled) if (word_wrap_enabled)
@ -3482,14 +3487,17 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col
} }
} }
// Support for untinted glyphs
ImU32 glyph_col = glyph->Colored ? col_untinted : col;
// We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here: // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here:
{ {
idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2);
idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3);
vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1; vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = glyph_col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1;
vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1; vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = glyph_col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1;
vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2; vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = glyph_col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2;
vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2; vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = glyph_col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2;
vtx_write += 4; vtx_write += 4;
vtx_current_idx += 4; vtx_current_idx += 4;
idx_write += 6; idx_write += 6;

View File

@ -2509,7 +2509,8 @@ IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent);
IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque);
IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int atlas_x, int atlas_y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value);
IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value);
IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor);
IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride);

View File

@ -6,6 +6,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2021/01/28: added support for color-layered glyphs via ImGuiFreeTypeBuilderFlags_LoadColor (require Freetype 2.10+).
// 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'. // 2021/01/26: simplified integration by using '#define IMGUI_ENABLE_FREETYPE'.
// renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas(). // renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API. removed ImGuiFreeType::BuildFontAtlas().
// 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails. // 2020/06/04: fix for rare case where FT_Get_Char_Index() succeed but FT_Load_Glyph() fails.
@ -96,7 +97,7 @@ namespace
// | | // | |
// |------------- advanceX ----------->| // |------------- advanceX ----------->|
/// A structure that describe a glyph. // A structure that describe a glyph.
struct GlyphInfo struct GlyphInfo
{ {
int Width; // Glyph's width in pixels. int Width; // Glyph's width in pixels.
@ -104,6 +105,7 @@ namespace
FT_Int OffsetX; // The distance from the origin ("pen position") to the left of the glyph. FT_Int OffsetX; // The distance from the origin ("pen position") to the left of the glyph.
FT_Int OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0. FT_Int OffsetY; // The distance from the origin to the top of the glyph. This is usually a value < 0.
float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0. float AdvanceX; // The distance from the origin to the origin of the next glyph. This is usually a value > 0.
bool IsColored;
}; };
// Font parameters and metrics. // Font parameters and metrics.
@ -252,6 +254,7 @@ namespace
out_glyph_info->OffsetX = Face->glyph->bitmap_left; out_glyph_info->OffsetX = Face->glyph->bitmap_left;
out_glyph_info->OffsetY = -Face->glyph->bitmap_top; out_glyph_info->OffsetY = -Face->glyph->bitmap_top;
out_glyph_info->AdvanceX = (float)FT_CEIL(slot->advance.x); out_glyph_info->AdvanceX = (float)FT_CEIL(slot->advance.x);
out_glyph_info->IsColored = (ft_bitmap->pixel_mode == FT_PIXEL_MODE_BGRA);
return ft_bitmap; return ft_bitmap;
} }
@ -271,18 +274,14 @@ namespace
if (multiply_table == NULL) if (multiply_table == NULL)
{ {
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
{
for (uint32_t x = 0; x < w; x++) for (uint32_t x = 0; x < w; x++)
dst[x] = IM_COL32(255, 255, 255, src[x]); dst[x] = IM_COL32(255, 255, 255, src[x]);
}
} }
else else
{ {
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
{
for (uint32_t x = 0; x < w; x++) for (uint32_t x = 0; x < w; x++)
dst[x] = IM_COL32(255, 255, 255, multiply_table[src[x]]); dst[x] = IM_COL32(255, 255, 255, multiply_table[src[x]]);
}
} }
break; break;
} }
@ -305,33 +304,28 @@ namespace
} }
case FT_PIXEL_MODE_BGRA: case FT_PIXEL_MODE_BGRA:
{ {
// FIXME: Converting pre-multiplied alpha to straight. Doesn't smell good.
#define DE_MULTIPLY(color, alpha) (ImU32)(255.0f * (float)color / (float)alpha + 0.5f) #define DE_MULTIPLY(color, alpha) (ImU32)(255.0f * (float)color / (float)alpha + 0.5f)
if (multiply_table == NULL) if (multiply_table == NULL)
{ {
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
{
for (uint32_t x = 0; x < w; x++) for (uint32_t x = 0; x < w; x++)
dst[x] = IM_COL32( {
DE_MULTIPLY(src[x * 4 + 2], src[x * 4 + 3]), uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
DE_MULTIPLY(src[x * 4 + 1], src[x * 4 + 3]), dst[x] = IM_COL32(DE_MULTIPLY(r, a), DE_MULTIPLY(g, a), DE_MULTIPLY(b, a), a);
DE_MULTIPLY(src[x * 4], src[x * 4 + 3]), }
src[x * 4 + 3]);
}
} }
else else
{ {
for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch) for (uint32_t y = 0; y < h; y++, src += src_pitch, dst += dst_pitch)
{ {
for (uint32_t x = 0; x < w; x++) for (uint32_t x = 0; x < w; x++)
dst[x] = IM_COL32( {
multiply_table[DE_MULTIPLY(src[x * 4 + 2], src[x * 4 + 3])], uint8_t r = src[x * 4 + 2], g = src[x * 4 + 1], b = src[x * 4], a = src[x * 4 + 3];
multiply_table[DE_MULTIPLY(src[x * 4 + 1], src[x * 4 + 3])], dst[x] = IM_COL32(multiply_table[DE_MULTIPLY(r, a)], multiply_table[DE_MULTIPLY(g, a)], multiply_table[DE_MULTIPLY(b, a)], multiply_table[a]);
multiply_table[DE_MULTIPLY(src[x * 4], src[x * 4 + 3])], }
multiply_table[src[x * 4 + 3]]);
} }
} }
#undef DE_MULTIPLY #undef DE_MULTIPLY
break; break;
} }
@ -359,6 +353,8 @@ struct ImFontBuildSrcGlyphFT
GlyphInfo Info; GlyphInfo Info;
uint32_t Codepoint; uint32_t Codepoint;
unsigned int* BitmapData; // Point within one of the dst_tmp_bitmap_buffers[] array unsigned int* BitmapData; // Point within one of the dst_tmp_bitmap_buffers[] array
ImFontBuildSrcGlyphFT() { memset(this, 0, sizeof(*this)); }
}; };
struct ImFontBuildSrcDataFT struct ImFontBuildSrcDataFT
@ -396,6 +392,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
atlas->ClearTexData(); atlas->ClearTexData();
// Temporary storage for building // Temporary storage for building
bool src_load_color = false;
ImVector<ImFontBuildSrcDataFT> src_tmp_array; ImVector<ImFontBuildSrcDataFT> src_tmp_array;
ImVector<ImFontBuildDstDataFT> dst_tmp_array; ImVector<ImFontBuildDstDataFT> dst_tmp_array;
src_tmp_array.resize(atlas->ConfigData.Size); src_tmp_array.resize(atlas->ConfigData.Size);
@ -425,6 +422,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
return false; return false;
// Measure highest codepoints // Measure highest codepoints
src_load_color |= (cfg.FontBuilderFlags & ImGuiFreeTypeBuilderFlags_LoadColor) != 0;
ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex]; ImFontBuildDstDataFT& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault(); src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault();
for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2) for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
@ -476,7 +474,6 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
if (entries_32 & ((ImU32)1 << bit_n)) if (entries_32 & ((ImU32)1 << bit_n))
{ {
ImFontBuildSrcGlyphFT src_glyph; ImFontBuildSrcGlyphFT src_glyph;
memset(&src_glyph, 0, sizeof(src_glyph));
src_glyph.Codepoint = (ImWchar)(((it - it_begin) << 5) + bit_n); src_glyph.Codepoint = (ImWchar)(((it - it_begin) << 5) + bit_n);
//src_glyph.GlyphIndex = 0; // FIXME-OPT: We had this info in the previous step and lost it.. //src_glyph.GlyphIndex = 0; // FIXME-OPT: We had this info in the previous step and lost it..
src_tmp.GlyphsList.push_back(src_glyph); src_tmp.GlyphsList.push_back(src_glyph);
@ -595,7 +592,7 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
// 7. Allocate texture // 7. Allocate texture
atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight);
atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight);
if (extra_flags & ImGuiFreeTypeBuilderFlags_LoadColor) if (src_load_color)
{ {
atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight * 4); atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(atlas->TexWidth * atlas->TexHeight * 4);
memset(atlas->TexPixelsRGBA32, 0, atlas->TexWidth * atlas->TexHeight * 4); memset(atlas->TexPixelsRGBA32, 0, atlas->TexWidth * atlas->TexHeight * 4);
@ -670,6 +667,10 @@ bool ImFontAtlasBuildWithFreeTypeEx(FT_Library ft_library, ImFontAtlas* atlas, u
float u1 = (tx + info.Width) / (float)atlas->TexWidth; float u1 = (tx + info.Width) / (float)atlas->TexWidth;
float v1 = (ty + info.Height) / (float)atlas->TexHeight; float v1 = (ty + info.Height) / (float)atlas->TexHeight;
dst_font->AddGlyph(&cfg, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX); dst_font->AddGlyph(&cfg, (ImWchar)src_glyph.Codepoint, x0, y0, x1, y1, u0, v0, u1, v1, info.AdvanceX);
IM_ASSERT(dst_font->Glyphs.back().Codepoint == src_glyph.Codepoint);
if (src_glyph.Info.IsColored)
dst_font->Glyphs.back().Colored = true;
} }
src_tmp.Rects = NULL; src_tmp.Rects = NULL;

View File

@ -44,6 +44,6 @@ namespace ImGuiFreeType
// Obsolete names (will be removed soon) // Obsolete names (will be removed soon)
// Prefer using '#define IMGUI_ENABLE_FREETYPE' // Prefer using '#define IMGUI_ENABLE_FREETYPE'
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontBuilderFlags = flags; return atlas->Build(); } static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontBuilderFlags = flags; return atlas->Build(); }
#endif #endif
} }