ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.46k stars 10.34k forks source link

AddRectMultiColor #8181

Closed paindoll closed 2 days ago

paindoll commented 5 days ago

Version/Branch of Dear ImGui:

Current version

Back-ends:

imgui_impl_win32.cpp + imgui_impl_dx11.cpp

Compiler, OS:

Windows 11 + MSVC 2022

Full config/build information:

v1.91.5 WIP

Details:

Is there any kind method to render a non-filled gradient rect without creating four rects of AddRectFilledMultiColor?

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

No response

GamingMinds-DanielC commented 4 days ago

Not that I know of. But there are a few ways to accomplish this:

  1. If you just want a neat one-liner in your UI code, you can make a wrapper function to hide the four calls to AddRectFilledMultiColor().
  2. If your goal is efficiency (f.e. because you draw a lot of them), you can draw a white rectangle and manipulate the colors of generated vertices afterwards.
  3. For full flexibility, you can always implement your own custom drawing functions with the ImDrawList::Prim...() methods.

I needed efficient lines interpolating between two colors for something. I didn't want to reimplement the vertex construction with taking anti aliasing into account, so I used approach 2 for my lines. Here is the code:

void DrawTwoColorLine( ImDrawList*   drawList,
                       const ImVec2& p1,
                       const ImVec2& p2,
                       ImU32         color1,
                       ImU32         color2,
                       float         thickness )
{
    if ( color1 == color2 )
    {
        drawList->AddLine( p1, p2, color1, thickness );
        return;
    }

    if ( ( ( color1 | color2 ) & IM_COL32_A_MASK ) == 0 )
        return;

    const int vtxStart = drawList->VtxBuffer.size();

    drawList->AddLine( p1, p2, 0xFFFFFFFF, thickness );

    const int vtxEnd = drawList->VtxBuffer.size();

    ImDrawVert* vtxData = drawList->VtxBuffer.begin();

    const ImVec4 col1 = ImColor( color1 );
    const ImVec4 col2 = ImColor( color2 );
    const float  amul = 1.0f / (float)IM_COL32_A_MASK;
    const float  dx   = p2.x - p1.x;
    const float  dy   = p2.y - p1.y;

    for ( int i = vtxStart; i < vtxEnd; ++i )
    {
        ImDrawVert& v = vtxData[ i ];

        const float dp = ( ( v.pos.x - p1.x ) * dx + ( v.pos.y - p1.y ) * dy ) / ( dx * dx + dy * dy );
        const float u  = ( dp < 0.0f ) ? 0.0f : ( ( dp < 1.0f ) ? dp : 1.0f );
        const float ui = 1.0f - u;
        const float a  = (float)( v.col & IM_COL32_A_MASK ) * amul;

        v.col = ImColor( col1.x * ui + col2.x * u, col1.y * ui + col2.y * u, col1.z * ui + col2.z * u, ( col1.w * ui + col2.w * u ) * a );
    }
}

You can use this as a reference. It should be relatively easy to adapt this to rectangles.

ocornut commented 4 days ago

Also see #4722 but it is essentially the same as answered above: draw the shape you want as white and then "shade" them, which is the call to ShadeVertsLinearColorGradientKeepAlpha() in #4722. I am also pretty sure the loop Daniel posted is equivalent to be calling ShadeVertsLinearColorGradientKeepAlpha(). So technically you could add well call AddRect() + ShadeVertsLinearColorGradientKeepAlpha().

However, I strongly feel that answer (1) is probably the right answer. It's very probably not a problem to call e.g. AddRectFilledMultiColor() multiple times if you need it. If you are not drawing 10000+ shapes every frame the difference will likely not matter.

ocornut commented 2 days ago

Closing as answered. Thanks Daniel!