Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CustomRect API problems (AddCustomRectRegular) #8107

Open
YarikTH opened this issue Oct 28, 2024 · 14 comments
Open

CustomRect API problems (AddCustomRectRegular) #8107

YarikTH opened this issue Oct 28, 2024 · 14 comments

Comments

@YarikTH
Copy link

YarikTH commented Oct 28, 2024

Version/Branch of Dear ImGui:

Version 1.91.4, Branch: master

Back-ends:

does not apply

Compiler, OS:

does not apply

Full config/build information:

Dear ImGui 1.91.4 (19140)
--------------------------------
sizeof(size_t): 8, sizeof(ImDrawIdx): 2, sizeof(ImDrawVert): 20
define: __cplusplus=201103
define: __linux__
define: __GNUC__=13
--------------------------------
io.BackendPlatformName: imgui_impl_sdl2
io.BackendRendererName: imgui_impl_opengl3
io.ConfigFlags: 0x00000003
 NavEnableKeyboard
 NavEnableGamepad
io.ConfigNavCaptureKeyboard
io.ConfigInputTextCursorBlink
io.ConfigWindowsResizeFromEdges
io.ConfigMemoryCompactTimer = 60.0
io.BackendFlags: 0x0000000E
 HasMouseCursors
 HasSetMousePos
 RendererHasVtxOffset
--------------------------------
io.Fonts: 1 fonts, Flags: 0x00000000, TexSize: 512,128
io.DisplaySize: 1280.00,720.00
io.DisplayFramebufferScale: 1.00,1.00
--------------------------------
style.WindowPadding: 8.00,8.00
style.WindowBorderSize: 1.00
style.FramePadding: 4.00,3.00
style.FrameRounding: 0.00
style.FrameBorderSize: 0.00
style.ItemSpacing: 8.00,4.00
style.ItemInnerSpacing: 4.00,4.00

Details:

My Issue:

I use ImGui to draw a custom service menu with some additional graphics like button texture, company logo etc. So far I used additional texture atlas for custom graphics. But for performance reasons I have tried to pack custom graphics in ImGui atlas using custom rect API as described in using-custom-colorful-icons.

It worked fine most of the time, but I found several limitations:

  1. fixed Custom rects are not added in total_surface value in ImFontAtlasBuildWithStbTruetype(), so auto calculated width is not optimal in case surface of custom rects is comparable with total surface of all fonts.
  2. In case of very wide custom rects we have several problems:
    1. Custom rect is silently not added in the texture atlas in case selected width or calculated width is not enough to pack it. The only indication it that packing has failed is 0xFFFF value in X and Y of ImFontAtlasCustomRect. There is no assertion or other obvious indication of a problem. I wasn't ready for it based on "using-custom-colorful-icons" example and unexpectedly bumped into write out of memory error because of it.
    2. Maybe it is a rare case, but custom rect api allows adding rects of any size, so in my case width of the added rect was the same as autodetected width of a font atlas texture. Considering additional 1 pixel padding (TexGlyphPadding) it doesn't fit. Such case can be easily checked in texture width autodetect algorithm. Obviously minimal width should be pot(max(custom rects widths) + TexGlyphPadding). It can be optionally disabled with ImFontAtlasFlags_ like other things, but it is nice to have consideration.
  3. fixed It seems that TexGlyphPadding is applied only for... tex glyphs, but not for custom rects. So as a result all custom rects are clumped with each other seamlessly without any option to pad them from each other. It can be seen on the upscaled screenshot below. Colored rects are not separated, while font glyphs are.
    image

MCVE is not enough for demonstration, so, I prepared the patch file for example_sdl2_opengl3 instead. Unfortunately github doesn't want to attach .patch files, so I provide its content below (it might be easier to make a fork and add link to the commit, but I'm too lazy to make a fork for it at this point).

0001-custom-rect-example.patch

From c41efcc58a36abcd2fc9d92a9166f8c8c10d6854 Mon Sep 17 00:00:00 2001
From: John Doeh <[email protected]>
Date: Mon, 28 Oct 2024 17:26:09 +0100
Subject: [PATCH] custom rect example

---
 backends/imgui_impl_opengl3.cpp        |   5 +
 backends/imgui_impl_opengl3.h          |   4 +
 examples/example_sdl2_opengl3/main.cpp | 123 ++++++++++++++++++++++---
 3 files changed, 118 insertions(+), 14 deletions(-)

diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp
index f5235096..91e46950 100644
--- a/backends/imgui_impl_opengl3.cpp
+++ b/backends/imgui_impl_opengl3.cpp
@@ -664,6 +664,8 @@ void    ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
     (void)bd; // Not all compilation paths use this
 }
 
+extern ImGui_ImplOpenGL3_PostProcessFontTextureCallback_Signature ImGui_ImplOpenGL3_PostProcessFontTextureCallback = nullptr;
+
 bool ImGui_ImplOpenGL3_CreateFontsTexture()
 {
     ImGuiIO& io = ImGui::GetIO();
@@ -674,6 +676,9 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture()
     int width, height;
     io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
 
+    if(ImGui_ImplOpenGL3_PostProcessFontTextureCallback)
+        ImGui_ImplOpenGL3_PostProcessFontTextureCallback(pixels, width, height);
+
     // Upload texture to graphics system
     // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
     GLint last_texture;
diff --git a/backends/imgui_impl_opengl3.h b/backends/imgui_impl_opengl3.h
index 54545f95..7bd164fc 100644
--- a/backends/imgui_impl_opengl3.h
+++ b/backends/imgui_impl_opengl3.h
@@ -29,6 +29,10 @@
 #include "imgui.h"      // IMGUI_IMPL_API
 #ifndef IMGUI_DISABLE
 
+typedef void (*ImGui_ImplOpenGL3_PostProcessFontTextureCallback_Signature)(unsigned char* pixels, int width, int height);
+
+extern ImGui_ImplOpenGL3_PostProcessFontTextureCallback_Signature ImGui_ImplOpenGL3_PostProcessFontTextureCallback;
+
 // Follow "Getting Started" link and check examples/ folder to learn about using backends!
 IMGUI_IMPL_API bool     ImGui_ImplOpenGL3_Init(const char* glsl_version = nullptr);
 IMGUI_IMPL_API void     ImGui_ImplOpenGL3_Shutdown();
diff --git a/examples/example_sdl2_opengl3/main.cpp b/examples/example_sdl2_opengl3/main.cpp
index 2a4d7b9e..fe20c969 100644
--- a/examples/example_sdl2_opengl3/main.cpp
+++ b/examples/example_sdl2_opengl3/main.cpp
@@ -23,6 +23,29 @@
 #include "../libs/emscripten/emscripten_mainloop_stub.h"
 #endif
 
+enum CustomRectNames
+{
+    CR_RED,
+    CR_GREEN,
+    CR_BLUE,
+    CR_BIG_MINUS_ONE,
+    CR_BIG,
+    CR_BIG_PLUS_ONE,
+
+    CR_AMOUNT
+};
+
+const char* rect_names[] = {
+    "CR_RED",
+    "CR_GREEN",
+    "CR_BLUE",
+    "CR_BIG_MINUS_ONE",
+    "CR_BIG",
+    "CR_BIG_PLUS_ONE",
+};
+
+int rect_ids[CR_AMOUNT];
+
 // Main code
 int main(int, char**)
 {
@@ -110,6 +133,45 @@ int main(int, char**)
     //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese());
     //IM_ASSERT(font != nullptr);
 
+    rect_ids[CR_RED]            = io.Fonts->AddCustomRectRegular(20, 20);
+    rect_ids[CR_GREEN]          = io.Fonts->AddCustomRectRegular(20, 20);
+    rect_ids[CR_BLUE]           = io.Fonts->AddCustomRectRegular(20, 20);
+    rect_ids[CR_BIG_MINUS_ONE]  = io.Fonts->AddCustomRectRegular(511, 20);
+    rect_ids[CR_BIG]            = io.Fonts->AddCustomRectRegular(512, 20);
+    rect_ids[CR_BIG_PLUS_ONE]   = io.Fonts->AddCustomRectRegular(513, 20);
+
+    ImGui_ImplOpenGL3_PostProcessFontTextureCallback = [](unsigned char* pixels, int width, int height){
+        const ImU32 rect_colors[CR_AMOUNT] = {
+            IM_COL32(255, 0,   0,   255),
+            IM_COL32(0,   255, 0,   255),
+            IM_COL32(0,   0,   255, 255),
+            IM_COL32(255, 255, 0,   255),
+            IM_COL32(0,   255, 255, 255),
+            IM_COL32(255, 0,   255, 255),
+        };
+
+        ImGuiIO& io = ImGui::GetIO();
+
+        for (int rect_n = 0; rect_n < CR_AMOUNT; rect_n++)
+        {
+            const int rect_id = rect_ids[rect_n];
+            const ImU32 rect_color = rect_colors[rect_n];
+            if (const ImFontAtlasCustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id))
+            {
+                if (rect->IsPacked())
+                {
+                    // Fill the custom rectangle with red pixels (in reality you would draw/copy your bitmap data here!)
+                    for (int y = 0; y < rect->Height; y++)
+                    {
+                        ImU32* p = (ImU32*)pixels + (rect->Y + y) * width + (rect->X);
+                        for (int x = rect->Width; x > 0; x--)
+                            *p++ = rect_color;
+                    }
+                }
+            }
+        }
+    };
+
     // Our state
     bool show_demo_window = true;
     bool show_another_window = false;
@@ -157,24 +219,57 @@ int main(int, char**)
 
         // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
         {
-            static float f = 0.0f;
-            static int counter = 0;
-
-            ImGui::Begin("Hello, world!");                          // Create a window called "Hello, world!" and append into it.
+            ImGui::SetNextWindowSize(ImVec2(544, 440), ImGuiCond_FirstUseEver);
+            ImGui::Begin("Custom rect test");
 
-            ImGui::Text("This is some useful text.");               // Display some text (you can use a format strings too)
-            ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our window open/close state
-            ImGui::Checkbox("Another Window", &show_another_window);
+            // Copy from "Widgets/Images"
+            ImTextureID my_tex_id = io.Fonts->TexID;
+            float my_tex_w = (float)io.Fonts->TexWidth;
+            float my_tex_h = (float)io.Fonts->TexHeight;
+            {
+                static bool use_text_color_for_tint = false;
+                ImGui::Checkbox("Use Text Color for Tint", &use_text_color_for_tint);
+                ImGui::Text("%.0fx%.0f", my_tex_w, my_tex_h);
+                ImVec2 pos = ImGui::GetCursorScreenPos();
+                ImVec2 uv_min = ImVec2(0.0f, 0.0f);                 // Top-left
+                ImVec2 uv_max = ImVec2(1.0f, 1.0f);                 // Lower-right
+                ImVec4 tint_col = use_text_color_for_tint ? ImGui::GetStyleColorVec4(ImGuiCol_Text) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
+                ImVec4 border_col = ImGui::GetStyleColorVec4(ImGuiCol_Border);
+                ImGui::Image(my_tex_id, ImVec2(my_tex_w, my_tex_h), uv_min, uv_max, tint_col, border_col);
+                if (ImGui::BeginItemTooltip())
+                {
+                    float region_sz = 32.0f;
+                    float region_x = io.MousePos.x - pos.x - region_sz * 0.5f;
+                    float region_y = io.MousePos.y - pos.y - region_sz * 0.5f;
+                    float zoom = 4.0f;
+                    if (region_x < 0.0f) { region_x = 0.0f; }
+                    else if (region_x > my_tex_w - region_sz) { region_x = my_tex_w - region_sz; }
+                    if (region_y < 0.0f) { region_y = 0.0f; }
+                    else if (region_y > my_tex_h - region_sz) { region_y = my_tex_h - region_sz; }
+                    ImGui::Text("Min: (%.2f, %.2f)", region_x, region_y);
+                    ImGui::Text("Max: (%.2f, %.2f)", region_x + region_sz, region_y + region_sz);
+                    ImVec2 uv0 = ImVec2((region_x) / my_tex_w, (region_y) / my_tex_h);
+                    ImVec2 uv1 = ImVec2((region_x + region_sz) / my_tex_w, (region_y + region_sz) / my_tex_h);
+                    ImGui::Image(my_tex_id, ImVec2(region_sz * zoom, region_sz * zoom), uv0, uv1, tint_col, border_col);
+                    ImGui::EndTooltip();
+                }
+            }
 
-            ImGui::SliderFloat("float", &f, 0.0f, 1.0f);            // Edit 1 float using a slider from 0.0f to 1.0f
-            ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
+            const auto describeCustomRect = [&](CustomRectNames rect_id, const char* description)
+            {
+                ImFontAtlasCustomRect* customRect = io.Fonts->GetCustomRectByIndex(rect_id);
+                ImGui::PushStyleColor(ImGuiCol_Text, customRect->IsPacked() ? ImVec4(152.f/255, 251.f/255, 152.f/255, 1.0f) : ImVec4(255.f/255, 192.f/255, 203.f/255, 1.0f));
+                ImGui::Text("%s: {Width: %d, Height: %d, X: %d, Y: %d}", rect_names[rect_id], customRect->Width, customRect->Height, customRect->X, customRect->Y);
+                ImGui::Text("%s", description);
+                ImGui::PopStyleColor();
+            };
 
-            if (ImGui::Button("Button"))                            // Buttons return true when clicked (most widgets return true when edited/activated)
-                counter++;
-            ImGui::SameLine();
-            ImGui::Text("counter = %d", counter);
+            describeCustomRect( CR_BIG_MINUS_ONE, "Width a little less than atlas texture width.\nWorks fine." );
+            ImGui::NewLine();
+            describeCustomRect( CR_BIG, "Width the same atlas texture width.\nFor some reason can't pack such rect.\nThe only indication that packing is failed is 65535 value in X and Y" );
+            ImGui::NewLine();
+            describeCustomRect( CR_BIG_PLUS_ONE, "Width a little more than atlas texture width.\nFor some reason this value is not considered as minimal\natlas width on auto detection of atlas texture width.\nThe only indication that packing is failed is 65535 value in X and Y" );
 
-            ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
             ImGui::End();
         }
 
-- 
2.43.0


Result looks like this:
image
requested: 6 colored rects
result: 4 colored rects were added next to each other, 2 rects were silently skipped

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

No response

@YarikTH YarikTH changed the title CustomRect API problems CustomRect API problems (AddCustomRectRegular) Oct 28, 2024
@ocornut
Copy link
Owner

ocornut commented Oct 28, 2024

But for performance reasons I have tried to pack

Before everything: have you thoroughly measured the performance cost of texture changes?
It should be wholly negligible for a large majority of use. If you have, would you mind sharing your findings because that would be useful for us. If you haven't, I reckon you shouldn't be using this API for this.

@ocornut ocornut added drawing/drawlist drag drop drag and drop and removed drag drop drag and drop labels Oct 28, 2024
@ocornut
Copy link
Owner

ocornut commented Oct 28, 2024

I've been working on completely rewriting this system, so it's going to change and I am unlikely to want to put much energy into fixing the existing one, but I will investigate your 3 issues and at minimum try to make sure the new system doesn't have them. I'll keep this open in the meanwhile. Thanks!

EDIT I believe if we at least fix (1) which is very easy, it should reduce the occurrences of (2).

@ocornut ocornut added the bug label Oct 28, 2024
@YarikTH
Copy link
Author

YarikTH commented Oct 28, 2024

Before
image
After
image

In case of drawing a lot of buttons with custom image and text (Tabs) amount of draw calls was reduced from 27 and 5 to 1 and 1.

It improved FPS a little.

                                before      after
LOGIN page (only logout page)   28.01       33.52 FPS
PARTIAL STATISTICS page         17.18       19.19 FPS
TOTAL STATISTICS page           16.67       18.36 FPS
EVENTS page                     18.75       21.28 FPS

It is not a common use case though. It is webgl version on a very outdated hardware. There are other things to improve, but apart from some minor problems, merging of atlases gave me a small visible improvement.

@YarikTH
Copy link
Author

YarikTH commented Oct 28, 2024

But for performance reasons I have tried to pack

Before everything: have you thoroughly measured the performance cost of texture changes? It should be wholly negligible for a large majority of use. If you have, would you mind sharing your findings because that would be useful for us. If you haven't, I reckon you shouldn't be using this API for this.

I forgot to make previous message as a reply. But tl;dr I believe that on more or less modern hardware it wouldn't be a problem, but I have a very specific use case where every small optimization matters.

@ocornut
Copy link
Owner

ocornut commented Oct 28, 2024

Thank you for your thoughtful initial post and answer.
I am treating it as low-priority as I assume that given your known content you can set a desired texture width and not have any immediate problem?

@YarikTH
Copy link
Author

YarikTH commented Oct 28, 2024

Thank you for your thoughtful initial post and answer. I am treating it as low-priority as I assume that given your known content you can set a desired texture width and not have any immediate problem?

Yes, it is low priority. I can set texture width and in case of problems I can patch my fork of ImGui to fix at least some mentioned problems. But I believe that hardcoding of width and making manual padding will solve all my issues without touching imgui.

@axeldavy
Copy link

axeldavy commented Nov 9, 2024

I faced the issue (3) described here, and it gives visible artifacts when rendering:
image

The current workaround I found is to add a padding of 1 when filling custom rects, and use the following imgui patch:

@@ -2687,7 +2689,7 @@ void ImFontAtlas::CalcCustomRectUV(const ImFontAtlasCustomRect* rect, ImVec2* ou
     IM_ASSERT(TexWidth > 0 && TexHeight > 0);   // Font atlas needs to be built before we can calculate UV coordinates
     IM_ASSERT(rect->IsPacked());                // Make sure the rectangle has been packed
     *out_uv_min = ImVec2((float)rect->X * TexUvScale.x, (float)rect->Y * TexUvScale.y);
-    *out_uv_max = ImVec2((float)(rect->X + rect->Width) * TexUvScale.x, (float)(rect->Y + rect->Height) * TexUvScale.y);
+    *out_uv_max = ImVec2((float)(rect->X + rect->Width-1) * TexUvScale.x, (float)(rect->Y + rect->Height-1) * TexUvScale.y);
 }
 
 bool ImFontAtlas::GetMouseCursorTexData(ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2])
@@ -3284,7 +3286,7 @@ void ImFontAtlasBuildFinish(ImFontAtlas* atlas)
         IM_ASSERT(r->Font->ContainerAtlas == atlas);
         ImVec2 uv0, uv1;
         atlas->CalcCustomRectUV(r, &uv0, &uv1);
-        r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width, r->GlyphOffset.y + r->Height, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX);
+        r->Font->AddGlyph(NULL, (ImWchar)r->GlyphID, r->GlyphOffset.x, r->GlyphOffset.y, r->GlyphOffset.x + r->Width-1, r->GlyphOffset.y + r->Height-1, uv0.x, uv0.y, uv1.x, uv1.y, r->GlyphAdvanceX);
     }
 

Basically in addition to adding the padding in the filled rects, you need Imgui to be aware of it. This replicates the behaviour of the normal font path.

I might add an issue (4), is that I encountered crashes if I try to build a custom-rect only font (appending custom rect to a font created with AddFont() and no font file attached), thus I have to add a minimal font file for it to work.

ocornut added a commit that referenced this issue Nov 29, 2024
@ocornut
Copy link
Owner

ocornut commented Nov 29, 2024

I have pushed 19a1f2a with the following fixes:

(1) Fixed: "Custom rects are not added in total_surface value in ImFontAtlasBuildWithStbTruetype(), so auto calculated width is not optimal in case surface of custom rects is comparable with total surface of all fonts."

(3) Fixed "It seems that TexGlyphPadding is applied only for... tex glyphs, but not for custom rects."

My working branch will better handle packing errors mentioned in (2).

It is "correct" that a 512 wide custom rect cannot be packed into a 512 wide texture, as we want to honor TexGlyphPadding for bilinear filtering. Please note that in future version I may rename TexGlyphPadding to TexPackPadding for clarity.

"only indication it that packing has failed is 0xFFFF value in X and Y of ImFontAtlasCustomRect"

Technically IsPacked() returns false here, but I understand it is difficult to work with the fact that you don't immediately get an error in the AddCustomRectXXXX call. The upcoming revamped system should fix this.

I am keeping this open to track those details when I work on the new system. Thank you for the careful details and test code.

@YarikTH
Copy link
Author

YarikTH commented Nov 29, 2024

It is "correct" that a 512 wide custom rect cannot be packed into a 512 wide texture, as we want to honor TexGlyphPadding for bilinear filtering. Please note that in future version I may rename TexGlyphPadding to TexPackPadding for clarity.

Technically if we use autodetect of used width, then any custom rect of reasonable width (not 20k+) can be packed if autodetected width will greater or equal to max(custom_rect_widths)+TexPackPadding. The only way rect can't be packed should be case when font texture width is set by user (or custom rect width is unreasonable).

P.S. should I edit my post with numbered points to add things that other users mentioned? So it would be easier to track.

I mean.
(4) [@axeldavy] is that I encountered crashes if I try to build a custom-rect only font (appending custom rect to a font created with AddFont() and no font file attached), thus I have to add a minimal font file for it to work.
(5) [https://github.com//issues/8133] custom rects supposedly can't work with light/dark mode feature because they are not considered as "colored" and there is not API to mark them as such. There is only way for mark as colored glyphs that were added via AddCustomRectFontGlyph... or I completely misunderstood this and it doesn't affect AddCustomRectRegular at all, only AddCustomRectFontGlyph.

@ocornut
Copy link
Owner

ocornut commented Dec 2, 2024

For (4):

appending custom rect to a font created with AddFont() and no font file attached

How do you currently do that? AFAIK AddFont() always requires font data.
But I agree it should be possible.

For (5) the workaround is:
#8133 (comment)
I don't want to add a bool parameter to AddCustomRectFontGlyph() right now because I will refactor those API soon.

@YarikTH
Copy link
Author

YarikTH commented Dec 2, 2024

For (4):

appending custom rect to a font created with AddFont() and no font file attached

How do you currently do that? AFAIK AddFont() always requires font data. But I agree it should be possible.

For (5) the workaround is: #8133 (comment) I don't want to add a bool parameter to AddCustomRectFontGlyph() right now because I will refactor those API soon.

At least these issues can be considered in the new implementation. I hope it includes possibility to store A or RGBA data on the rects registration stage, so inconvinient post processing after io.Fonts->Build(); is no longer needed

@ocornut
Copy link
Owner

ocornut commented Dec 2, 2024

I hope it includes possibility to store A or RGBA data on the rects registration stage, so inconvinient post processing after io.Fonts->Build(); is no longer needed

Can you clarify?

@YarikTH
Copy link
Author

YarikTH commented Dec 2, 2024

I hope it includes possibility to store A or RGBA data on the rects registration stage, so inconvinient post processing after io.Fonts->Build(); is no longer needed

Can you clarify?

I mean, currently process is performed in 2 steps

// STEP 1. Register only rects before io.Fonts->Build();
// =====================================================
// Add font, then register two custom 13x13 rectangles mapped to glyph 'a' and 'b' of this font
ImFont* font = io.Fonts->AddFontDefault();
int rect_ids[2];
rect_ids[0] = io.Fonts->AddCustomRectFontGlyph(font, 'a', 13, 13, 13+1);
rect_ids[1] = io.Fonts->AddCustomRectFontGlyph(font, 'b', 13, 13, 13+1);

// Build atlas
io.Fonts->Build();

// STEP 2. Fill result atlas with actual data after io.Fonts->Build();
// ===================================================================
// Retrieve texture in RGBA format
unsigned char* tex_pixels = nullptr;
int tex_width, tex_height;
io.Fonts->GetTexDataAsRGBA32(&tex_pixels, &tex_width, &tex_height);

for (int rect_n = 0; rect_n < IM_ARRAYSIZE(rect_ids); rect_n++)
{
    int rect_id = rect_ids[rect_n];
    if (const ImFontAtlasCustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id))
    {
        // Fill the custom rectangle with red pixels (in reality you would draw/copy your bitmap data here!)
        for (int y = 0; y < rect->Height; y++)
        {
            ImU32* p = (ImU32*)tex_pixels + (rect->Y + y) * tex_width + (rect->X);
            for (int x = rect->Width; x > 0; x--)
                *p++ = IM_COL32(255, 0, 0, 255);
        }
    }
}

To perform the second step, I had to modify example to receive callback after Build() is called, because it is called asynchronously on the first draw.

It would be more convenient if it would be something like

uint8_t aGlyphPixelsRGBA[13*13*4] = {...};
uint8_t bGlyphPixelsA[13*13] = {...};

ImFont* font = io.Fonts->AddFontDefault();
io.Fonts->AddCustomRectFontGlyphColored(font, 'a', 13, 13, 13+1, aGlyphPixelsRGBA);
io.Fonts->AddCustomRectFontGlyphGrayscale(font, 'b', 13, 13, 13+1, bGlyphPixelsA);

// Build atlas
io.Fonts->Build();

@ocornut
Copy link
Owner

ocornut commented Dec 2, 2024

To perform the second step, I had to modify example to receive callback after Build() is called, because it is called asynchronously on the first draw.

You can manually call Build() yourself during init to simplify this.

It would be more convenient if it would be something like

Yes it is possible with the new version.

matthew-mccall pushed a commit to matthew-mccall/imgui that referenced this issue Jan 1, 2025
matthew-mccall pushed a commit to matthew-mccall/imgui that referenced this issue Jan 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants