Skip to content

CustomRect API problems (AddCustomRectRegular) #8107

@YarikTH

Description

@YarikTH

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 <John.Doeh@gmail.com>
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

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions