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

rounded clipping / rounded AddRectFilledMultiColor #3710

Closed
lennyRBLX opened this issue Jan 12, 2021 · 8 comments
Closed

rounded clipping / rounded AddRectFilledMultiColor #3710

lennyRBLX opened this issue Jan 12, 2021 · 8 comments

Comments

@lennyRBLX
Copy link

lennyRBLX commented Jan 12, 2021

1.79 shadows branch:

Version: 1.79 WIP
Branch: shadows

DX11 Win32 Windows 10 1903 Application

Back-ends: imgui_impl_win32.cpp + imgui_impl_dx11.cpp
Compiler: Win64 x64 Visual Studio 2019
Operating System: Windows 10 x64 1903

Are Rounded ClipRect's being considered and why does AddRectFilledMultiColor not have rounded options:

Hi, I am trying to create a rounded Color Picker. I located the location where ImGui's widget file (imgui_widgets.cpp) renders the actual color picker where you can select the color with the widget's cursor (you can navigate to this via visual interface by going to the demo menu's Tools tab and then selecting Style Editor, locate the section named Colors which is next to Sizes, click on the mini view of the color for Text or any other option and the view with the white to black to red picker is what I am referring to)

The lines to find this is 5149 and looks like:

// Render SV Square
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
sv_cursor_pos.x = ImClamp(IM_ROUND(picker_pos.x + ImSaturate(S)     * sv_picker_size), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
sv_cursor_pos.y = ImClamp(IM_ROUND(picker_pos.y + ImSaturate(1 - V) * sv_picker_size), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);

I want to make this render a rounded version of this since my menu is based around being very round, I had a solution to this in other engine but since this is ImGui and it looks like functionality that should be inherited from AddRect I'm stumped to why this doesn't have the ability to round like other methods.

To be clear I am working with internal methods and know the dangers or responsibilities I must take with this, I was just reversing how ImGui::ColorPicker4 and ColorEditor4 worked so I could find what method they used for this specific display.

I believe another solution to my problem could be something like an inverted clip or inverted filled rect where instead of filling the interior of the rectangle it would fill the spots where it wouldn't be visible, which would be the rounded edges. Personally, I do not know much about the internals of ImGui's vertex shading but I can see with some extra effort I might be able to solve this. This is being posted as I am curious if anyone has already worked on something like this and I am not the first one to find a solution to this problem.

@lennyRBLX
Copy link
Author

I forgot to mention, I don't really consider the compromises of losing the edges of the color picker, my previous fix for this was to simply have the picker's cursor navigate between all 4 spots to their complete end as if it was not gone or clipped. I never exceeded something like 5.f in rounding if anyone was curious.

@ocornut
Copy link
Owner

ocornut commented Jan 12, 2021

Are Rounded ClipRect's being considered

No.
As per our current drawing infrastructure it would probably require CPU-side CSG operations.

Regardless, I don't think you need rounded cliprect to achieve so I believe this request for "rounded cliprect" is a XY Problem (http://xyproblem.info).

I never exceeded something like 5.f in rounding if anyone was curious.

Thanks for the clarification, that makes a big difference actually.

If you look at AddRectFilledMultiColor() all it does it create two triangles where each of the corner vertices use a different color in the vertex data. It's a pretty trivial rendering function.

An alternative solution: use AddRectFilled() with fully white color and shade afterwards, something like:

const int vtx_idx_0 = draw_list->VtxBuffer.Size;
draw_list->AddRectFilled(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, 10.0f);
const int vtx_idx_1 = draw_list->VtxBuffer.Size;
draw_list->AddRectFilled(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, 10.0f);
const int vtx_idx_2 = draw_list->VtxBuffer.Size;
ShadeVertsLinearColorGradientSetAlpha(draw_list, vtx_idx_0, vtx_idx_1, picker_pos, picker_pos + ImVec2(sv_picker_size, 0.0f), col_white, hue_color32);
ShadeVertsLinearColorGradientSetAlpha(draw_list, vtx_idx_1, vtx_idx_2, picker_pos, picker_pos + ImVec2(0.0f, sv_picker_size), 0, col_black);

With

void ShadeVertsLinearColorGradientSetAlpha(ImDrawList* draw_list, int vert_start_idx, int vert_end_idx, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1)
{
    ImVec2 gradient_extent = gradient_p1 - gradient_p0;
    float gradient_inv_length2 = 1.0f / ImLengthSqr(gradient_extent);
    ImDrawVert* vert_start = draw_list->VtxBuffer.Data + vert_start_idx;
    ImDrawVert* vert_end = draw_list->VtxBuffer.Data + vert_end_idx;
    const int col0_r = (int)(col0 >> IM_COL32_R_SHIFT) & 0xFF;
    const int col0_g = (int)(col0 >> IM_COL32_G_SHIFT) & 0xFF;
    const int col0_b = (int)(col0 >> IM_COL32_B_SHIFT) & 0xFF;
    const int col0_a = (int)(col0 >> IM_COL32_A_SHIFT) & 0xFF;
    const int col_delta_r = ((int)(col1 >> IM_COL32_R_SHIFT) & 0xFF) - col0_r;
    const int col_delta_g = ((int)(col1 >> IM_COL32_G_SHIFT) & 0xFF) - col0_g;
    const int col_delta_b = ((int)(col1 >> IM_COL32_B_SHIFT) & 0xFF) - col0_b;
    const int col_delta_a = ((int)(col1 >> IM_COL32_A_SHIFT) & 0xFF) - col0_a;
    for (ImDrawVert* vert = vert_start; vert < vert_end; vert++)
    {
        float d = ImDot(vert->pos - gradient_p0, gradient_extent);
        float t = ImClamp(d * gradient_inv_length2, 0.0f, 1.0f);
        int r = (int)(col0_r + col_delta_r * t);
        int g = (int)(col0_g + col_delta_g * t);
        int b = (int)(col0_b + col_delta_b * t);
        int a = (int)(col0_a + col_delta_a * t);
        vert->col = (r << IM_COL32_R_SHIFT) | (g << IM_COL32_G_SHIFT) | (b << IM_COL32_B_SHIFT) | (a << IM_COL32_A_SHIFT);
    }
}

But it will override the anti-aliased alpha fringe and therefore break outer alpha, so that function would need to be reworked.

@ocornut
Copy link
Owner

ocornut commented Jan 12, 2021

image

@lennyRBLX
Copy link
Author

Looks great, I will test to see if this is exactly what I need. I was considering one of my other ideas last night where you would do an inverted fill rect where the parts it doesn't render to (like where it would be rounded) would be filled instead of the center. Either way this looks like a good enough solution as is, so thank you so much!

@ocornut
Copy link
Owner

ocornut commented Jan 12, 2021

Note that the other color picker is circular by default:
image

Feel free to close this issue if happy.

@lennyRBLX
Copy link
Author

lennyRBLX commented Jan 12, 2021

Finished that other idea I had with the inverted rect.

Please note though that if you plan to use my solution it will not work with transparency, it is not clipping nor is it trying to round the picker itself, it is the third solution I thought up where it draws over the edges of the pickers ends.

For reference here are the differences between yours (second photo) and my solution (first photo):
[https://cdn.discordapp.com/attachments/683802314007642129/798556067180052500/unknown.png](my solution)
[https://cdn.discordapp.com/attachments/683802314007642129/798547620250517514/unknown.png](your solution)

If anyone is curious here is the code for my solution (probably won't ever be used outside of my use case though):

void ImDrawList::AddInvertedRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawCornerFlags rounding_corners)
{
    if ((col & IM_COL32_A_MASK) == 0)
        return;
    if (rounding > 0.0f)
        PathInvertedRect(p_min, p_max, col, rounding, rounding_corners);
}

void ImDrawList::PathInvertedRect(const ImVec2& a, const ImVec2& b, ImU32 col, float rounding, ImDrawCornerFlags rounding_corners)
{
    rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((rounding_corners & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((rounding_corners & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f) - 1.0f);
    rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((rounding_corners & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((rounding_corners & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f) - 1.0f);

    if (rounding <= 0.0f || rounding_corners == 0)
        return;
    else
    {
        const float rounding_tl = (rounding_corners & ImDrawCornerFlags_TopLeft) ? rounding : 0.0f;
        const float rounding_tr = (rounding_corners & ImDrawCornerFlags_TopRight) ? rounding : 0.0f;
        const float rounding_br = (rounding_corners & ImDrawCornerFlags_BotRight) ? rounding : 0.0f;
        const float rounding_bl = (rounding_corners & ImDrawCornerFlags_BotLeft) ? rounding : 0.0f;
        PathLineTo(a);
        PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9);
        PathFillConvex(col);

        PathLineTo(ImVec2(b.x, a.y));
        PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12);
        PathFillConvex(col);

        PathLineTo(ImVec2(b.x, b.y));
        PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3);
        PathFillConvex(col);

        PathLineTo(ImVec2(a.x, b.y));
        PathArcToFast(ImVec2(a.x + rounding_bl, b.y - rounding_bl), rounding_bl, 3, 6);
        PathFillConvex(col);
    }
}

// line 5174 in imgui_widgets.cpp
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), col_white, hue_color32, hue_color32, col_white);
draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0, 0, col_black, col_black);
draw_list->AddInvertedRectFilled(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), ImColor(ImGui::GetStyleColorVec4(ImGuiCol_PopupBg)), 5.f);

@Bartis1313
Copy link

I have encountered this problem too, ended up doing it myself. Here is the code, without need of adding extra background color argument. Side note, this isn't very very cpu friendly.

void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left, float rounding, ImDrawFlags flags)
{
    if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0)
        return;

    flags = FixRectCornerFlags(flags);
    rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawCornerFlags_Top) == ImDrawCornerFlags_Top) || ((flags & ImDrawCornerFlags_Bot) == ImDrawCornerFlags_Bot) ? 0.5f : 1.0f) - 1.0f);
    rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawCornerFlags_Left) == ImDrawCornerFlags_Left) || ((flags & ImDrawCornerFlags_Right) == ImDrawCornerFlags_Right) ? 0.5f : 1.0f) - 1.0f);

    // https://github.com/ocornut/imgui/issues/3710#issuecomment-758555966
    if (rounding > 0.0f)
    {
        const int size_before = VtxBuffer.Size;
        AddRectFilled(p_min, p_max, IM_COL32_WHITE, rounding, flags);
        const int size_after = VtxBuffer.Size;

        for (int i = size_before; i < size_after; i++)
        {
            ImDrawVert* vert = VtxBuffer.Data + i;

            ImVec4 upr_left = ImGui::ColorConvertU32ToFloat4(col_upr_left);
            ImVec4 bot_left = ImGui::ColorConvertU32ToFloat4(col_bot_left);
            ImVec4 up_right = ImGui::ColorConvertU32ToFloat4(col_upr_right);
            ImVec4 bot_right = ImGui::ColorConvertU32ToFloat4(col_bot_right);

            float X = ImClamp((vert->pos.x - p_min.x) / (p_max.x - p_min.x), 0.0f, 1.0f);

            // 4 colors - 8 deltas

            float r1 = upr_left.x + (up_right.x - upr_left.x) * X;
            float r2 = bot_left.x + (bot_right.x - bot_left.x) * X;

            float g1 = upr_left.y + (up_right.y - upr_left.y) * X;
            float g2 = bot_left.y + (bot_right.y - bot_left.y) * X;

            float b1 = upr_left.z + (up_right.z - upr_left.z) * X;
            float b2 = bot_left.z + (bot_right.z - bot_left.z) * X;

            float a1 = upr_left.w + (up_right.w - upr_left.w) * X;
            float a2 = bot_left.w + (bot_right.w - bot_left.w) * X;


            float Y = ImClamp((vert->pos.y - p_min.y) / (p_max.y - p_min.y), 0.0f, 1.0f);
            float r = r1 + (r2 - r1) * Y;
            float g = g1 + (g2 - g1) * Y;
            float b = b1 + (b2 - b1) * Y;
            float a = a1 + (a2 - a1) * Y;
            ImVec4 RGBA(r, g, b, a);

            RGBA = RGBA * ImGui::ColorConvertU32ToFloat4(vert->col);

            vert->col = ImColor(RGBA);
        }
        return;
    }

    const ImVec2 uv = _Data->TexUvWhitePixel;
    PrimReserve(6, 4);
    PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2));
    PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 3));
    PrimWriteVtx(p_min, uv, col_upr_left);
    PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv, col_upr_right);
    PrimWriteVtx(p_max, uv, col_bot_right);
    PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv, col_bot_left);
}

@Averta047
Copy link

I got an other idea i hope it's possible and i will work on it:
so the idea was to create a faded rect texture then render it using AddImageRounded and round it :)

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

4 participants