ocornut / imgui

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

Color picker #346

Closed nem0 closed 7 years ago

nem0 commented 9 years ago

(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 commented 9 years ago

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 commented 9 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

:+1:

is the SV picker exact?

r-lyeh-archived commented 8 years ago

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

ocornut commented 8 years ago

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:

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 commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

nem0 commented 8 years ago

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

ocornut commented 8 years ago

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 commented 8 years ago

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

ocornut commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

the color round trip conversion seems the next avoidable step indeed

ocornut commented 8 years ago

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

r-lyeh-archived commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 :smiley:

ocornut commented 8 years ago

I have updated the gist https://gist.github.com/ocornut/9a55357df27d73cb8b34

picker

Above commit fixes the issue I was referring to with PushItemWidth(). Exact alignment here if we want the color button to align with the picking bar is a little nasty, the color button uses (FontSize+FramePadding.y*2) for size so I'm using that. However I'm not super happy with ColorButton() in the first place, it's API should probably be fixed.

Now I am trying to figure out the relationship between ColorEdit and ColorPicker, how both can be used together and separately. It is easy to turn the colorbutton of ColorEdit into a picker popup. In the case of using a popup it may make sense to expose RGB and HSV and HEX together.

r-lyeh-archived commented 8 years ago

What about ColorEdit4( float*, show_alpha, show_picker ); ? If show_picker = true, draw a triangle on top of button color to toggle color picker logic/rendering. If enabled, it should be displayed after the color edit widget and before the next one (ie, bottom).

r-lyeh-archived commented 8 years ago

The current snippet is misaligned on my build. Do I need to update the repo as well?

image

ocornut commented 8 years ago

Yes I made minor breaking changes to imgui.cpp i've been wanting to do for a while. Been adding various flags to ColorEdit functions right now. I'm out but i'll try to resume that work together.

Basically showing the picker in a popup when clicking on the color.

r-lyeh-archived commented 8 years ago

:+1:

ocornut commented 8 years ago

I made a first pass in a branch.

2016-02-21_23-28-38

capture

ColorEdit

ColorPicker

Inline with ImGui::ColorPicker4("color", (float*)&clear_color, ImGuiColorEditFlags_NoSliders); capture

// Enumeration for ColorEdit3() / ColorEdit4() / ColorPicker3() / ColorPicker4()
enum ImGuiColorEditFlags_
{
    ImGuiColorEditFlags_Alpha           = 1 << 0,   // ColorEdit/ColorPicker: show/edit Alpha component. Must be 0x01 for compatibility with old API taking bool
    ImGuiColorEditFlags_RGB             = 1 << 1,   // ColorEdit: Choose one among RGB/HSV/HEX. User can still use the options menu to change. ColorPicker: Choose any combination or RGB/HSX/HEX.
    ImGuiColorEditFlags_HSV             = 1 << 2,
    ImGuiColorEditFlags_HEX             = 1 << 3,
    ImGuiColorEditFlags_NoPicker        = 1 << 4,   // ColorEdit: Disable picker when clicking on colored square
    ImGuiColorEditFlags_NoOptions       = 1 << 5,   // ColorEdit: Disable toggling options menu when right-clicking colored square
    ImGuiColorEditFlags_NoColorSquare   = 1 << 6,   // ColorEdit: Disable colored square
    ImGuiColorEditFlags_NoSliders       = 1 << 7,   // ColorPicker: Disable RGB/HSV/HEX sliders
    ImGuiColorEditFlags_ModeMask_       = ImGuiColorEditFlags_RGB|ImGuiColorEditFlags_HSV|ImGuiColorEditFlags_HEX
};

Some remaining issues to work on: (probably more to come)

r-lyeh-archived commented 8 years ago

not showing a preview of the color in the picker what about the background for the #66b20080 hexcode?

uses right-click context menu + tooltip What if the color button... 1) uses left-button to show/hide the popup 2) has the hovering tooltip as suggested 3) has the ".." text printed inside the color button (no i18n issues with that :)

contextual menu in ColorEdit shows options in English Two routes: 1) Discard the Edit as... text and use RGB/HSV/Hex only. as they'll be probably the same in most if not all languages. 2) Provide a i18n/L10n interface in the same way that we tweak colors. For me it would be something like...

enum ImGuiLang {
IMGUI_LANG_EDIT_AS_RGB,
IMGUI_LANG_EDIT_AS_HSV,
//...
};
ImGui &lang = ImGui::GetLang();
lang[ IMGUI_LANG_EDIT_AS_RGB ] = "Editar como RGB"; // ES-es
lang[ IMGUI_LANG_EDIT_AS_HSV ] = "Editar como HSV"; // ES-es
// ...
nem0 commented 8 years ago

@ocornut good job

RGB/HSV/HEX

does this contex menu need to be in the core? Users can do it manually anyway.

DubbleClick commented 8 years ago

Where did all this go in imgui version 1.50?

ColorPicker3/4 are missing and ColorEdit3/4 are buggy, can't be edited.

ocornut commented 8 years ago

ColorPickers aren't merged (they are in a branch) because design isn't locked.

ColorEdit should work, what is the problem? Can't you drag the values?

DubbleClick commented 8 years ago

Oh, couldn't find that branch.

As for ColorEdit3/4 in the 1.50 version, I couldn't replace the values. Ctrl + left click would work to select one, however any numbers entered would just straight away be reset to 0 again. Not sure whether that was just a temporary bug, as my main objective were Color Pickers.

Anyway I took the code from version 1.48 and merged and edited it into the 1.50 version, which works well except for an issue when multiple ColorEdits have the same name.

ratchetfreak commented 8 years ago

ImGui has a problem in general when multiple items have the same name, which is why the ###ID system exists

https://github.com/ocornut/imgui/blob/master/imgui.cpp#L300

ocornut commented 8 years ago

@DubbleClick

Oh, couldn't find that branch.

Look at the list of branches.

As for ColorEdit3/4 in the 1.50 version, I couldn't replace the values.

I just tested master again, and it seems to all work. Perhaps you aren't using it correctly and aren't retaining your value. Please provide a repro/failing code in another thread.

Anyway I took the code from version 1.48 and merged and edited it into the 1.50 version

Not sure which "code from version 1.48" you are referring to?

ImGui has a problem in general when multiple items have the same name, which is why the ###ID system exists

It's not a "problem" it is essential and core to how the system was designed, @DubbleClick please read the FAQ.

DubbleClick commented 8 years ago

I just tested master again, and it seems to all work. Perhaps you aren't using it correctly and aren't retaining your value.

Perhaps, I didn't care to see what's wrong since ColorEdit wasn't really what I'm looking for anyways.

Not sure which "code from version 1.48" you are referring to?

https://github.com/ocornut/imgui/blob/9e817a7c38ba72def5fde244a9695eb4985e1bcd/imgui.cpp

ImGui has a problem in general when multiple items have the same name, which is why the ###ID system exists

I first used a version before ID scoping was added. Thanks for the FAQ link.

ocornut commented 8 years ago

Interestingly, only the commit to the branch that I've marked as linked to this issue are linked here, but the branch is roughly being kept up to date. The 1.48 just happen to be version at the time I made that commit linked above. (just updated the branch right now to be in sync with master).

I first used a version before ID scoping was added. Thanks for the FAQ link.

That's unrelated, the small commit above was definitively a bug in my early implementation of ColorPicker(). All widgets in ImGui have a ID which is derived from the id-stack + their label which itself can contain additional ID information. The short version is that if you want to have two widgets with the same name next to each others you need to push something on the ID stack or differentiate their ID with the ## marker within the label.

You can merge the branch if you want or borrow some other code from this thread. However note that the branch is unfinished and I still expect to redo and maybe break a few API flags, hence why it is still in a branch.

cgimenez commented 8 years ago

Hi all ! I need to drive a dozen of HSV light bulbs (Lifx) and I didn't find a way with the color picker code above... Dunno if it's me but the HSV values returned were wrong. I compared with http://colorizer.org/, they never matched...

So I hacked this one, which seems to works, not very DRY but multiple instances allowed and size fixed (200 px width). There's a bug that prevents labels 'H', 'S', 'B', to be displayed at the right of the sliders. Might be related with cursor coordinates but, for now, they are not displayed. Who knows where they are ;-)

https://gist.github.com/cgimenez/ba5111b8231afa2578c85f0bfcfdf8cf

Any improvement, optimization, idea welcome !

ocornut commented 8 years ago

@cgimenez The code here (and in the branch) always takes inputs as RGB/RGBA and transform/expose as RGB+HSV on the fly. Maybe you expected is to read HSVA?

Could you post a picture of your widget?

cgimenez commented 8 years ago

capture d ecran 2016-07-28 a 22 13 34

Input is RGB ? Ok... I misunderstood the widget so ;-) I thought it needed to be fed with HSV... My bad. But being HSV in & HSV out (with RGB for screen) is easier to me

ocornut commented 7 years ago

Dropping an idea here.

Something that was bothering me was the fact that e.g. ColorEdit4() would call the color picker, which render the possibility of creating a custom color picker less practical.

The way I'm thinking of solving this is by providing an (entirely optional) function pointer to allow for color picker override. Those (rare) function pointers (each carrying an api/semantic) would make it easier to have complex widget XX uses complex widget YY, etc. and reduce the need to have a color picker supporting every bell and whistle.

jdumas commented 7 years ago

Hi,

I was wondering if there are any plan to merge the color picker into the main branch? I've tried it out and it looks like it's working great. Providing a callback for an optional color picker function also seems like a reasonable approach to me.

I just have a minor suggestion regarding the current color square of the ColorEdit functions: I would suggest adding a frame of a different color (similar to the checkbox). Otherwise, when the picked color is black, or a dark grey, it becomes impossible to distinguish it from the background, which is a bit unfortunate.

ocornut commented 7 years ago

Hello Jerémie, yes I very much plan to return back to that this summer (I barely touched imgui for a year aside from navigation stuff), and color picker will be an early thing to merge. The branch has been kept up to date this year so it should be mergeable in the meanwhile.

Frame: the problem is that it would look out of place in many situations. I also agree it'd be useful. I'll look at enforcing a BorderCol in there.

jdumas commented 7 years ago

Cool! Looking forward to it then.

For the frame, why would it look more out of place than the frame of the current CheckBox() for example?

ocornut commented 7 years ago

I was thinking of a border (which are off by default), if it's a regular Frame color it may be more in place.

ocornut commented 7 years ago

Made a few commits on the ColorEdit, ColorPicker, ColorButton functions, it's becoming pretty good and flexible imho.

demo

I'm working (not committed) on tweaking the layout to show current vs reference color, except the Reference Color can only work in certain contexts (e.g. backing up reference color when opening Popup is possible, but not when using an inline color picker): ref

I'm considering just removing the ALPHA bar (or at least having it off by default). It doesn't actually feel really important/useful. What do you think? Perhaps it could requires an extra flag to enable.

alpha

johanwendin commented 7 years ago

I'm using the alpha bar very very frequently. But I could always make a copy of the current one if need be. If at all possible leave the functionality in, enable with an extra flag if you must but please leave it in. :)