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

Color picker #346

Closed
nem0 opened this issue Sep 22, 2015 · 108 comments
Closed

Color picker #346

nem0 opened this issue Sep 22, 2015 · 108 comments

Comments

@nem0
Copy link
Contributor

nem0 commented Sep 22, 2015

(ADMIN EDIT): COLOR PICKING TOOLS ARE NOW INCLUDED IN IMGUI. From version 1.51 (Aug 2017), ColorEdit3/ColorEdit4 wll allow you to open a picker by clicking on the colored square. Also added right-mouse click to open option. And you can call ColorPicker4 functions to directly embed a picker with custom options in your app. Read the release note and check the demo code.

I've implemented advanced color picker, maybe somebody find this useful:

color_picker

    void ImDrawList::AddTriangleFilledMultiColor(const ImVec2& a, const ImVec2& b, const ImVec2& c, ImU32 col_a, ImU32 col_b, ImU32 col_c)
    {
        if (((col_a | col_b | col_c) >> 24) == 0)
            return;

        const ImVec2 uv = GImGui->FontTexUvWhitePixel;
        PrimReserve(3, 3);
        PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 1)); PrimWriteIdx((ImDrawIdx)(_VtxCurrentIdx + 2));
        PrimWriteVtx(a, uv, col_a);
        PrimWriteVtx(b, uv, col_b);
        PrimWriteVtx(c, uv, col_c);
    }

    bool ColorPicker(const char* label, ImColor* color)
    {
        static const float HUE_PICKER_WIDTH = 20.0f;
        static const float CROSSHAIR_SIZE = 7.0f;
        static const ImVec2 SV_PICKER_SIZE = ImVec2(200, 200);

        bool value_changed = false;

        ImDrawList* draw_list = ImGui::GetWindowDrawList();

        ImVec2 picker_pos = ImGui::GetCursorScreenPos();

        ImColor colors[] = {ImColor(255, 0, 0),
            ImColor(255, 255, 0),
            ImColor(0, 255, 0),
            ImColor(0, 255, 255),
            ImColor(0, 0, 255),
            ImColor(255, 0, 255),
            ImColor(255, 0, 0)};

        for (int i = 0; i < 6; ++i)
        {
            draw_list->AddRectFilledMultiColor(
                ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 10, picker_pos.y + i * (SV_PICKER_SIZE.y / 6)),
                ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 10 + HUE_PICKER_WIDTH,
                    picker_pos.y + (i + 1) * (SV_PICKER_SIZE.y / 6)),
                colors[i],
                colors[i],
                colors[i + 1],
                colors[i + 1]);
        }

        float hue, saturation, value;
        ImGui::ColorConvertRGBtoHSV(
            color->Value.x, color->Value.y, color->Value.z, hue, saturation, value);
        auto hue_color = ImColor::HSV(hue, 1, 1);

        draw_list->AddLine(
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 8, picker_pos.y + hue * SV_PICKER_SIZE.y),
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 12 + HUE_PICKER_WIDTH,
                picker_pos.y + hue * SV_PICKER_SIZE.y),
            ImColor(255, 255, 255));

        draw_list->AddTriangleFilledMultiColor(picker_pos,
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x, picker_pos.y + SV_PICKER_SIZE.y),
            ImVec2(picker_pos.x, picker_pos.y + SV_PICKER_SIZE.y),
            ImColor(0, 0, 0),
            hue_color,
            ImColor(255, 255, 255));

        float x = saturation * value;
        ImVec2 p(picker_pos.x + x * SV_PICKER_SIZE.x, picker_pos.y + value * SV_PICKER_SIZE.y);
        draw_list->AddLine(ImVec2(p.x - CROSSHAIR_SIZE, p.y), ImVec2(p.x - 2, p.y), ImColor(255, 255, 255));
        draw_list->AddLine(ImVec2(p.x + CROSSHAIR_SIZE, p.y), ImVec2(p.x + 2, p.y), ImColor(255, 255, 255));
        draw_list->AddLine(ImVec2(p.x, p.y + CROSSHAIR_SIZE), ImVec2(p.x, p.y + 2), ImColor(255, 255, 255));
        draw_list->AddLine(ImVec2(p.x, p.y - CROSSHAIR_SIZE), ImVec2(p.x, p.y - 2), ImColor(255, 255, 255));

        ImGui::InvisibleButton("saturation_value_selector", SV_PICKER_SIZE);
        if (ImGui::IsItemHovered())
        {
            ImVec2 mouse_pos_in_canvas = ImVec2(
                ImGui::GetIO().MousePos.x - picker_pos.x, ImGui::GetIO().MousePos.y - picker_pos.y);
            if (ImGui::GetIO().MouseDown[0])
            {
                mouse_pos_in_canvas.x =
                    ImMin(mouse_pos_in_canvas.x, mouse_pos_in_canvas.y);

                value = mouse_pos_in_canvas.y / SV_PICKER_SIZE.y;
                saturation = value == 0 ? 0 : (mouse_pos_in_canvas.x / SV_PICKER_SIZE.x) / value;
                value_changed = true;
            }
        }

        ImGui::SetCursorScreenPos(ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 10, picker_pos.y));
        ImGui::InvisibleButton("hue_selector", ImVec2(HUE_PICKER_WIDTH, SV_PICKER_SIZE.y));

        if (ImGui::IsItemHovered())
        {
            if (ImGui::GetIO().MouseDown[0])
            {
                hue = ((ImGui::GetIO().MousePos.y - picker_pos.y) / SV_PICKER_SIZE.y);
                value_changed = true;
            }
        }

        *color = ImColor::HSV(hue, saturation, value);
        return value_changed | ImGui::ColorEdit3(label, &color->Value.x);
    }
@ocornut
Copy link
Owner

ocornut commented Sep 22, 2015

Thanks. Really useful!

I've been meaning to add a proper color picker by default in ImGui but haven't got around to do one with a proper feature set.

There's this one:
https://twitter.com/ApoorvaJ/status/644452534917009408
cpgokhhuyaaddyo png large
From https://github.com/ApoorvaJ/Papaya

There's this one
color picker - copy
https://github.com/benoitjacquier/imgui

We could probably combine some of those.

Micko's old imgui using nanovg also have a better color picker:
cmftstudio_win4-picker

That I wanted to replicate but haven't got around to do it yet.

@nem0
Copy link
Contributor Author

nem0 commented Sep 22, 2015

I thought I'd seen a task for this somewhere, however I was not able to find it again.

If I understand Papaya uses specific shader for the color picker. In general it is not possible to do the picker as in the first and second example with default shader or without runtime generated texture or huge amount of polygons. Mikko's solution is possible, it's basically the same as mine, only the hue selector has different shape

@ocornut
Copy link
Owner

ocornut commented Sep 23, 2015

The second one didn't use a huge amount of polygon asap, it's using multiple layers with transparency. Unfortunately the code itself is pretty huge and unbearable for what it does so I'll probably prefer to start from your base. At least we could improve it and ship it as an Example first before it gets stable enough to be promoted as an API thing.

@WearyWanderer
Copy link

These are awesome, thanks for posting. I particularly like the last one you posted @ocornut, might work on trying to implement one. If I get one done I'll post it for people.

@r-lyeh-archived
Copy link

Hey guys,

I merged @nem0's and @benoitjacquier's code.

The resulting snippet not as big as @benoitjacquier's, but still embeddable, standalone and does not require a new primitive (ImDrawList::AddTriangleFilledMultiColor) anymore.

image

I have improved the color picker to capture out-of-bounds hovers as well, as featured in the video below.

video

// [src] https://github.com/ocornut/imgui/issues/346

#include <imgui.h>

bool ColorPicker(const char* label, float col[3])
{
    static const float HUE_PICKER_WIDTH = 20.0f;
    static const float CROSSHAIR_SIZE = 7.0f;
    static const ImVec2 SV_PICKER_SIZE = ImVec2(200, 200);

    ImColor color(col[0], col[1], col[2]);
    bool value_changed = false;

    ImDrawList* draw_list = ImGui::GetWindowDrawList();

    ImVec2 picker_pos = ImGui::GetCursorScreenPos();

    ImColor colors[] = { ImColor(255, 0, 0),
        ImColor(255, 255, 0),
        ImColor(0, 255, 0),
        ImColor(0, 255, 255),
        ImColor(0, 0, 255),
        ImColor(255, 0, 255),
        ImColor(255, 0, 0) };

    for (int i = 0; i < 6; ++i)
    {
        draw_list->AddRectFilledMultiColor(
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 10, picker_pos.y + i * (SV_PICKER_SIZE.y / 6)),
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 10 + HUE_PICKER_WIDTH,
            picker_pos.y + (i + 1) * (SV_PICKER_SIZE.y / 6)),
            colors[i],
            colors[i],
            colors[i + 1],
            colors[i + 1]);
    }

    float hue, saturation, value;
    ImGui::ColorConvertRGBtoHSV(
        color.Value.x, color.Value.y, color.Value.z, hue, saturation, value);

    draw_list->AddLine(
        ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 8, picker_pos.y + hue * SV_PICKER_SIZE.y),
        ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 12 + HUE_PICKER_WIDTH, picker_pos.y + hue * SV_PICKER_SIZE.y),
        ImColor(255, 255, 255));

    {
        const int step = 5;
        ImVec2 pos = ImVec2(0, 0);

        ImVec4 c00(1, 1, 1, 1);
        ImVec4 c10(1, 1, 1, 1);
        ImVec4 c01(1, 1, 1, 1);
        ImVec4 c11(1, 1, 1, 1);
        for (int y = 0; y < step; y++) {
            for (int x = 0; x < step; x++) {
                float s0 = (float)x / (float)step;
                float s1 = (float)(x + 1) / (float)step;
                float v0 = 1.0 - (float)(y) / (float)step;
                float v1 = 1.0 - (float)(y + 1) / (float)step;

                ImGui::ColorConvertHSVtoRGB(hue, s0, v0, c00.x, c00.y, c00.z);
                ImGui::ColorConvertHSVtoRGB(hue, s1, v0, c10.x, c10.y, c10.z);
                ImGui::ColorConvertHSVtoRGB(hue, s0, v1, c01.x, c01.y, c01.z);
                ImGui::ColorConvertHSVtoRGB(hue, s1, v1, c11.x, c11.y, c11.z);

                draw_list->AddRectFilledMultiColor(
                    ImVec2(picker_pos.x + pos.x, picker_pos.y + pos.y), 
                    ImVec2(picker_pos.x + pos.x + SV_PICKER_SIZE.x / step, picker_pos.y + pos.y + SV_PICKER_SIZE.y / step),
                    ImGui::ColorConvertFloat4ToU32(c00),
                    ImGui::ColorConvertFloat4ToU32(c10),
                    ImGui::ColorConvertFloat4ToU32(c11),
                    ImGui::ColorConvertFloat4ToU32(c01));

                pos.x += SV_PICKER_SIZE.x / step;
            }
            pos.x = 0;
            pos.y += SV_PICKER_SIZE.y / step;
        }
    }

    float x = saturation * SV_PICKER_SIZE.x;
    float y = (1 -value) * SV_PICKER_SIZE.y;
    ImVec2 p(picker_pos.x + x, picker_pos.y + y);
    draw_list->AddLine(ImVec2(p.x - CROSSHAIR_SIZE, p.y), ImVec2(p.x - 2, p.y), ImColor(255, 255, 255));
    draw_list->AddLine(ImVec2(p.x + CROSSHAIR_SIZE, p.y), ImVec2(p.x + 2, p.y), ImColor(255, 255, 255));
    draw_list->AddLine(ImVec2(p.x, p.y + CROSSHAIR_SIZE), ImVec2(p.x, p.y + 2), ImColor(255, 255, 255));
    draw_list->AddLine(ImVec2(p.x, p.y - CROSSHAIR_SIZE), ImVec2(p.x, p.y - 2), ImColor(255, 255, 255));

    ImGui::InvisibleButton("saturation_value_selector", SV_PICKER_SIZE);

    if (ImGui::IsItemActive() && ImGui::GetIO().MouseDown[0])
    {
        ImVec2 mouse_pos_in_canvas = ImVec2(
            ImGui::GetIO().MousePos.x - picker_pos.x, ImGui::GetIO().MousePos.y - picker_pos.y);

        /**/ if( mouse_pos_in_canvas.x <                     0 ) mouse_pos_in_canvas.x = 0;
        else if( mouse_pos_in_canvas.x >= SV_PICKER_SIZE.x - 1 ) mouse_pos_in_canvas.x = SV_PICKER_SIZE.x - 1;

        /**/ if( mouse_pos_in_canvas.y <                     0 ) mouse_pos_in_canvas.y = 0;
        else if( mouse_pos_in_canvas.y >= SV_PICKER_SIZE.y - 1 ) mouse_pos_in_canvas.y = SV_PICKER_SIZE.y - 1;

        value = 1 - (mouse_pos_in_canvas.y / (SV_PICKER_SIZE.y - 1));
        saturation = mouse_pos_in_canvas.x / (SV_PICKER_SIZE.x - 1);
        value_changed = true;
    }

    ImGui::SetCursorScreenPos(ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 10, picker_pos.y));
    ImGui::InvisibleButton("hue_selector", ImVec2(HUE_PICKER_WIDTH, SV_PICKER_SIZE.y));

    if( (ImGui::IsItemHovered()||ImGui::IsItemActive()) && ImGui::GetIO().MouseDown[0])
    {
        ImVec2 mouse_pos_in_canvas = ImVec2(
            ImGui::GetIO().MousePos.x - picker_pos.x, ImGui::GetIO().MousePos.y - picker_pos.y);

        /* Previous horizontal bar will represent hue=1 (bottom) as hue=0 (top). Since both colors are red, we clamp at (-2, above edge) to avoid visual continuities */
        /**/ if( mouse_pos_in_canvas.y <                     0 ) mouse_pos_in_canvas.y = 0;
        else if( mouse_pos_in_canvas.y >= SV_PICKER_SIZE.y - 2 ) mouse_pos_in_canvas.y = SV_PICKER_SIZE.y - 2;

        hue = mouse_pos_in_canvas.y / (SV_PICKER_SIZE.y - 1 );
        value_changed = true;
    }

    color = ImColor::HSV(hue > 0 ? hue : 1e-6, saturation > 0 ? saturation : 1e-6, value > 0 ? value : 1e-6);
    col[0] = color.Value.x;
    col[1] = color.Value.y;
    col[2] = color.Value.z;
    return value_changed | ImGui::ColorEdit3(label, col);
}

@nem0
Copy link
Contributor Author

nem0 commented Jan 15, 2016

👍

is the SV picker exact?

@r-lyeh-archived
Copy link

should be. it would be nice to have a H,S,V triad below those R,G,B numbers to debug & confirm

@ocornut
Copy link
Owner

ocornut commented Jan 15, 2016

Superb! My small feedback from looking at the video is that you could align the R/G/B+Square at the bottom to be the same width as the main frame, e.g. using PushItemWidth(), and perhaps in this context it would make more sense to not have a label visible and save the horizontal space all together.

Ideally it would be able to interact with the default ColorEdit3/4() widget as well:

  • perhaps ColorEdit3/4 can be expanded with a small button and show the full thing.
  • or clicking on the colored square open a popup with this picker :)

I'm sorry I've got so much to catch on with ImGui, been working often 7 days a week already and finding it tough to sit down (though I've made notable progress on a few branch of work, but not color picker, so thanks all for posting your stuff here!).

@nem0
Copy link
Contributor Author

nem0 commented Jan 15, 2016

I mean this

                ImGui::ColorConvertHSVtoRGB(hue, s0, v0, c00.x, c00.y, c00.z);
                ImGui::ColorConvertHSVtoRGB(hue, s1, v0, c10.x, c10.y, c10.z);
                ImGui::ColorConvertHSVtoRGB(hue, s0, v1, c01.x, c01.y, c01.z);
                ImGui::ColorConvertHSVtoRGB(hue, s1, v1, c11.x, c11.y, c11.z);

                draw_list->AddRectFilledMultiColor(
                    ImVec2(picker_pos.x + pos.x, picker_pos.y + pos.y), 
                    ImVec2(picker_pos.x + pos.x + SV_PICKER_SIZE.x / step, picker_pos.y + pos.y + SV_PICKER_SIZE.y / step),
                    ImGui::ColorConvertFloat4ToU32(c00),
                    ImGui::ColorConvertFloat4ToU32(c10),
                    ImGui::ColorConvertFloat4ToU32(c11),
                    ImGui::ColorConvertFloat4ToU32(c01));

Interpolating HSV in RGB space - the difference is very visible when the big square is made from only two triangles

@r-lyeh-archived
Copy link

@nem0

Ah yup, it is not that exact. I also experienced that while building the widget before.
@benoitjacquier "fixed" the canvas picker by rendering many small squares (step size=5 px wide, customizable in src), which I found an elegant workaround considering the limitations.

I think it is overall smooth enough, though. I cannot spot very big issues when zooming, so it is ok for me as it is :)

image

@ocornut
yep that would be cool. I think another sidebar going from black to white to define alpha would be a nice addition as well.

@thennequin
Copy link

Another simple way (2 drawcalls), draw one quad with horizontal gradient from white to hue color and an other quad over the first with vertical gradient from black to transparant black.

const ImU32 c_oColorBlack = ImGui::ColorConvertFloat4ToU32(ImVec4(0.f,0.f,0.f,1.f));
const ImU32 c_oColorBlackTransparent = ImGui::ColorConvertFloat4ToU32(ImVec4(0.f,0.f,0.f,0.f));
const ImU32 c_oColorWhite = ImGui::ColorConvertFloat4ToU32(ImVec4(1.f,1.f,1.f,1.f));

ImVec4 cHueValue(1, 1, 1, 1);
ImGui::ColorConvertHSVtoRGB(hue, 1, 1, cHueValue.x, cHueValue.y, cHueValue.z);
ImU32 oHueColor = ImGui::ColorConvertFloat4ToU32(cHueValue);

ImVec2 oSaturationAreaMin /*= ImGui::GetItemRectMin()*/;
ImVec2 oSaturationAreaMax /*= ImGui::GetItemRectMax()*/;

pDrawList->AddRectFilledMultiColor(
    oSaturationAreaMin,
    oSaturationAreaMax,
    c_oColorWhite,
    oHueColor,
    oHueColor,
    c_oColorWhite
    );

pDrawList->AddRectFilledMultiColor(
    oSaturationAreaMin,
    oSaturationAreaMax,
    c_oColorBlackTransparent,
    c_oColorBlackTransparent,
    c_oColorBlack,
    c_oColorBlack
    );

@r-lyeh-archived
Copy link

aha very cool :) will try asap
i am fixing the align issues and adding an alpha bar as well.

Uploading image.png…

@r-lyeh-archived
Copy link

ok, v2
thanks for the suggestions guys

image

// [src] https://github.com/ocornut/imgui/issues/346
// v2

#include <imgui.h>

static bool ColorPicker( float *col, bool alphabar )
{
    const int    EDGE_SIZE = 200; // = int( ImGui::GetWindowWidth() * 0.75f );
    const ImVec2 SV_PICKER_SIZE = ImVec2(EDGE_SIZE, EDGE_SIZE);
    const float  SPACING = ImGui::GetStyle().ItemInnerSpacing.x;
    const float  HUE_PICKER_WIDTH = 20.f;
    const float  CROSSHAIR_SIZE = 7.0f;

    ImColor color(col[0], col[1], col[2]);
    bool value_changed = false;

    ImDrawList* draw_list = ImGui::GetWindowDrawList();

    // setup

    ImVec2 picker_pos = ImGui::GetCursorScreenPos();

    float hue, saturation, value;
    ImGui::ColorConvertRGBtoHSV(
        color.Value.x, color.Value.y, color.Value.z, hue, saturation, value);

    // draw hue bar

    ImColor colors[] = { ImColor(255, 0, 0),
        ImColor(255, 255, 0),
        ImColor(0, 255, 0),
        ImColor(0, 255, 255),
        ImColor(0, 0, 255),
        ImColor(255, 0, 255),
        ImColor(255, 0, 0) };

    for (int i = 0; i < 6; ++i)
    {
        draw_list->AddRectFilledMultiColor(
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + SPACING, picker_pos.y + i * (SV_PICKER_SIZE.y / 6)),
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + SPACING + HUE_PICKER_WIDTH,
            picker_pos.y + (i + 1) * (SV_PICKER_SIZE.y / 6)),
            colors[i],
            colors[i],
            colors[i + 1],
            colors[i + 1]);
    }

    draw_list->AddLine(
        ImVec2(picker_pos.x + SV_PICKER_SIZE.x + SPACING - 2, picker_pos.y + hue * SV_PICKER_SIZE.y),
        ImVec2(picker_pos.x + SV_PICKER_SIZE.x + SPACING + 2 + HUE_PICKER_WIDTH, picker_pos.y + hue * SV_PICKER_SIZE.y),
        ImColor(255, 255, 255));

    // draw alpha bar

    if( alphabar ) {
        float alpha = col[3];

        draw_list->AddRectFilledMultiColor(
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 2*SPACING + HUE_PICKER_WIDTH, picker_pos.y),
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 2*SPACING + 2*HUE_PICKER_WIDTH, picker_pos.y + SV_PICKER_SIZE.y),
            ImColor(0, 0, 0), ImColor(0, 0, 0), ImColor(255, 255, 255), ImColor(255, 255, 255) );

        draw_list->AddLine(
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 2*(SPACING - 2) + HUE_PICKER_WIDTH, picker_pos.y + alpha * SV_PICKER_SIZE.y),
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x + 2*(SPACING + 2) + 2*HUE_PICKER_WIDTH, picker_pos.y + alpha * SV_PICKER_SIZE.y),
            ImColor(255.f - alpha, 255.f, 255.f));
    }

    // draw color matrix

    {
        const ImU32 c_oColorBlack = ImGui::ColorConvertFloat4ToU32(ImVec4(0.f,0.f,0.f,1.f));
        const ImU32 c_oColorBlackTransparent = ImGui::ColorConvertFloat4ToU32(ImVec4(0.f,0.f,0.f,0.f));
        const ImU32 c_oColorWhite = ImGui::ColorConvertFloat4ToU32(ImVec4(1.f,1.f,1.f,1.f));

        ImVec4 cHueValue(1, 1, 1, 1);
        ImGui::ColorConvertHSVtoRGB(hue, 1, 1, cHueValue.x, cHueValue.y, cHueValue.z);
        ImU32 oHueColor = ImGui::ColorConvertFloat4ToU32(cHueValue);

        draw_list->AddRectFilledMultiColor(
            ImVec2(picker_pos.x, picker_pos.y),
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x, picker_pos.y + SV_PICKER_SIZE.y),
            c_oColorWhite,
            oHueColor,
            oHueColor,
            c_oColorWhite
            );

        draw_list->AddRectFilledMultiColor(
            ImVec2(picker_pos.x, picker_pos.y),
            ImVec2(picker_pos.x + SV_PICKER_SIZE.x, picker_pos.y + SV_PICKER_SIZE.y),
            c_oColorBlackTransparent,
            c_oColorBlackTransparent,
            c_oColorBlack,
            c_oColorBlack
            );
    }

    // draw cross-hair

    float x = saturation * SV_PICKER_SIZE.x;
    float y = (1 -value) * SV_PICKER_SIZE.y;
    ImVec2 p(picker_pos.x + x, picker_pos.y + y);
    draw_list->AddLine(ImVec2(p.x - CROSSHAIR_SIZE, p.y), ImVec2(p.x - 2, p.y), ImColor(255, 255, 255));
    draw_list->AddLine(ImVec2(p.x + CROSSHAIR_SIZE, p.y), ImVec2(p.x + 2, p.y), ImColor(255, 255, 255));
    draw_list->AddLine(ImVec2(p.x, p.y + CROSSHAIR_SIZE), ImVec2(p.x, p.y + 2), ImColor(255, 255, 255));
    draw_list->AddLine(ImVec2(p.x, p.y - CROSSHAIR_SIZE), ImVec2(p.x, p.y - 2), ImColor(255, 255, 255));

    // color matrix logic

    ImGui::InvisibleButton("saturation_value_selector", SV_PICKER_SIZE);

    if (ImGui::IsItemActive() && ImGui::GetIO().MouseDown[0])
    {
        ImVec2 mouse_pos_in_canvas = ImVec2(
            ImGui::GetIO().MousePos.x - picker_pos.x, ImGui::GetIO().MousePos.y - picker_pos.y);

        /**/ if( mouse_pos_in_canvas.x <                     0 ) mouse_pos_in_canvas.x = 0;
        else if( mouse_pos_in_canvas.x >= SV_PICKER_SIZE.x - 1 ) mouse_pos_in_canvas.x = SV_PICKER_SIZE.x - 1;

        /**/ if( mouse_pos_in_canvas.y <                     0 ) mouse_pos_in_canvas.y = 0;
        else if( mouse_pos_in_canvas.y >= SV_PICKER_SIZE.y - 1 ) mouse_pos_in_canvas.y = SV_PICKER_SIZE.y - 1;

        value = 1 - (mouse_pos_in_canvas.y / (SV_PICKER_SIZE.y - 1));
        saturation = mouse_pos_in_canvas.x / (SV_PICKER_SIZE.x - 1);
        value_changed = true;
    }

    // hue bar logic

    ImGui::SetCursorScreenPos(ImVec2(picker_pos.x + SPACING + SV_PICKER_SIZE.x, picker_pos.y));
    ImGui::InvisibleButton("hue_selector", ImVec2(HUE_PICKER_WIDTH, SV_PICKER_SIZE.y));

    if( ImGui::GetIO().MouseDown[0] && (ImGui::IsItemHovered() || ImGui::IsItemActive()) )
    {
        ImVec2 mouse_pos_in_canvas = ImVec2(
            ImGui::GetIO().MousePos.x - picker_pos.x, ImGui::GetIO().MousePos.y - picker_pos.y);

        /**/ if( mouse_pos_in_canvas.y <                     0 ) mouse_pos_in_canvas.y = 0;
        else if( mouse_pos_in_canvas.y >= SV_PICKER_SIZE.y - 1 ) mouse_pos_in_canvas.y = SV_PICKER_SIZE.y - 1;

        hue = mouse_pos_in_canvas.y / (SV_PICKER_SIZE.y - 1 );
        value_changed = true;
    }

    // alpha bar logic

    if( alphabar ) {

    ImGui::SetCursorScreenPos(ImVec2(picker_pos.x + SPACING * 2 + HUE_PICKER_WIDTH + SV_PICKER_SIZE.x, picker_pos.y));
    ImGui::InvisibleButton("alpha_selector", ImVec2(HUE_PICKER_WIDTH, SV_PICKER_SIZE.y));

    if( ImGui::GetIO().MouseDown[0] && (ImGui::IsItemHovered() || ImGui::IsItemActive()) )
    {
        ImVec2 mouse_pos_in_canvas = ImVec2(
            ImGui::GetIO().MousePos.x - picker_pos.x, ImGui::GetIO().MousePos.y - picker_pos.y);

        /**/ if( mouse_pos_in_canvas.y <                     0 ) mouse_pos_in_canvas.y = 0;
        else if( mouse_pos_in_canvas.y >= SV_PICKER_SIZE.y - 1 ) mouse_pos_in_canvas.y = SV_PICKER_SIZE.y - 1;

        float alpha = mouse_pos_in_canvas.y / (SV_PICKER_SIZE.y - 1 );
        col[3] = alpha;
        value_changed = true;
    }

    }

    // R,G,B or H,S,V color editor

    color = ImColor::HSV(hue >= 1 ? hue - 10 * 1e-6 : hue, saturation > 0 ? saturation : 10*1e-6, value > 0 ? value : 1e-6);
    col[0] = color.Value.x;
    col[1] = color.Value.y;
    col[2] = color.Value.z;

    bool widget_used;
    ImGui::PushItemWidth( ( alphabar ? SPACING + HUE_PICKER_WIDTH : 0 ) +
        SV_PICKER_SIZE.x + SPACING + HUE_PICKER_WIDTH - 2*ImGui::GetStyle().FramePadding.x );
    widget_used = alphabar ? ImGui::ColorEdit4("", col) : ImGui::ColorEdit3("", col);
    ImGui::PopItemWidth();

    // try to cancel hue wrap (after ColorEdit), if any
    {
        float new_hue, new_sat, new_val;
        ImGui::ColorConvertRGBtoHSV( col[0], col[1], col[2], new_hue, new_sat, new_val );
        if( new_hue <= 0 && hue > 0 ) {
            if( new_val <= 0 && value != new_val ) {
                color = ImColor::HSV(hue, saturation, new_val <= 0 ? value * 0.5f : new_val );
                col[0] = color.Value.x;
                col[1] = color.Value.y;
                col[2] = color.Value.z;
            }
            else
            if( new_sat <= 0 ) {
                color = ImColor::HSV(hue, new_sat <= 0 ? saturation * 0.5f : new_sat, new_val );
                col[0] = color.Value.x;
                col[1] = color.Value.y;
                col[2] = color.Value.z;
            }
        }
    }

    return value_changed | widget_used;
}

bool ColorPicker3( float col[3] ) {
    return ColorPicker( col, false );
}

bool ColorPicker4( float col[4] ) {
    return ColorPicker( col, true );
}

@brucelane
Copy link

thanks to all of you, that is going to be useful!

@nem0
Copy link
Contributor Author

nem0 commented Jan 16, 2016

@r-lyeh Nice, can we freely use your code?

@ocornut
Copy link
Owner

ocornut commented Jan 16, 2016

I'm looking at simplifying the code and making it match ImGui coding style.

v2.1 (-50 lines, bit faster)
https://gist.github.com/ocornut/9a55357df27d73cb8b34

@r-lyeh-archived
Copy link

@nem0: sure, my contribs are public domain : ) there is that thennequin's snippet in it too
@ocornut: 👍

@ocornut
Copy link
Owner

ocornut commented Jan 16, 2016

Normally I'd be inclined to make that stuff optional and a separate file, but I think it would make more sense here to include a basic color picker in core imgui for all to us by default?

@ocornut
Copy link
Owner

ocornut commented Jan 16, 2016

Update again
https://gist.github.com/ocornut/9a55357df27d73cb8b34

AFAIK all those:

if (ImGui::IsItemActive() && io.MouseDown[0])
if (io.MouseDown[0] && (ImGui::IsItemHovered() || ImGui::IsItemActive()))

Can be remplaced by if ImGui::isItemActive()
Any reason you added the mouse button check?

I have removed some of the use of ImColor helpers but that's mainly because I'm rather unhappy about this helper, if you want to emit a U32 bits it'd do a back and forth to float which is really a waste of cpu. The color helpers are quite a sorry state right now and needs some cleanup.

How about adding an ImColor32 that also provide the same service but provide an ImU32 storage? It may be confusing, seeing ImColor in the first place is here to bridge the gap between usage of the float4 or the u32 (the later are used by the low-level api).
EDIT Using a macro IMCOL32() for now. May add it to imgui.h or imgui_internal.h

@r-lyeh-archived
Copy link

ah, because I messed it up. I dont know the full API at all :) first baby steps on your lib, sorry.

On the other hand, since most of us are using the lib as a GUI/interface toolkit I guess a color picker is mandatory to have :) We're all making editors with it anyways.

@r-lyeh-archived
Copy link

the color round trip conversion seems the next avoidable step indeed

@ocornut
Copy link
Owner

ocornut commented Jan 16, 2016

I would also like to handle inputs fully because doing any drawing to remove one frame of lack of visual update.

@r-lyeh-archived
Copy link

Off-topic: Can the bottom-right color button width be retrieved by checking style? The fact that the hue/alpha bars do not align perfectly on both sides with the color button is getting me out of my nerves xD

@ocornut
Copy link
Owner

ocornut commented Jan 16, 2016

Off-topic: Can the bottom-right color button width be retrieved by checking style? The fact that the hue/alpha bars do not align perfectly on both sides with the color button is getting me out of my nerves xD

There's a bunch of small issues with size which I've having fixing locally recently. Right now the width passed to PushItemWidth() doesn't result in consistent result. Working on this separately but I wouldn't worry too much right now (and I know it is super frustrating!).

@r-lyeh-archived
Copy link

I mean, current hue color width is 20.f. I suspect the color button width is around 22.f in my theme. I would like to retrieve this 22.f programatically. Therefore, I would set 22 as the hue bar width before rendering the widget, and it would fit perfectly (I guess this width is the only variable missing because inner spacing is already present in formulae).

The other solution/workaround would be to edit ColorEditX and render the color button on the left, and the RGB/HSV/#hex input boxes on the right :) And forget about retrieving sizes 😃

ocornut added a commit that referenced this issue Jan 23, 2016
…dd extra FramePadding.x*2 over that width. (ref #346)

If you had manual pixel-perfect alignment in place it might affect you.
ocornut added a commit that referenced this issue Jan 23, 2016
@ocornut
Copy link
Owner

ocornut commented Aug 8, 2017

By the way I think this is ready to merge into Master so I will proceed. There will probably be some things to change/tweaks prior to 1.51 but it'll get more testing and feedback if it's in Master.

@DubbleClick
Copy link

Which of either picker do you think should be the default default (in case of not calling SetColorEditOptions) ?

The square one in my opinion, it's more commonly found on the internet. It would be nice if it was able to specify whether the little preview rectangle should show the alpha (with the transparency squares) or the filled colour (so alpha set to 255 for the preview). It could be extremely hard to tell the colour with a low alpha otherwise.

@ocornut
Copy link
Owner

ocornut commented Aug 8, 2017

It would be nice if it was able to specify whether the little preview rectangle should show the alpha (with the transparency squares) or the filled colour (so alpha set to 255 for the preview). It could be extremely hard to tell the colour with a low alpha otherwise.

It's already the case, you have 3 different options (no alpha, alpha, half of each)

@RickLamb
Copy link

RickLamb commented Aug 8, 2017

Sorry haven't been following this before. Have you already discussed if this belongs in core imgui.h rather than a separate file of 'batteries-included' widgets or something.

@DubbleClick
Copy link

It's already the case, you have 3 different options (no alpha, alpha, half of each)

Then there's nothing else I could wish for

@ocornut
Copy link
Owner

ocornut commented Aug 8, 2017

Sorry haven't been following this before. Have you already discussed if this belongs in core imgui.h rather than a separate file of 'batteries-included' widgets or something.

Well, I just merged it to master! :)
Right now we don't have a mechanism to split thing easily considering that ColorEdit4 is already an existing/legacy entry point of imgui.

Depending on how imgui evolves I'm not against either splitting it into more files and/or splitting out more stuff. Right now the color-picker branch I think added about 300-400 lines and that includes many improvements to the old ColorEdit4 as well.

@ocornut
Copy link
Owner

ocornut commented Aug 9, 2017

Closing the color picker topic (exactly 100 messages :)

Some ideas for later:

  • Better support for HDR: Right now I think our RGB-HSV roundtrip doesn't play well with unbounded values.
  • Potentially ColorButton could render HDR with a gradient showing the falloff
  • Would like to reorganize the code to allow for adding custom color pickers, but in the meanwhile if someone needs to use a really custom picker they can use ColorButton() and create their own popup.

For reference, here's code for a HDR friendly "always-relative" picker suggested by cupe_cupe
https://twitter.com/cupe_cupe/status/891755433714700289

dgap-ywxsaehbat jpg large

// color editor for 3 or 4 component colors
bool drawColorSelector(const char* label, float height, float* r, float* g, float* b, float* a = nullptr) {
	ImGui::PushID(label);

	ImVec2 buttonStart = ImGui::GetCursorScreenPos();
	
	ImGui::Image((void*)g_wheelTexture, ImVec2(height,height), ImVec2(0,0), ImVec2(1,1));
	ImGui::SetCursorScreenPos(buttonStart);
	ImGui::InvisibleButton(label, ImVec2(height,height)); ImGui::SameLine();

	vec3 rgb = vec3(max(0.f,*r),max(0.f,*g),max(0.f,*b));
	vec3 hsv = rgb_to_hsv(degamma(rgb));

	float h = hsv.r;
	float s = hsv.g;
	float v = hsv.b;

	vec2 onCircle = vec2(cos(h*TAU), sin(h*TAU)) * s;

	ImGui::GetWindowDrawList()->AddCircle(vec2(buttonStart) + vec2(height,height)*0.5f + onCircle * height * 0.5f, 3.0f, ImColor(0,0,0));

	bool changed = false;
	if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0)) {
		float speed = 0.015f;
		if (ImGui::GetIO().KeyShift) {
			speed *= 0.1f;
		}
		onCircle += vec2(ImGui::GetMouseDragDelta() * speed);
		ImGui::ResetMouseDragDelta();
		s = min(1.0f, length(onCircle));
		if (s == 0.0f) {
			h = 0.0f;
		} else { 
			h = atan2f(onCircle.y, onCircle.x) / TAU;
			if (h < 0) {
				h += 1.0f;
			}
		}
		changed = true;
	}
	ImVec4 c = vec4(hsv_to_rgb(h,s,0.5f), 1.0f);
	ImGui::PushStyleColor(ImGuiCol_FrameBg, c);
	changed |= ImGui::VSliderFloat("##v",ImVec2(10,height),&v, 0.0f, 10.0f, "",2.0f);
	ImGui::PopStyleColor();

	ImGui::SameLine();

	if (changed) {
		rgb = gamma(hsv_to_rgb(vec3(h,s,v)));
		*r = rgb.r;
		*g = rgb.g;
		*b = rgb.b;
	}


	if (a) {
		ImGui::VSliderFloat("##a",ImVec2(10,height),a, -10.0f, 10.0f, "",1.5f); ImGui::SameLine();
	}

	ImGui::PopID();
	return changed;
}

The main issue with it is that it relies on a texture (which is easy to render with code, but using polygons doesn't cut it). Currently the font atlas can in theory be Alpha-only so we'd either need to lift this limitation (or e.g: have a different set of available feature depending on if we render the atlas as Alpha-only or RGBA). Or, if that picker is shipped as a separate extension, which would be a healthier direction to go to, it could embed the code to create the texture and it's up to the user to upload it.

I'm starting to consider "repo number 2" which would hold optional extensions and helpers such as Docking, Memory Editor or that sort of Color Picker.

@ocornut ocornut closed this as completed Aug 9, 2017
@jdumas
Copy link
Contributor

jdumas commented Aug 9, 2017

I'm starting to consider "repo number 2" which would hold optional extensions and helpers such as Docking, Memory Editor or that sort of Color Picker.

Does it need to be in a different repo? A folder named plugins/ would be fine imho.

ocornut added a commit that referenced this issue Aug 10, 2017
…: always read a default picker type + forward flag to sub ColorEdit widgets. (#346)
ocornut added a commit that referenced this issue Sep 28, 2017
@lolnobody
Copy link

lolnobody commented Nov 12, 2017

syntax error: identifier ImGuiColorEditFlags when i paste it in imgui.h

@ocornut
Copy link
Owner

ocornut commented Nov 12, 2017

syntax error: identifier ImGuiColorEditFlags when i paste it in imgui.h

  1. This is not how you report an error.
  2. The color picker is now included in imgui by default so you don't need to use any of the code provided here.

@nem0
Copy link
Contributor Author

nem0 commented Nov 20, 2017

One reason why the triangle in the beginning of this thread was done how it was one https://twitter.com/omigamedev/status/932277181962735616, should I open a new issue?

@ocornut
Copy link
Owner

ocornut commented Nov 20, 2017

@nem0 a PR or new issue would be ideal, yes. Thanks!

@jtrites
Copy link

jtrites commented Feb 9, 2021

I'm watching and coding Cherno's OpenGL YT tutorials (made 3 years ago) and run into a problem with the ImGUI ColorPicker4 that comes up in a Debug popup window BUT in all black. I've read the documentation, reviewed the GitHub ImGUI code, and watched some video's on how to use this function. It appears that this code converts (r, g, b, alpha) into HSV(?) which may be causing this problem (or something else).

This is what it looks like:
image

Any ideas?

@PathogenDavid
Copy link
Contributor

To me, that looks like your blending mode and other graphics state are wrong. I wouldn't focus on the color picker here, it looks like your backend is the problem. If you add a call to ImGui::ShowDemoWindow to your code and play around with it, I bet you'll find that every popover looks like that.

(As an aside, you should generally prefer creating new discussions for questions rather than adding to old issues, especially closed issues which have been inactive for over 3 years. Kudos for searching for an existing issue first though!)

@meemknight
Copy link

Hi, I made a simple color edit that also has swatches. I'll leave it here in case anyone would find it useful

image

void addColorButton(const char *id, const ImVec4 &col, float outCol[4])
{
	if (ImGui::ColorButton(id, col))
	{
		outCol[0] = col.x;
		outCol[1] = col.y;
		outCol[2] = col.z;
		outCol[3] = col.w;
	}
}

bool ColorEdit4Swatches(const char *label, float col[4], ImGuiColorEditFlags flags)
{
	bool rez = ::ImGui::ColorEdit4(label, col);

	::ImGui::BeginGroup();
	::ImGui::PushID(label);

	if (::ImGui::BeginPopup("picker"))
	{
		addColorButton("0", {0,0,0,1}, col); ImGui::SameLine(); 
		addColorButton("1", {1,0,0,1}, col); ImGui::SameLine();
		addColorButton("2", {0,1,0,1}, col); ImGui::SameLine();
		addColorButton("3", {0,0,1,1}, col); ImGui::SameLine();
		addColorButton("4", {1,1,0,1}, col); ImGui::SameLine();
		addColorButton("5", {1,0,1,1}, col); ImGui::SameLine();
		addColorButton("6", {0,1,1,1}, col); ImGui::SameLine();
		addColorButton("7", {1,1,1,1}, col);
		
		::ImGui::EndPopup();
	}

	::ImGui::PopID();
	::ImGui::EndGroup();

	return rez;
}```

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