Texture-based round corners: Added support for multiple stroke widths

This commit is contained in:
Ben Carter 2019-12-05 15:48:41 +09:00 committed by ocornut
parent 8e97a4a724
commit ff3e7feba7
5 changed files with 254 additions and 122 deletions

View File

@ -5577,8 +5577,7 @@ static bool AddResizeGrip(ImDrawList* dl, const ImVec2& corner, unsigned int rad
const ImVec2 uv[] =
{
ImVec2(ImLerp(uvs.x, uvs.z, 0.5f), ImLerp(uvs.y, uvs.w, 0.5f)),
ImVec2(uvs.x, uvs.y),//ImLerp(uvs.w, uvs.y, 0.1f)),
//ImVec2(uvs.x, uvs.w),
ImVec2(uvs.x, uvs.y),
ImVec2(uvs.z, uvs.w),
};

View File

@ -2704,10 +2704,11 @@ struct ImFontGlyphRangesBuilder
// Data for texture-based rounded corners for a given radius
struct ImFontRoundedCornerData
{
ImVec4 TexUvFilled; // UV of filled round corner quad in the atlas
ImVec4 TexUvFilled; // UV of filled round corner quad in the atlas (only valid when stroke width is 1)
ImVec4 TexUvStroked; // UV of stroked round corner quad in the atlas
float ParametricStrokeWidth; // Pre-calculated value for stroke width divided by the radius
int RectId; // Rect ID in the atlas
int RectId; // Rect ID in the atlas, or -1 if there is no data
bool StrokedUsesAlternateUVs; // True if stroked drawing should use the alternate (i.e. other corner) UVs
};
// See ImFontAtlas::AddCustomRectXXX functions.
@ -2844,7 +2845,7 @@ struct ImFontAtlas
int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors
int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines
ImVector<ImFontRoundedCornerData> TexRoundCornerData; // Data for texture-based round corners indexed by size [0] is 1px, [n] is (n+1)px (index up to ImFontAtlasRoundCornersMaxSize - 1).
ImVector<ImFontRoundedCornerData> TexRoundCornerData; // Data for texture-based round corners indexed by radius/size (from 1 to ImFontAtlasRoundCornersMaxSize) and stroke width (from 1 to ImFontAtlasRoundCornersMaxStrokeWidth), with index = stroke_width_index + (radius_index * ImFontAtlasRoundCornersMaxStrokeWidth).
// [Obsolete]
//typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+

View File

@ -258,7 +258,6 @@ static void GetVtxIdxDelta(ImDrawList* dl, int* vtx, int *idx)
}
// https://github.com/ocornut/imgui/issues/1962
// FIXME-ROUNDCORNERS: Figure out how to support multiple thickness, might hard-code common steps (1.0, 1.5, 2.0, 3.0), not super satisfactory but may be best
static void TestTextureBasedRender()
{
ImGuiIO& io = ImGui::GetIO();
@ -272,7 +271,11 @@ static void TestTextureBasedRender()
static int segments = 20;
static int ngon_segments = 6;
ImGui::SliderFloat("radius", &radius, 0.0f, (float)64 /*ImFontAtlasRoundCornersMaxSize*/, "%.0f");
ImGui::SliderFloat("radius", &radius, 0.0f, 64.0f /*(float)ImFontAtlasRoundCornersMaxSize*/, "%.0f");
static float stroke_width = 1.0f;
ImGui::SliderFloat("stroke_width", &stroke_width, 1.0f, 10.0f, "%.0f");
int vtx_n = 0;
int idx_n = 0;
@ -299,7 +302,7 @@ static void TestTextureBasedRender()
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 size = ImGui::GetItemRectSize();
draw_list->AddCircle(ImVec2(min.x + size.x * 0.5f, min.y + size.y * 0.5f), radius, IM_COL32(255,0,255,255), segments);
draw_list->AddCircle(ImVec2(min.x + size.x * 0.5f, min.y + size.y * 0.5f), radius, IM_COL32(255,0,255,255), segments, stroke_width);
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImGui::Text("AddCircle\n %d vtx, %d idx", vtx_n, idx_n);
}
@ -336,7 +339,7 @@ static void TestTextureBasedRender()
ImVec2 r_max = ImGui::GetItemRectMax();
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
draw_list->AddRect(r_min, r_max, IM_COL32(255,0,255,255), radius, corner_flags);
draw_list->AddRect(r_min, r_max, IM_COL32(255,0,255,255), radius, corner_flags, stroke_width);
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImGui::Text("AddRect\n %d vtx, %d idx", vtx_n, idx_n);
}
@ -367,7 +370,7 @@ static void TestTextureBasedRender()
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 size = ImGui::GetItemRectSize();
draw_list->AddNgon(ImVec2(min.x + size.x * 0.5f, min.y + size.y * 0.5f), radius, IM_COL32(255, 0, 255, 255), ngon_segments);
draw_list->AddNgon(ImVec2(min.x + size.x * 0.5f, min.y + size.y * 0.5f), radius, IM_COL32(255, 0, 255, 255), ngon_segments, stroke_width);
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImGui::Text("AddNgon\n %d vtx, %d idx", vtx_n, idx_n);
}

View File

@ -1394,7 +1394,7 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th
// Returns true if the rectangle was drawn, false for some reason it couldn't
// be (in which case the caller should try again with the regular path drawing API)
// We are using the textures generated by ImFontAtlasBuildRenderRoundCornersTexData()
inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, ImDrawFlags flags, bool fill)
inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, float thickness, ImDrawFlags flags, bool fill)
{
if (!(draw_list->Flags & ImDrawListFlags_TexturedRoundCorners)) // Disabled by the draw list flags
return false;
@ -1406,32 +1406,36 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
#endif
const ImDrawListSharedData* data = draw_list->_Data;
const int rad = (int)rounding;
if (data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) // No data in font
return false;
if ((rad <= 0) || // Zero radius causes issues with the [rad - 1] UV lookup below
// Filled rectangles have no stroke width
const int stroke_width = fill ? 1 : (int)thickness;
if ((stroke_width <= 0) ||
(stroke_width > ImFontAtlasRoundCornersMaxStrokeWidth))
return false; // We can't handle this
// If we have a >1 stroke width, we actually need to increase the radius appropriately as well to match how the geometry renderer does things
const int rad = (int)rounding + (stroke_width - 1);
if ((rad <= 0) || // We don't support zero radius
(rad > ImFontAtlasRoundCornersMaxSize))
{
// We can't handle this
return false;
}
return false; // We can't handle this
// Debug command to force this render path to only execute when shift is held
if (!ImGui::GetIO().KeyShift)
return false;
const unsigned int index = (stroke_width - 1) + ((rad - 1) * ImFontAtlasRoundCornersMaxStrokeWidth);
ImFontRoundedCornerData& round_corner_data = (*data->TexRoundCornerData)[index];
if (round_corner_data.RectId < 0)
return false; // No data for this configuration
ImTextureID tex_id = data->Font->ContainerAtlas->TexID;
IM_ASSERT(tex_id == draw_list->_TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font.
// The width of our stroke for unfilled mode
// Something of a placeholder at the moment - used for calculations but without appropriately-generated
// textures won't actually achieve anything
const float stroke_width = 1.0f;
ImFontRoundedCornerData& round_corner_data = (*data->TexRoundCornerData)[rad - 1];
// Calculate UVs for the three points we are interested in from the texture
// corner_uv[0] is the innermost point of the circle (solid for filled circles)
// corner_uv[1] is either straight down or across from it (depending on if we are using the filled or stroked version)
@ -1439,11 +1443,14 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
// corner_uv[1] is always solid (either inside the circle or on the line), whilst corner_uv[2] is always blank
// This represents a 45 degree "wedge" of circle, which then gets mirrored here to produce a 90 degree curve
// See ImFontAtlasBuildRenderRoundCornersTexData() for more details of the texture contents
// If use_alternative_uvs is true then this means we are drawing a stroked texture that has been packed into the "filled"
// corner of the rectangle, so we need to calculate UVs appropriately
const ImVec4& uvs = fill ? round_corner_data.TexUvFilled : round_corner_data.TexUvStroked;
const bool use_alternative_uvs = fill | round_corner_data.StrokedUsesAlternateUVs;
const ImVec2 corner_uv[3] =
{
ImVec2(uvs.x, uvs.y),
fill ? ImVec2(uvs.x, uvs.w) : ImVec2(uvs.z, uvs.y),
use_alternative_uvs ? ImVec2(uvs.x, uvs.w) : ImVec2(uvs.z, uvs.y),
ImVec2(uvs.z, uvs.w)
};
@ -1476,14 +1483,17 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
// MAX2/MAY2/etc are those vertices offset inwards by the line width
// (only used for unfilled rectangles)
const ImVec2 ca(a.x, a.y), cb(b.x, a.y);
// Adjust size to account for the fact that wider strokes draw "outside the box"
const float stroke_width_size_expansion = stroke_width - 1.0f;
const ImVec2 ca(a.x - stroke_width_size_expansion, a.y - stroke_width_size_expansion), cb(b.x + stroke_width_size_expansion, a.y - stroke_width_size_expansion);
const ImVec2 may(ca.x + rad, ca.y), mby(cb.x - rad, cb.y);
const ImVec2 may2(may.x, may.y + stroke_width), mby2(mby.x, mby.y + stroke_width);
const ImVec2 max(ca.x, ca.y + rad), mbx(cb.x, cb.y + rad);
const ImVec2 max2(max.x + stroke_width, max.y), mbx2(mbx.x - stroke_width, mbx.y);
const ImVec2 ia(ca.x + rad, ca.y + rad), ib(cb.x - rad, cb.y + rad);
const ImVec2 cc(b.x, b.y), cd(a.x, b.y);
const ImVec2 cc(b.x + stroke_width_size_expansion, b.y + stroke_width_size_expansion), cd(a.x - stroke_width_size_expansion, b.y + stroke_width_size_expansion);
const ImVec2 mdx(cd.x, cd.y - rad), mcx(cc.x, cc.y - rad);
const ImVec2 mdx2(mdx.x + stroke_width, mdx.y), mcx2(mcx.x - stroke_width, mcx.y);
const ImVec2 mdy(cd.x + rad, cd.y), mcy(cc.x - rad, cc.y);
@ -1516,7 +1526,7 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
// each occupy one side of the texture
#define VTX_WRITE_LERPED(d, corner, px, py) \
draw_list->_VtxWritePtr[d].pos = ImVec2(ImLerp(i##corner.x, c##corner.x, px), ImLerp(i##corner.y, c##corner.y, py)); \
draw_list->_VtxWritePtr[d].uv = ((px < py) ^ fill) ? \
draw_list->_VtxWritePtr[d].uv = ((px < py) ^ use_alternative_uvs) ? \
ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, py), ImLerp(corner_uv[0].y, corner_uv[b##corner ? 2 : 1].y, px)) : \
ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, px), ImLerp(corner_uv[0].y, corner_uv[b##corner ? 2 : 1].y, py)); \
draw_list->_VtxWritePtr[d].col = col
@ -1525,12 +1535,16 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
#define VTX_WRITE_LERPED_X(d, corner, px) \
draw_list->_VtxWritePtr[d].pos = ImVec2(ImLerp(i##corner.x, c##corner.x, px), i##corner.y); \
draw_list->_VtxWritePtr[d].uv = ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, px), corner_uv[0].y); \
draw_list->_VtxWritePtr[d].uv = use_alternative_uvs ? \
ImVec2(corner_uv[0].x, ImLerp(corner_uv[0].y, corner_uv[b##corner ? 2 : 1].y, px)) : \
ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, px), corner_uv[0].y); \
draw_list->_VtxWritePtr[d].col = col
#define VTX_WRITE_LERPED_Y(d, corner, py) \
draw_list->_VtxWritePtr[d].pos = ImVec2(i##corner.x, ImLerp(i##corner.y, c##corner.y, py)); \
draw_list->_VtxWritePtr[d].uv = ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, py), corner_uv[0].y); \
draw_list->_VtxWritePtr[d].uv = use_alternative_uvs ? \
ImVec2(corner_uv[0].x, ImLerp(corner_uv[0].y, corner_uv[b##corner ? 2 : 1].y, py)) : \
ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, py), corner_uv[0].y); \
draw_list->_VtxWritePtr[d].col = col
// Set up the outer corners (vca-vcd being the four outermost corners)
@ -1752,7 +1766,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl
// Try to use fast path if we can
if (rounding > 0)
if (AddRoundCornerRect(this, p_min, p_max, col, rounding, flags, /* fill */ false))
if (AddRoundCornerRect(this, p_min, p_max, col, rounding, thickness, flags, /* fill */ false))
return;
if (Flags & ImDrawListFlags_AntiAliasedLines)
@ -1781,7 +1795,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c
else
{
// Try fast path first
if (AddRoundCornerRect(this, p_min, p_max, col, rounding, flags, /* fill */ true))
if (AddRoundCornerRect(this, p_min, p_max, col, rounding, 1.0f, flags, /* fill */ true))
return;
PathRect(p_min, p_max, rounding, flags);
@ -1854,7 +1868,7 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV
// Draw a circle using the rounded corner textures
// Returns true if the circle was drawn, or false if for some reason it could not be
// (in which case the caller should try the regular circle drawing code)
inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, float radius, ImU32 col, bool fill)
inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, float radius, float thickness, ImU32 col, bool fill)
{
if (!(draw_list->Flags & ImDrawListFlags_TexturedRoundCorners)) // Disabled by the draw list flags
return false;
@ -1866,15 +1880,29 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
if (data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) // No data in font
return false;
const int rad = (int)radius;
if (rad < 1 || rad > ImFontAtlasRoundCornersMaxSize) // Radius 0 will cause issues with the UV lookup below
// Filled rectangles have no stroke width
const int stroke_width = fill ? 1 : (int)thickness;
if ((stroke_width <= 0) ||
(stroke_width > ImFontAtlasRoundCornersMaxStrokeWidth))
return false; // We can't handle this
// If we have a >1 stroke width, we actually need to increase the radius appropriately as well to match how the geometry renderer does things
const int rad = (int)radius + (stroke_width - 1);
if ((rad <= 0) || // We don't support zero radius
(rad > ImFontAtlasRoundCornersMaxSize))
return false; // We can't handle this
// Debug command to force this render path to only execute when shift is held
if (!ImGui::GetIO().KeyShift)
return false;
ImFontRoundedCornerData& round_corner_data = (*data->TexRoundCornerData)[rad - 1];
const unsigned int index = (stroke_width - 1) + ((rad - 1) * ImFontAtlasRoundCornersMaxStrokeWidth);
ImFontRoundedCornerData& round_corner_data = (*data->TexRoundCornerData)[index];
if (round_corner_data.RectId < 0)
return false; // No data for this configuration
// Calculate UVs for the three points we are interested in from the texture
// corner_uv[0] is the innermost point of the circle (solid for filled circles)
@ -1883,17 +1911,17 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
// corner_uv[1] is always solid (either inside the circle or on the line), whilst corner_uv[2] is always blank
// This represents a 45 degree "wedge" of circle, which then gets mirrored here to produce a 90 degree curve
// See ImFontAtlasBuildRenderRoundCornersTexData() for more details of the texture contents
// If use_alternative_uvs is true then this means we are drawing a stroked texture that has been packed into the "filled"
// corner of the rectangle, so we need to calculate UVs appropriately
const ImVec4& uvs = fill ? round_corner_data.TexUvFilled : round_corner_data.TexUvStroked;
const bool use_alternative_uvs = fill | round_corner_data.StrokedUsesAlternateUVs;
const ImVec2 corner_uv[3] =
{
ImVec2(uvs.x, uvs.y),
fill ? ImVec2(uvs.x, uvs.w) : ImVec2(uvs.z, uvs.y),
ImVec2(uvs.z, uvs.w),
use_alternative_uvs ? ImVec2(uvs.x, uvs.w) : ImVec2(uvs.z, uvs.y),
ImVec2(uvs.z, uvs.w)
};
// Our stroke width (requires a texture with the appropriate stroke width to actually do anything)
const float stroke_width = 1.0f;
// Calculate the circle bounds
const ImVec2& c = center;
ImVec2 tl = ImVec2(c.x - rad, c.y - rad);
@ -1943,17 +1971,25 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
// UV for the inside diagonal points
ImVec2 uvbi = ImVec2(ImLerp(corner_uv[0].x, corner_uv[2].x, half_sqrt_two - width_offset_parametric), ImLerp(corner_uv[0].y, corner_uv[2].y, half_sqrt_two - width_offset_parametric));
// Left/right/top/bottom interior positions
const ImVec2 lbi = ImVec2(ImLerp(tl.x, c.x, width_offset_parametric), c.y);
const ImVec2 rbi = ImVec2(ImLerp(br.x, c.x, width_offset_parametric), c.y);
const ImVec2 tbi = ImVec2(c.x, ImLerp(tl.y, c.y, width_offset_parametric));
const ImVec2 bbi = ImVec2(c.x, ImLerp(br.y, c.y, width_offset_parametric));
// UV for the interior cardinal points
ImVec2 uvi_cardinal = ImVec2(ImLerp(corner_uv[0].x, corner_uv[2].x, 1.0f - width_offset_parametric), corner_uv[0].y);
ImVec2 uvi_cardinal = use_alternative_uvs ?
ImVec2(corner_uv[0].x, ImLerp(corner_uv[2].y, corner_uv[0].y, width_offset_parametric)) :
ImVec2(ImLerp(corner_uv[2].x, corner_uv[0].x, width_offset_parametric), corner_uv[0].y);
// Inner vertices, starting from the left
VTX_WRITE(8, ImVec2(tl.x + stroke_width, c.y), uvi_cardinal);
VTX_WRITE(8, lbi, uvi_cardinal);
VTX_WRITE(9, tlbi, uvbi);
VTX_WRITE(10, ImVec2(c.x, tl.y + stroke_width), uvi_cardinal);
VTX_WRITE(10, tbi, uvi_cardinal);
VTX_WRITE(11, trbi, uvbi);
VTX_WRITE(12, ImVec2(br.x - stroke_width, c.y), uvi_cardinal);
VTX_WRITE(12, rbi, uvi_cardinal);
VTX_WRITE(13, brbi, uvbi);
VTX_WRITE(14, ImVec2(c.x, br.y - stroke_width), uvi_cardinal);
VTX_WRITE(14, bbi, uvi_cardinal);
VTX_WRITE(15, blbi, uvbi);
}
@ -2022,7 +2058,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu
return;
// First try the fast texture-based renderer, and only if that can't handle this fall back to paths
if (AddRoundCornerCircle(this, center, radius, col, false))
if (AddRoundCornerCircle(this, center, radius, thickness, col, false))
return;
// Obtain segment count
@ -2051,7 +2087,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col,
return;
// First try the fast texture-based renderer, and only if that can't handle this fall back to paths
if (AddRoundCornerCircle(this, center, radius, col, true))
if (AddRoundCornerCircle(this, center, radius, 1.0f, col, true))
return;
if (num_segments <= 0)
@ -3341,17 +3377,47 @@ static void ImFontAtlasBuildRegisterRoundCornersCustomRects(ImFontAtlas* atlas)
return;
const int pad = FONT_ATLAS_ROUNDED_CORNER_TEX_PADDING;
const unsigned int max = ImFontAtlasRoundCornersMaxSize;
const unsigned int max_radius = ImFontAtlasRoundCornersMaxSize;
const unsigned int max_thickness = ImFontAtlasRoundCornersMaxStrokeWidth;
atlas->TexRoundCornerData.reserve(max);
atlas->TexRoundCornerData.reserve(max_radius * max_thickness);
for (unsigned int n = 0; n < max; n++)
for (unsigned int radius_index = 0; radius_index < max_radius; radius_index++)
{
const int width = n + 1 + pad * 2;
const int height = n + 1 + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2;
ImFontRoundedCornerData corner_data;
corner_data.RectId = atlas->AddCustomRectRegular(width, height);
atlas->TexRoundCornerData.push_back(corner_data);
int spare_rect_id = -1; // The last rectangle ID we generated with a spare half
for (unsigned int stroke_width_index = 0; stroke_width_index < max_thickness; stroke_width_index++)
{
//const unsigned int index = stroke_width_index + (radius_index * ImFontAtlasRoundCornersMaxStrokeWidth);
const int width = radius_index + 1 + pad * 2;
const int height = radius_index + 1 + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2;
ImFontRoundedCornerData corner_data;
if (ImFontAtlasRoundCornersStrokeWidthMask & (1 << stroke_width_index))
{
if ((stroke_width_index == 0) || (spare_rect_id < 0))
{
corner_data.RectId = atlas->AddCustomRectRegular(width, height);
corner_data.StrokedUsesAlternateUVs = false;
if (stroke_width_index != 0)
spare_rect_id = corner_data.RectId;
}
else
{
// Pack this into the spare half of the previous rect
corner_data.RectId = spare_rect_id;
corner_data.StrokedUsesAlternateUVs = true;
spare_rect_id = -1;
}
}
else
corner_data.RectId = -1; // Set RectId to -1 if we don't want any data
IM_ASSERT_PARANOID(atlas->TexRoundCornerData.size() == (int)index);
atlas->TexRoundCornerData.push_back(corner_data);
}
}
}
@ -3364,85 +3430,141 @@ static void ImFontAtlasBuildRenderRoundCornersTexData(ImFontAtlas* atlas)
// Render the texture
const int w = atlas->TexWidth;
const unsigned int max = ImFontAtlasRoundCornersMaxSize;
const unsigned int max = ImFontAtlasRoundCornersMaxSize * ImFontAtlasRoundCornersMaxStrokeWidth;
const int pad = FONT_ATLAS_ROUNDED_CORNER_TEX_PADDING;
IM_ASSERT(atlas->TexRoundCornerData.Size == (int)max); // ImFontAtlasBuildRegisterRoundCornersCustomRects() will have created this for us
for (unsigned int n = 0; n < max; n++)
{
const unsigned int id = n;
ImFontRoundedCornerData& data = atlas->TexRoundCornerData[id];
ImFontAtlasCustomRect& r = atlas->CustomRects[data.RectId];
IM_ASSERT(r.IsPacked());
IM_ASSERT(r.Width == n + 1 + pad * 2 && r.Height == n + 1 + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2);
// What we're doing here is generating a rectangular image that contains the data for both the filled and
// stroked variants of the corner with the radius specified. We do it like this because we only need 45 degrees
// worth of curve (as each corner mirrors the texture to get the full 90 degrees), and hence with a little care
// we can put both variants into one texture by using two triangular regions. In practice this is a little more
// tricky than it first looks because if the two regions are packed tightly you get filtering errors where they meet,
// so we offset one vertically from the other by FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING pixels.
// The stroked version is at the top-right of the texture, and the filled version at the bottom-left.
const int radius = (int)(r.Width - pad * 2);
const float stroke_width = 1.0f;
const unsigned int max_radius = ImFontAtlasRoundCornersMaxSize;
const unsigned int max_thickness = ImFontAtlasRoundCornersMaxStrokeWidth;
// Pre-calcuate the parameteric stroke width
data.ParametricStrokeWidth = stroke_width / (float)radius;
atlas->TexRoundCornerData.reserve(max_radius * max_thickness);
for (int y = -pad; y < (int)(radius + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING); y++)
for (int x = -pad; x < (int)(radius); x++)
for (unsigned int radius_index = 0; radius_index < max_radius; radius_index++)
for (unsigned int stroke_width_index = 0; stroke_width_index < max_thickness; stroke_width_index++)
{
const unsigned int index = stroke_width_index + (radius_index * ImFontAtlasRoundCornersMaxStrokeWidth);
const unsigned int radius = radius_index + 1;
const float stroke_width = (float)stroke_width_index + 1;
ImFontRoundedCornerData& data = atlas->TexRoundCornerData[index];
if (data.RectId < 0)
continue; // We don't want to generate data for this
ImFontAtlasCustomRect& r = atlas->CustomRects[data.RectId];
IM_ASSERT(r.IsPacked());
IM_ASSERT(r.Width == radius + pad * 2 && r.Height == radius + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2);
// If we are generating data for a stroke width > 0, then look for another stroke width sharing this rectangle
float other_stroke_width = -1.0f;
ImFontRoundedCornerData* other_data = NULL;
if (stroke_width_index > 0)
{
// We want the pad area to essentially contain a clamped version of the 0th row/column, so
// clamp here. Not doing this results in nasty filtering artifacts at low radii.
int cx = ImMax(x, 0);
int cy = ImMax(y, 0);
// The X<Y region of the texture contains the data for filled corners, the X>Y region
// the data for stroked ones. We add half of FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING so that
// each side gets a buffer zone to avoid filtering artifacts.
const bool filled = x < (y - (FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING >> 1));
if (filled)
// We use the fact that we know shared pairs will always appear together to both make this check fast and skip trying
// to generate the second half of the pair again when the main loop comes around
stroke_width_index++;
while (stroke_width_index < max_thickness)
{
// The filled version starts a little further down the texture to give us the padding in the middle.
cy = ImMax(y - FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING, 0);
}
const unsigned int candidate_index = stroke_width_index + (radius_index * ImFontAtlasRoundCornersMaxStrokeWidth);
ImFontRoundedCornerData* candidate_data = &atlas->TexRoundCornerData[candidate_index];
const float dist = ImSqrt((float)(cx*cx+cy*cy)) - (float)(radius - (filled ? 0 : stroke_width));
float alpha = 0.0f;
if (filled)
{
alpha = ImClamp(-dist, 0.0f, 1.0f);
}
else
{
const float alpha1 = ImClamp(dist + stroke_width, 0.0f, 1.0f);
const float alpha2 = ImClamp(dist, 0.0f, 1.0f);
alpha = alpha1 - alpha2;
}
if (candidate_data->RectId == data.RectId)
{
other_data = candidate_data;
other_stroke_width = (float)stroke_width_index + 1;
other_data->ParametricStrokeWidth = ((other_stroke_width > 1.0f) ? (other_stroke_width + 2.0f) : other_stroke_width) / (float)radius;
break;
}
const unsigned int offset = (int)(r.X + pad + x) + (int)(r.Y + pad + y) * w;
atlas->TexPixelsAlpha8[offset] = (unsigned char)(0xFF * ImSaturate(alpha));
stroke_width_index++;
}
}
// We generate two sets of UVs for each rectangle, one for the filled portion and one for the unfilled bit.
for (unsigned int stage = 0; stage < 2; stage++)
{
ImFontAtlasCustomRect stage_rect = r;
// What we're doing here is generating a rectangular image that contains the data for both the filled and
// stroked variants of the corner with the radius specified. We do it like this because we only need 45 degrees
// worth of curve (as each corner mirrors the texture to get the full 90 degrees), and hence with a little care
// we can put both variants into one texture by using two triangular regions. In practice this is a little more
// tricky than it first looks because if the two regions are packed tightly you get filtering errors where they meet,
// so we offset one vertically from the other by FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING pixels.
// The stroked version is at the top-right of the texture, and the filled version at the bottom-left.
const bool filled = (stage == 0);
stage_rect.X += pad;
stage_rect.Y += pad + (filled ? FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING : 0);
stage_rect.Width -= (pad * 2);
stage_rect.Height -= (pad * 2) + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING;
// Pre-calculate the parametric stroke width (+2 to give space for texture filtering on non-single-pixel widths)
data.ParametricStrokeWidth = ((stroke_width > 1.0f) ? (stroke_width + 2.0f) : stroke_width) / (float)radius;
ImVec2 uv0, uv1;
atlas->CalcCustomRectUV(&stage_rect, &uv0, &uv1);
for (int y = -pad; y < (int)(radius + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad); y++)
for (int x = -pad; x < (int)(radius); x++)
{
// We want the pad area to essentially contain a clamped version of the 0th row/column, so
// clamp here. Not doing this results in nasty filtering artifacts at low radii.
int cx = ImMax(x, 0);
int cy = ImMax(y, 0);
if (stage == 0)
data.TexUvFilled = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
else
data.TexUvStroked = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
// The X<Y region of the texture contains the data for filled corners, the X>Y region
// the data for stroked ones. We add half of FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING so that
// each side gets a buffer zone to avoid filtering artifacts.
// For stroke widths > 1, we use the "filled" area to hold a second stroke width variant
const bool filled = x < (y - (FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING >> 1));
if (filled)
{
// The filled version starts a little further down the texture to give us the padding in the middle.
cy = ImMax(y - FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING, 0);
}
const float dist = ImSqrt((float)(cx*cx+cy*cy)) - (float)(radius - (filled ? 0 : (stroke_width * 0.5f) + 0.5f));
float alpha = 0.0f;
if (filled)
{
if (stroke_width_index > 0)
{
if (other_data)
{
// Using the filled section to hold a second stroke width variant instead of filled if we are at a stroke width > 1
const float other_dist = ImSqrt((float)(cx*cx + cy * cy)) - (float)(radius - ((other_stroke_width * 0.5f) + 0.5f));
const float alpha1 = ImClamp(other_dist + other_stroke_width, 0.0f, 1.0f);
const float alpha2 = ImClamp(other_dist, 0.0f, 1.0f);
alpha = alpha1 - alpha2;
}
}
else
alpha = ImClamp(-dist, 0.0f, 1.0f); // Filled version
}
else
{
const float alpha1 = ImClamp(dist + stroke_width, 0.0f, 1.0f);
const float alpha2 = ImClamp(dist, 0.0f, 1.0f);
alpha = alpha1 - alpha2;
}
const unsigned int offset = (int)(r.X + pad + x) + (int)(r.Y + pad + y) * w;
atlas->TexPixelsAlpha8[offset] = (unsigned char)(0xFF * ImSaturate(alpha));
}
// We generate two sets of UVs for each rectangle, one for the filled portion and one for the unfilled bit.
for (unsigned int stage = 0; stage < 2; stage++)
{
ImFontAtlasCustomRect stage_rect = r;
const bool filled = (stage == 0);
stage_rect.X += pad;
stage_rect.Y += pad + (filled ? FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING : 0);
stage_rect.Width -= (pad * 2);
stage_rect.Height -= (pad * 2) + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING;
ImVec2 uv0, uv1;
atlas->CalcCustomRectUV(&stage_rect, &uv0, &uv1);
if (stage == 0)
{
if (other_data)
other_data->TexUvStroked = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
else
data.TexUvFilled = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
}
else
data.TexUvStroked = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
}
}
}
}
// This is called/shared by both the stb_truetype and the FreeType builder.

View File

@ -2889,7 +2889,6 @@ struct ImFontBuilderIO
#ifdef IMGUI_ENABLE_STB_TRUETYPE
IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype();
#endif
const unsigned int ImFontAtlasRoundCornersMaxSize = 32; // Maximum size of rounded corner texture to generate in fonts
IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas);
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);
@ -2899,6 +2898,14 @@ IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas
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);
// Note that stroke width increases effective radius, so (e.g.) a max radius circle will have to use the fallback path if stroke width is > 1
const unsigned int ImFontAtlasRoundCornersMaxSize = 32; // Maximum size of rounded corner texture to generate in fonts
const unsigned int ImFontAtlasRoundCornersMaxStrokeWidth = 5; // Maximum stroke width of rounded corner texture to generate in fonts
// Bit mask for which stroke widths should have textures generated for them (the default of 0xD means widths 1, 2 and 4)
// Only bits up to ImFontAtlasRoundCornersMaxStrokeWidth are considered, and bit 0 (stroke width 1) must always be set
// Optimally there should be an odd number of bits set, as the texture packing packs the data in pairs, with one half of one pair being occupied by the filled texture
const unsigned int ImFontAtlasRoundCornersStrokeWidthMask = 0xD;
//-----------------------------------------------------------------------------
// [SECTION] Test Engine specific hooks (imgui_test_engine)
//-----------------------------------------------------------------------------