-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
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
Suggestion: Font Outlines #745
Comments
The problem are
Note that you can also render fonts yourself following e.g. what #618 does and then you are free to render with outlines. Easier to do some of that:
|
@ocornut, FYI |
Interesting. Still too much work to take any action now, but if you want to integrate a way to do the outlining easily within ImGui be my guest :) |
I've used fontstash-es in the past. Quite good with SDF and outlines, and gives you a standarised dual stb/freetype interface for free. It will add some complexity to the library though. Might try to merge everything but then I think DearImgui wont be so compact anymore :) On the other hand, I know there is a single-header freetype distribution lying around. |
here is a simple way to solve the problem : draw your info twice, first one is black, another one is drawn with normal color and offset a little, this way it will be much easier to read. cheap outline effect would be like this : draw your info 9 times, first 4 times are outer shadows, then 4 times to draw inner shadows, the final draw would be your normal info placed in the shadow center. this is more expensive so i prefer first one. |
Rendering things multiple times like that is too much overhead on intensive UIs that have a lot of text to render. A system similar to how FreeType works would be much more efficient. |
if the font is just textured quads would it really be so bad? |
I was missing text shadows so I have looked into this. Here is my probably hacky way of achieving something that works and looks nice. No ImGui changes required, you can do all of this in your application. I see @ocornut's answer above saying there isn't a clean way of doing so I though how about pre-multiplied alpha. First step is to generate shadows in your glyph atlas. One could use freetype or what not to generate high quality shadows but I chose to just edit the glyph atlas before submitting to GPU as texture. The glyph atlas used in demos is 32-bit RGBA texture but only A has data in it. So I chose to use the blue component to save the shadow. Thats how it looks. with a zoomed view of one glyph. Thats how you generate it. // First lets make a copy
unsigned char *pixels_shadow = new unsigned char[width * height * 4];
memcpy(pixels_shadow, pixels, width * height * 4);
// Clear the buffers' blue component, its set to white in the RGBA texture
for (int i = 0; i < height * width * 4; i+=4)
pixels_shadow[i + 2] = 0;
// Create shadow at offset
// this depends on how far you want your shadows
// but make sure the glyph atlas has padding around glyphs to accommodate for this
const int offset = 2;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
unsigned int current_index = ((j * 4) + (width * 4 * i));
unsigned int write_shadow_index = (((j + offset - 1) * 4) + (width * 4 * (i + offset)));
unsigned char current_pixel = pixels[current_index + 3];
unsigned char current_pixel_shadow = pixels[write_shadow_index + 3];
// Only write shadow pixels into empty areas
if (current_pixel != 0 && current_pixel_shadow == 0)
pixels_shadow[write_shadow_index + 2] = pixels[current_index + 3];
}
} Step 2 is to change shader to read this blue channel for shadows. Change the following line: " return half4(in.color) * texColor;\n" to: " float texShadow = texColor.b;\n"
" half4 shadowColor = half4(0.0f, 0.0f, 0.0f, 1.0f);\n" // You can use any color but need to use pre-multiplied alpha
" texColor.b = 1.0f;\n"
" texColor.rgb *= texColor.a;\n" // Use pre-multiplied alpha
" in.color.rgb *= in.color.a;\n" // Use pre-multiplied alpha
" return half4(in.color) * texColor + shadowColor * texShadow;\n" Step 3 is to enable pre-multiplied alpha in your setup. pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; to pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorOne; Thats it, now time for some pretty screenshots. I need to workout how to increase the actual renderer area of the glyph to accommodate for some shadows missing (look at the bottom of "L"). It might already be possible with ImGui's settings. Inspired by beautiful colours and text in blender [edit]: fixed a bug in the shadow generator. Haven't updated the screenshots they looks ok. |
with some padding hack it will look like: sweet! For those interested peeps, here is the patch. diff --git a/imgui.h b/imgui.h
index 83bb2558..6d469f76 100644
--- a/imgui.h
+++ b/imgui.h
@@ -2173,6 +2173,7 @@ struct ImFontAtlas
ImTextureID TexID; // User data to refer to the texture once it has been uploaded to user's graphic systems. It is passed back to you during rendering via the ImDrawCmd structure.
int TexDesiredWidth; // Texture width desired by user before Build(). Must be a power-of-two. If have many glyphs your graphics API have texture size restrictions you may want to increase texture width to decrease height.
int TexGlyphPadding; // Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0.
+ ImVec2 TexGlyphShadowOffset; // If you would like to use shadows with your text use this. Defaults to (0, 0). Defines horizontal and vertical shadows. Can only be positive at the moment.
// [Internal]
// NB: Access texture data via GetTexData*() calls! Which will setup a default font for you.
diff --git a/imgui_draw.cpp b/imgui_draw.cpp
index 5a8477fc..d8ccb3f6 100644
--- a/imgui_draw.cpp
+++ b/imgui_draw.cpp
@@ -1532,6 +1532,7 @@ ImFontAtlas::ImFontAtlas()
TexID = (ImTextureID)NULL;
TexDesiredWidth = 0;
TexGlyphPadding = 1;
+ TexGlyphShadowOffset = ImVec2(0.0f, 0.0f);
TexPixelsAlpha8 = NULL;
TexPixelsRGBA32 = NULL;
@@ -2012,14 +2013,15 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
// Gather the sizes of all rectangles we will need to pack (this loop is based on stbtt_PackFontRangesGatherRects)
const float scale = (cfg.SizePixels > 0) ? stbtt_ScaleForPixelHeight(&src_tmp.FontInfo, cfg.SizePixels) : stbtt_ScaleForMappingEmToPixels(&src_tmp.FontInfo, -cfg.SizePixels);
const int padding = atlas->TexGlyphPadding;
+ const ImVec2 shadow_offset = atlas->TexGlyphShadowOffset;
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
{
int x0, y0, x1, y1;
const int glyph_index_in_font = stbtt_FindGlyphIndex(&src_tmp.FontInfo, src_tmp.GlyphsList[glyph_i]);
IM_ASSERT(glyph_index_in_font != 0);
stbtt_GetGlyphBitmapBoxSubpixel(&src_tmp.FontInfo, glyph_index_in_font, scale * cfg.OversampleH, scale * cfg.OversampleV, 0, 0, &x0, &y0, &x1, &y1);
- src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1);
- src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1);
+ src_tmp.Rects[glyph_i].w = (stbrp_coord)(x1 - x0 + padding + cfg.OversampleH - 1 + shadow_offset.x);
+ src_tmp.Rects[glyph_i].h = (stbrp_coord)(y1 - y0 + padding + cfg.OversampleV - 1 + shadow_offset.y);
total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h;
}
}
diff --git a/misc/freetype/imgui_freetype.cpp b/misc/freetype/imgui_freetype.cpp
index 6695fb65..85f1008b 100644
--- a/misc/freetype/imgui_freetype.cpp
+++ b/misc/freetype/imgui_freetype.cpp
@@ -456,6 +511,7 @@ bool ImFontAtlasBuildWithFreeType(FT_Library ft_library, ImFontAtlas* atlas, uns
// Gather the sizes of all rectangles we will need to pack
const int padding = atlas->TexGlyphPadding;
+ const ImVec2 shadow_offset = atlas->TexGlyphShadowOffset;
for (int glyph_i = 0; glyph_i < src_tmp.GlyphsList.Size; glyph_i++)
{
ImFontBuildSrcGlyphFT& src_glyph = src_tmp.GlyphsList[glyph_i];
@@ -482,8 +538,8 @@ bool ImFontAtlasBuildWithFreeType(FT_Library ft_library, ImFontAtlas* atlas, uns
buf_bitmap_current_used_bytes += bitmap_size_in_bytes;
src_tmp.Font.BlitGlyph(ft_bitmap, src_glyph.BitmapData, src_glyph.Info.Width * 1, multiply_enabled ? multiply_table : NULL);
- src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + padding);
- src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + padding);
+ src_tmp.Rects[glyph_i].w = (stbrp_coord)(src_glyph.Info.Width + padding + shadow_offset.x);
+ src_tmp.Rects[glyph_i].h = (stbrp_coord)(src_glyph.Info.Height + padding + shadow_offset.y);
total_surface += src_tmp.Rects[glyph_i].w * src_tmp.Rects[glyph_i].h;
}
} For that screenshot @ocornut could something like this be upstreamed? or do I still don't know how to use |
Oh Sexy! |
Not recommended for heavy use, but another means of adding font outlines I made for fun. In imconfig.h, add to the custom namespace code: namespace ImGui
{
inline bool custom_UseFontShadow;
inline unsigned int custom_FontShadowColor;
inline static void PushFontShadow(unsigned int col)
{
custom_UseFontShadow = true;
custom_FontShadowColor = col;
}
inline static void PopFontShadow(void)
{
custom_UseFontShadow = false;
}
}; // namespace ImGui In imgui_draw.cpp find if (ImGui::custom_UseFontShadow)
{
ImVec2 shadowPos1(pos);
ImVec2 shadowPos2(pos);
ImVec2 shadowPos3(pos);
ImVec2 shadowPos4(pos);
shadowPos1.x -= 1;
shadowPos1.y -= 1;
font->RenderText(this, font_size, shadowPos1, ImGui::custom_FontShadowColor, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL);
shadowPos2.x -= 1;
shadowPos2.y += 1;
font->RenderText(this, font_size, shadowPos2, ImGui::custom_FontShadowColor, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL);
shadowPos3.x += 1;
shadowPos3.y -= 1;
font->RenderText(this, font_size, shadowPos3, ImGui::custom_FontShadowColor, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL);
shadowPos4.x += 1;
shadowPos4.y += 1;
font->RenderText(this, font_size, shadowPos4, ImGui::custom_FontShadowColor, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL);
} Example usage would be like this, (modded the demo): ImGui::PushFontShadow(0xFF000000);
ShowDemoWindowPopups();
ImGui::PopFontShadow();
ImGui::PushFontShadow(0xFFFF00FF);
ShowDemoWindowColumns();
ImGui::PopFontShadow(); Because of how this works, it is NOT ideal for actual usage in production. It's also not ideal for adding outlines to all text, you should not do it like this if you wish to outline everything ImGui draws text wise. This will heavily impact performance if used on a lot of text. Not recommended for anything other than debug setups and simple/light usage. |
@atom0s you are right this won’t be performant at all. For outline use the same trick and putting the outline in Green or Red channel should work. |
Yes we could! But I think that exact patch is not ideal. Opening a new issue/PR specifically for it would be good so we can discuss that in a separate that. Your patch assume that: padding is only desired from two sides (bottom and right) and that ShadowPadding <= TexGlyphPadding which is not guaranteed. We can find out a solution. |
cool, I will create a PR. I am sure there must be other things wrong with that. Could also add some controls in the demo. |
In some cases with certain controls, the text can become hard to read as colors can blend into each other.
A good example of this is the progress bar control where text can blend into the bar color.
It would be nice to be able to implement a font outline / shadow so things are easier to read on certain controls. Perhaps as a flag passed to a controls creation?
For example:
![imgur](https://camo.githubusercontent.com/6aa437b1f87978aa24978f132219593e169f9870d2537d2ff969e10981e919f8/687474703a2f2f692e696d6775722e636f6d2f6541316c4239382e706e67)
This is my current theme, a slider bar or progress bar has the same issue with being a little hard to read under certain conditions.
The text was updated successfully, but these errors were encountered: