Skip to content

Commit f8e941e

Browse files
committed
Make FreeType select a size for fixed sized fonts
and scale it down before adding it to atlas to save on atlas space.
1 parent b46f099 commit f8e941e

File tree

1 file changed

+103
-22
lines changed

1 file changed

+103
-22
lines changed

misc/freetype/imgui_freetype.cpp

Lines changed: 103 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ static FT_Error ImGuiLunasvgPortPresetSlot(FT_GlyphSlot slot, FT_Bool cache, FT_
108108
//-------------------------------------------------------------------------
109109

110110
#define FT_CEIL(X) (((X + 63) & -64) / 64) // From SDL_ttf: Handy routines for converting from fixed point
111-
#define FT_SCALEFACTOR 64.0f
111+
#define FT_SCALEFACTOR 64.0f // For converting from/to 26.6 factionals
112112

113113
// Glyph metrics:
114114
// --------------
@@ -435,19 +435,58 @@ static bool ImGui_ImplFreeType_FontBakedInit(ImFontAtlas* atlas, ImFontConfig* s
435435

436436
FT_New_Size(bd_font_data->FtFace, &bd_baked_data->FtSize);
437437
FT_Activate_Size(bd_baked_data->FtSize);
438-
439-
// Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
440-
// is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me.
441-
// FT_Set_Pixel_Sizes() doesn't seem to get us the same result."
442-
// (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL)
443438
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
444-
FT_Size_RequestRec req;
445-
req.type = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
446-
req.width = 0;
447-
req.height = (uint32_t)(size * 64 * rasterizer_density);
448-
req.horiResolution = 0;
449-
req.vertResolution = 0;
450-
FT_Request_Size(bd_font_data->FtFace, &req);
439+
if (((bd_font_data->FtFace->face_flags & FT_FACE_FLAG_FIXED_SIZES) != 0) && ((bd_font_data->FtFace->face_flags & FT_FACE_FLAG_SCALABLE) == 0) && ((bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) != 0))
440+
{
441+
IM_ASSERT(bd_font_data->FtFace->num_fixed_sizes > 0);
442+
443+
// Loop over sizes and pick the closest, larger (or better equal) size.
444+
int best_index = 0;
445+
float best_height = bd_font_data->FtFace->available_sizes[best_index].y_ppem / FT_SCALEFACTOR;
446+
for (int i = 1; i < bd_font_data->FtFace->num_fixed_sizes; i++)
447+
{
448+
const float cur_height = bd_font_data->FtFace->available_sizes[i].y_ppem / FT_SCALEFACTOR;
449+
// TODO: is this overkill?
450+
// TODO: is-float-close with epsilon param would be nice, maybe
451+
if (ImFabs(cur_height - size) < 0.001f)
452+
{
453+
best_index = i;
454+
break;
455+
}
456+
else if (cur_height < size)
457+
{
458+
if (best_height < cur_height)
459+
{
460+
best_index = i;
461+
best_height = cur_height;
462+
}
463+
}
464+
else
465+
{
466+
if (best_height > cur_height)
467+
{
468+
best_index = i;
469+
best_height = cur_height;
470+
}
471+
}
472+
}
473+
FT_Select_Size(bd_font_data->FtFace, best_index);
474+
}
475+
else
476+
{
477+
// Vuhdo 2017: "I'm not sure how to deal with font sizes properly. As far as I understand, currently ImGui assumes that the 'pixel_height'
478+
// is a maximum height of an any given glyph, i.e. it's the sum of font's ascender and descender. Seems strange to me.
479+
// FT_Set_Pixel_Sizes() doesn't seem to get us the same result."
480+
// (FT_Set_Pixel_Sizes() essentially calls FT_Request_Size() with FT_SIZE_REQUEST_TYPE_NOMINAL)
481+
482+
FT_Size_RequestRec req;
483+
req.type = (bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) ? FT_SIZE_REQUEST_TYPE_NOMINAL : FT_SIZE_REQUEST_TYPE_REAL_DIM;
484+
req.width = 0;
485+
req.height = (uint32_t)(size * FT_SCALEFACTOR * rasterizer_density);
486+
req.horiResolution = 0;
487+
req.vertResolution = 0;
488+
FT_Request_Size(bd_font_data->FtFace, &req);
489+
}
451490

452491
// Output
453492
if (src->MergeMode == false)
@@ -497,9 +536,21 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
497536
FT_Face face = bd_font_data->FtFace;
498537
FT_GlyphSlot slot = face->glyph;
499538
const float rasterizer_density = src->RasterizerDensity * baked->RasterizerDensity;
539+
float bitmap_x_scale = 1.f;
540+
float bitmap_y_scale = 1.f;
541+
if (((face->face_flags & FT_FACE_FLAG_FIXED_SIZES) != 0) && ((face->face_flags & FT_FACE_FLAG_SCALABLE) == 0) && ((bd_font_data->UserFlags & ImGuiFreeTypeLoaderFlags_Bitmap) != 0))
542+
{
543+
// TODO: what if this just means invisible?
544+
IM_ASSERT(bd_font_data->FtFace->size->metrics.x_ppem > 0);
545+
IM_ASSERT(bd_font_data->FtFace->size->metrics.y_ppem > 0);
546+
547+
// Scale fixed size bitmap to target size
548+
bitmap_x_scale = baked->Size / bd_font_data->FtFace->size->metrics.x_ppem;
549+
bitmap_y_scale = baked->Size / bd_font_data->FtFace->size->metrics.y_ppem;
550+
}
500551

501552
// Load metrics only mode
502-
const float advance_x = (slot->advance.x / FT_SCALEFACTOR) / rasterizer_density;
553+
const float advance_x = ((slot->advance.x / FT_SCALEFACTOR) * bitmap_x_scale) / rasterizer_density;
503554
if (out_advance_x != NULL)
504555
{
505556
IM_ASSERT(out_glyph == NULL);
@@ -514,9 +565,9 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
514565
if (error != 0 || ft_bitmap == nullptr)
515566
return false;
516567

517-
const int w = (int)ft_bitmap->width;
518-
const int h = (int)ft_bitmap->rows;
519-
const bool is_visible = (w != 0 && h != 0);
568+
const int bitmap_w = (int)ft_bitmap->width;
569+
const int bitmap_h = (int)ft_bitmap->rows;
570+
const bool is_visible = (bitmap_w != 0 && bitmap_h != 0);
520571

521572
// Prepare glyph
522573
out_glyph->Codepoint = codepoint;
@@ -525,6 +576,11 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
525576
// Pack and retrieve position inside texture atlas
526577
if (is_visible)
527578
{
579+
const int w = (int)ImFloor(bitmap_w * bitmap_x_scale);
580+
const int h = (int)ImFloor(bitmap_h * bitmap_y_scale);
581+
IM_ASSERT(!((h < bitmap_h && w > bitmap_w) || (h > bitmap_h && w < bitmap_w))); // Can't up AND downscale at the same time // TODO: or can we?
582+
const bool down_scaling = h < bitmap_h || w < bitmap_w;
583+
528584
ImFontAtlasRectId pack_id = ImFontAtlasPackAddRect(atlas, w, h);
529585
if (pack_id == ImFontAtlasRectId_Invalid)
530586
{
@@ -534,10 +590,35 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
534590
}
535591
ImTextureRect* r = ImFontAtlasPackGetRect(atlas, pack_id);
536592

537-
// Render pixels to our temporary buffer
538-
atlas->Builder->TempBuffer.resize(w * h * 4);
593+
// Render pixels to our temporary buffer, while making sure we have space for an extra copy used during downscaling.
594+
atlas->Builder->TempBuffer.resize(((down_scaling ? bitmap_w * bitmap_h : 0) + w * h) * 4);
539595
uint32_t* temp_buffer = (uint32_t*)atlas->Builder->TempBuffer.Data;
540-
ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, w);
596+
// Blit (and convert) into the first bm_w * bm_h * 4 bytes.
597+
ImGui_ImplFreeType_BlitGlyph(ft_bitmap, temp_buffer, bitmap_w);
598+
599+
if (down_scaling)
600+
{
601+
uint32_t* dst_buffer = temp_buffer + bitmap_w * bitmap_h;
602+
// Perform downscale, from temp_buffer (bitmap_w * bitmap_h) to dst_buffer (w * h)
603+
604+
#if 1
605+
// Point Sampling / Nearest Neighbor
606+
for (int y = 0; y < h; y++)
607+
{
608+
const int bitmap_y = ImFloor(((y + 0.5f) * bitmap_h) / h);
609+
for (int x = 0; x < w; x++)
610+
{
611+
const int bitmap_x = ImFloor(((x + 0.5f) * bitmap_w) / w);
612+
dst_buffer[y * w + x] = temp_buffer[bitmap_y * bitmap_w + bitmap_x];
613+
}
614+
}
615+
#else
616+
// TODO: box scaling
617+
#endif
618+
619+
// Redirect to downscaled part of the buffer
620+
temp_buffer = dst_buffer;
621+
}
541622

542623
const float ref_size = baked->ContainerFont->Sources[0]->SizePixels;
543624
const float offsets_scale = (ref_size != 0.0f) ? (baked->Size / ref_size) : 1.0f;
@@ -551,8 +632,8 @@ static bool ImGui_ImplFreeType_FontBakedLoadGlyph(ImFontAtlas* atlas, ImFontConf
551632
float recip_v = 1.0f / rasterizer_density;
552633

553634
// Register glyph
554-
float glyph_off_x = (float)face->glyph->bitmap_left;
555-
float glyph_off_y = (float)-face->glyph->bitmap_top;
635+
float glyph_off_x = ImFloor((float)face->glyph->bitmap_left * bitmap_x_scale);
636+
float glyph_off_y = ImFloor((float)-face->glyph->bitmap_top * bitmap_y_scale);
556637
out_glyph->X0 = glyph_off_x * recip_h + font_off_x;
557638
out_glyph->Y0 = glyph_off_y * recip_v + font_off_y;
558639
out_glyph->X1 = (glyph_off_x + w) * recip_h + font_off_x;

0 commit comments

Comments
 (0)