Page 1 of 3

Color picker gui

Posted: Mon Dec 22, 2014 1:23 am
by Ovan
plop everyone I developpe a small, minimalist and cute color picker pane to replace the internal irrlicht IGUIColorSelectDialog that is really poor
for once I share my code so:

Image Image

Code: Select all

#ifndef __C_GUI_COLOR_PICKER_HEADER__
#define __C_GUI_COLOR_PICKER_HEADER__
 
#include <irrlicht/IGUIElement.h>
#include <irrlicht/S3DVertex.h>
 
 
/**
 * Copyright (C) <2014> <Jehan-antoine vayssade>
 * Ovan/Magun contact on irrlicht-fr.org or ovan@sleek-think.ovh
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
**/
 
namespace irr
{
    namespace gui
    {
        class IGUIButton;
        class IGUIStaticText;
        class IGUIScrollBar;
 
        class CGUIColorPicker : public IGUIElement
        {
            public:
                CGUIColorPicker(IGUIEnvironment *environment, IGUIElement *parent, s32 id = 0) noexcept;
                ~CGUIColorPicker() noexcept;
 
                virtual void setRelativePosition(const core::recti &r);
 
                virtual bool OnEvent(const SEvent&) noexcept;
                virtual void updateAbsolutePosition();
 
                virtual void setPickedColor(const video::SColor&) noexcept;
                virtual const video::SColor& getPickedColor() const noexcept;
 
                virtual void setBackgroundColor(const video::SColor&) noexcept;
                virtual const video::SColor& getBackgroundColor() const noexcept;
 
                virtual void draw();
            protected:
                bool isGradient, isColor, isInside;
                virtual void recalculatePickedColor() noexcept;
                virtual void createAlphaTexture() noexcept;
                virtual void createGradientTexture() noexcept;
            protected:
                IGUIButton      *close;
                IGUIScrollBar   *scroll;
                video::ITexture *img[2];
            protected:
                video::SColor    pickcolor, color;
                video::SColor    background, white, black, alpha;
                core::recti      box, pickbox, gradient;
                core::vector2di  pickpos;
                int              colorpos;
        };
    }
}
 
#endif

Code: Select all

#include "CGUIColorPicker.h"
 
#include <irrlicht/IGUIEnvironment.h>
#include <irrlicht/IGUIButton.h>
#include <irrlicht/IGUIScrollBar.h>
#include <irrlicht/IVideoDriver.h>
#include <irrlicht/irrMath.h>
#include <irrlicht/SColor.h>
 
namespace irr
{
    inline core::vector3df RGBftoHSV(const video::SColorf &rgb)
    {
        core::vector3df hsv;
 
        f32 M = core::max_(rgb.getRed(), rgb.getGreen(), rgb.getBlue());
        f32 m = core::min_(rgb.getRed(), rgb.getGreen(), rgb.getBlue());
        f32 C = M - m;
 
        if(C == 0)
            hsv.X = 0;
        else if(M <= rgb.getRed())
            hsv.X =((rgb.getGreen() - rgb.getBlue()) / C);
        else if(M <= rgb.getGreen())
            hsv.X =((rgb.getBlue() - rgb.getRed()) / C) + 2;
        else if(M <= rgb.getBlue())
            hsv.X =((rgb.getRed() - rgb.getGreen()) / C) + 4;
 
        hsv.X *= 60;
        if(hsv.X < 0)
            hsv.X += 360;
 
        hsv.Y = M;
 
        if(hsv.Y == 0) hsv.Z = 0;
        else hsv.Z = C / hsv.Y;
 
        return hsv;
    }
    namespace gui
    {
        CGUIColorPicker::CGUIColorPicker(IGUIEnvironment *environment, IGUIElement *parent, s32 id) noexcept
            : IGUIElement(EGUIET_COLOR_SELECT_DIALOG , environment, parent, id, {200, 200, 310, 360}),
              background{64, 255, 255, 255}, white{255, 255, 255, 255}, black{255, 0, 0, 0},
              colorpos(0), isGradient(false), isColor(false)
        {
            close = Environment->addButton({5, 140, 85, 156}, this, 0, L"take this color");
 
            scroll = Environment->addScrollBar(true, {5, 125, 85, 135}, this);
            scroll->setMin(0);
            scroll->setMax(255);
            scroll->setPos(255);
 
            createAlphaTexture();
            createGradientTexture();
 
            setPickedColor({255, 64, 64, 128});
            updateAbsolutePosition();
        }
        CGUIColorPicker::~CGUIColorPicker() noexcept
        {
            close->drop();
            scroll->drop();
            img[0]->drop();
            img[1]->drop();
        }
        void CGUIColorPicker::createAlphaTexture() noexcept
        {
            img[0] = Environment->getVideoDriver()->addTexture({16, 16}, "alpha", video::ECF_A8R8G8B8);
            u32 *tmp = (u32*) img[0]->lock();
 
            video::SColor color;
 
            #define square(colorstart, sx, sy, sz, sw)                         \
                color = colorstart;                                            \
                for(int y=sy; y<sw; ++y)                                       \
                    for(int x=sx; x<sz; ++x)                                   \
                        color.getData(&tmp[x + y*16], video::ECF_A8R8G8B8);    \

            square(video::SColor(255, 153, 153, 153), 0, 0,  8,  8);
            square(video::SColor(255, 153, 153, 153), 8, 8, 16, 16);
            square(video::SColor(255, 102, 102, 102), 8, 0, 16,  8);
            square(video::SColor(255, 102, 102, 102), 0, 8,  8, 16);
 
            img[0]->unlock();
        }
        void CGUIColorPicker::createGradientTexture() noexcept
        {
            img[1] = Environment->getVideoDriver()->addTexture({15, 151}, "gradient", video::ECF_A8R8G8B8);
            u32 *tmp = (u32*) img[1]->lock();
 
            video::SColor from;
            video::SColor to;
 
            #define interpolate(colorstart, colorend, start, end)              \
                from = colorstart;                                             \
                to = colorend;                                                 \
                                                                               \
                for(int y=start; y<end; ++y)                                   \
                {                                                              \
                    video::SColor c = to.getInterpolated(from, (y-start)/25.f);\
                    for(int x=0; x<15; ++x)                                    \
                        c.getData(&tmp[x + y*15], video::ECF_A8R8G8B8);        \
                }
 
            interpolate(video::SColor(255, 255, 0, 0),   video::SColor(255, 255, 0, 255),   0,  25);
            interpolate(video::SColor(255, 255, 0, 255), video::SColor(255, 0, 0, 255),    25,  50);
 
            interpolate(video::SColor(255, 0, 0, 255), video::SColor(255, 0, 255, 255),  50,  75);
            interpolate(video::SColor(255, 0, 255, 255), video::SColor(255, 0, 255, 0),  75, 100);
 
            interpolate(video::SColor(255, 0, 255, 0), video::SColor(255, 255, 255, 0), 100, 125);
            interpolate(video::SColor(255, 255, 255, 0), video::SColor(255, 255, 0, 0), 125, 151);
 
            img[1]->unlock();
        }
        void CGUIColorPicker::setRelativePosition(const core::recti &r)
        {
            RelativeRect.UpperLeftCorner = r.UpperLeftCorner;
            RelativeRect.LowerRightCorner.X = r.UpperLeftCorner.X + 110;
            RelativeRect.LowerRightCorner.Y = r.UpperLeftCorner.X + 160;
        }
        bool CGUIColorPicker::OnEvent(const SEvent &event) noexcept
        {
            if(event.EventType == EET_MOUSE_INPUT_EVENT)
            {
                core::vector2di pos(event.MouseInput.X, event.MouseInput.Y);
 
                if(event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
                {
                    isGradient = gradient.isPointInside(pos);
                    isColor = box.isPointInside(pos);
                    isInside = AbsoluteRect.isPointInside(pos);
                }
 
                if(event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
                {
                    // does not work since 1.7 (i think)
                    // probably need CGUIModalScreen if this module is add to the engine
                    //if(!AbsoluteRect.isPointInside(pos) && !isInside)
                    //{
                    //    SEvent event;
                    //    event.EventType = EET_GUI_EVENT;
                    //    event.GUIEvent.Caller = this;
                    //    event.GUIEvent.Element = 0;
                    //    event.GUIEvent.EventType = EGET_ELEMENT_CLOSED;
                    //    Parent->OnEvent(event);
                    //    remove();
                    //    drop();
                    //}
                    isGradient = isColor = false;
                }
 
                if(isGradient)
                {
                    if(pos.X < gradient.UpperLeftCorner.X)
                        pos.X = gradient.UpperLeftCorner.X;
                    if(pos.Y < gradient.UpperLeftCorner.Y)
                        pos.Y = gradient.UpperLeftCorner.Y;
 
                    if(pos.X > gradient.LowerRightCorner.X)
                        pos.X = gradient.LowerRightCorner.X;
                    if(pos.Y > gradient.LowerRightCorner.Y)
                        pos.Y = gradient.LowerRightCorner.Y;
 
                    colorpos = pos.Y - gradient.UpperLeftCorner.Y;
                    u32 *tmp =(u32*)img[1]->lock();
                    color.set(tmp[colorpos*img[1]->getOriginalSize().Width]);
                    img[1]->unlock();
                    recalculatePickedColor();
                }
 
                if(isColor)
                {
                    if(pos.X < box.UpperLeftCorner.X)
                        pos.X = box.UpperLeftCorner.X;
                    if(pos.Y < box.UpperLeftCorner.Y)
                        pos.Y = box.UpperLeftCorner.Y;
 
                    if(pos.X > box.LowerRightCorner.X)
                        pos.X = box.LowerRightCorner.X;
                    if(pos.Y > box.LowerRightCorner.Y)
                        pos.Y = box.LowerRightCorner.Y;
 
                    pickpos.X = pos.X - box.UpperLeftCorner.X;
                    pickpos.Y = pos.Y - box.UpperLeftCorner.Y;
                    recalculatePickedColor();
                }
 
                if(isGradient || isColor)
                    return true;
            }
 
            if(event.EventType == EET_GUI_EVENT)
            {
                switch(event.GUIEvent.EventType)
                {
                    case EGET_BUTTON_CLICKED:
                        SEvent event;
                        event.EventType = EET_GUI_EVENT;
                        event.GUIEvent.Caller = this;
                        event.GUIEvent.Element = 0;
                        event.GUIEvent.EventType = EGET_FILE_SELECTED;
                        Parent->OnEvent(event);
                    break;
                    case EGET_SCROLL_BAR_CHANGED:
                        recalculatePickedColor();
                        return true;
                    break;
                }
            }
 
            return IGUIElement::OnEvent(event);
        }
        void CGUIColorPicker::recalculatePickedColor() noexcept
        {
            video::SColor hcolor = color.getInterpolated(white, pickpos.X/80.f);
 
            pickcolor = black.getInterpolated(hcolor, pickpos.Y/80.f);
            pickcolor.setAlpha(scroll->getPos());
 
            alpha = color;
            alpha.setAlpha(0);
        }
        void CGUIColorPicker::setPickedColor(const video::SColor &c) noexcept
        {
            pickcolor = c;
 
            core::vector3df hsv = RGBftoHSV({
                c.getRed()/255.f,
                c.getGreen()/255.f,
                c.getBlue()/255.f,
                c.getAlpha()/255.f
            });
 
            colorpos = 150-hsv.X/360.f*150.f;
            pickpos.X = hsv.Y*80.f;
            pickpos.Y = 80-(hsv.Z)*80.f;
 
            u32 *tmp =(u32*)img[1]->lock();
            color.set(tmp[colorpos*img[1]->getOriginalSize().Width]);
            img[1]->unlock();
 
            alpha = color;
            alpha.setAlpha(0);
        }
        const video::SColor& CGUIColorPicker::getPickedColor() const noexcept
        {
            return pickcolor;
        }
        void CGUIColorPicker::setBackgroundColor(const video::SColor &b) noexcept
        {
            background = b;
        }
        const video::SColor& CGUIColorPicker::getBackgroundColor() const noexcept
        {
            return background;
        }
        void CGUIColorPicker::updateAbsolutePosition()
        {
            IGUIElement::updateAbsolutePosition();
 
            box.UpperLeftCorner = AbsoluteRect.UpperLeftCorner;
            box.LowerRightCorner = AbsoluteRect.UpperLeftCorner;
            box.UpperLeftCorner.X += 5;
            box.UpperLeftCorner.Y += 5;
            box.LowerRightCorner.X += 85;
            box.LowerRightCorner.Y += 85;
 
            gradient.UpperLeftCorner = AbsoluteRect.UpperLeftCorner;
            gradient.LowerRightCorner = AbsoluteRect.UpperLeftCorner;
            gradient.UpperLeftCorner.X += 90;
            gradient.UpperLeftCorner.Y += 5;
            gradient.LowerRightCorner.X += 105;
            gradient.LowerRightCorner.Y += 155;
 
            pickbox.UpperLeftCorner = AbsoluteRect.UpperLeftCorner;
            pickbox.LowerRightCorner = AbsoluteRect.UpperLeftCorner;
            pickbox.UpperLeftCorner.X += 5;
            pickbox.UpperLeftCorner.Y += 90;
            pickbox.LowerRightCorner.X += 85;
            pickbox.LowerRightCorner.Y += 120;
        }
        void CGUIColorPicker::draw()
        {
            Environment->getSkin()->draw3DSunkenPane(
                this, background,
                false, true,
                AbsoluteRect,
                &AbsoluteClippingRect
            );
 
            IGUIElement::draw();
 
            Environment->getVideoDriver()->draw2DImage(img[1], {
                AbsoluteRect.UpperLeftCorner.X+90,
                AbsoluteRect.UpperLeftCorner.Y+5
            });
 
            // 2 draw because the interpolation in the diagonal is not well rendered
            Environment->getVideoDriver()->draw2DRectangle(black, box, &AbsoluteClippingRect);
            Environment->getVideoDriver()->draw2DRectangle(box, white, color, alpha, alpha, &AbsoluteClippingRect);
 
            {
                const core::vector2di start =  {AbsoluteRect.UpperLeftCorner.X+90,  AbsoluteRect.UpperLeftCorner.Y+5+colorpos};
                const core::vector2di end =    {AbsoluteRect.UpperLeftCorner.X+105,  AbsoluteRect.UpperLeftCorner.Y+5+colorpos};
                const core::vector2di hstart = {box.UpperLeftCorner.X,  box.UpperLeftCorner.Y+pickpos.Y};
                const core::vector2di hend =   {box.LowerRightCorner.X, box.UpperLeftCorner.Y+pickpos.Y};
                const core::vector2di vstart = {box.UpperLeftCorner.X+pickpos.X, box.UpperLeftCorner.Y};
                const core::vector2di vend =   {box.UpperLeftCorner.X+pickpos.X, box.LowerRightCorner.Y};
 
                Environment->getVideoDriver()->draw2DLine({ start.X,    start.Y-1}, { end.X,    end.Y-1}, white);
                Environment->getVideoDriver()->draw2DLine({ start.X,    start.Y+1}, { end.X,    end.Y+1}, white);
                Environment->getVideoDriver()->draw2DLine({hstart.X,   hstart.Y-1}, {hend.X,   hend.Y-1}, white);
                Environment->getVideoDriver()->draw2DLine({hstart.X,   hstart.Y+1}, {hend.X,   hend.Y+1}, white);
                Environment->getVideoDriver()->draw2DLine({vstart.X-1,   vstart.Y}, {vend.X-1, vend.Y  }, white);
                Environment->getVideoDriver()->draw2DLine({vstart.X+1,   vstart.Y}, {vend.X+1, vend.Y  }, white);
 
                Environment->getVideoDriver()->draw2DLine(start,   end, black);
                Environment->getVideoDriver()->draw2DLine(hstart, hend, black);
                Environment->getVideoDriver()->draw2DLine(vstart, vend, black);
            }
 
            Environment->getVideoDriver()->draw2DImage(img[0], pickbox, pickbox);
            Environment->getVideoDriver()->draw2DRectangle(
                pickcolor, pickbox,
                &AbsoluteClippingRect
            );
        }
    }
}
in accordance with that of irrlicht EGET_FILE_SELECTED is return "take this color" is clicked

my original post at http://irrlicht-fr.org/viewtopic.php?pid=11560 ;)

Re: Color picker gui

Posted: Mon Dec 22, 2014 1:14 pm
by Seven
Awesome!

Re: Color picker gui

Posted: Mon Dec 22, 2014 2:15 pm
by CuteAlien
Hi, could you consider putting this code under the zlib-license? The current color-picker in the engine never worked, so I would be glad if we had a replacement. I haven't tested this yet, but looks nice from screenshots, so maybe we could replace the one in the engine.

Re: Color picker gui

Posted: Mon Dec 22, 2014 2:45 pm
by Ovan
yes off course (I hoped this)
I edit my previous message to add the licence on the header (it's good?)
can need some minor rewrite i use brace-initialization of c++11 feature

Re: Color picker gui

Posted: Mon Dec 22, 2014 5:10 pm
by CuteAlien
Thanks! I suppose header is good. Although in Irrlicht we tend to just mention that the license is beside the sources, I hope that's fine. I will also have to do some minor rewrites before adding it (Irrlicht style slightly different and maybe we can replace the define by some function or even have one for that already). But that's minor stuff. I'll try to find some time to add it in the new year (probably no time before that).

Re: Color picker gui

Posted: Mon Dec 22, 2014 5:48 pm
by Ovan
Cool :)
you can add CGUIModalScreen or something similar when is created by IGUIEnvironment ?
make it as "popup" element if is add as modal, would be useful

Re: Color picker gui

Posted: Mon Dec 22, 2014 6:38 pm
by CuteAlien
Yeah, old one also had a flag for that.

Re: Color picker gui

Posted: Mon Dec 22, 2014 8:37 pm
by hendu
OT: Who named the function getData, when it clearly *puts* data... :P

Re: Color picker gui

Posted: Mon Dec 22, 2014 9:47 pm
by Ovan
http://irrlicht.sourceforge.net/docu/cl ... f28be96bc9
no it does not put data it really get data from color to a ptr memory and here this memory is the texture at coord [x, y]

same if you write

irr::u32 i;
color.getData(&i, ...);

Re: Color picker gui

Posted: Tue Dec 23, 2014 8:05 am
by hendu
No, it doesn't get anything. It writes the color value into the data pointer. Getting would mean "get the color data *from* this pointer".

Re: Color picker gui

Posted: Tue Dec 23, 2014 3:00 pm
by CuteAlien
It's correct. Getter functions in classes are about getting the data from the class. The data pointer is the target. It doesn't matter if the target is an lvalue or a reference or a pointer into which to write the result - it's always a getter function. This way the name tells you some information which is not as easy visible on first view when looking at the function declaration.

Re: Color picker gui

Posted: Tue Dec 23, 2014 8:29 pm
by hendu
I still think it's poorly named. getDataInto would be far more understandable if putData is against the irr style. Has it been in a release yet?

Re: Color picker gui

Posted: Tue Dec 23, 2014 9:57 pm
by ent1ty
Looks like the RGBftoHSV function is from my snippet here. If it makes into the engine, acknowledgement would be nice :P

Re: Color picker gui

Posted: Tue Dec 23, 2014 10:42 pm
by Ovan
yeah sorry to not mention your code, I forget and thanks ;)

and no hendu, this name is clearly the right name it's the same thing if you write
void getSquare(int x, int *result) { *result = x*x; }
void getSquare(int x, int &result) { result = x*x;}
int getSquare(int x) { return x*x; }
all 3 function do the same thing, (in old C style "getSquare(int x, int *result)" is highly used)
and in this case for SColor a ptr is needed because the size of the returned value can change (due to E_COLOR_FORMAT)

getter function mean you get from the object (here is color) not from the parameter, the parameter is clearly the returned value

Re: Color picker gui

Posted: Fri Dec 26, 2014 4:14 pm
by hendu
Logically a C style function would be "int getSquare(int)", returning it. Having a random result pointer without a different name would be confusing.

Like in this case, a reader may read it as "get data *into* this SColor from this pointer" like I did. It is not clear without changing the name, because we have 99% of getFoo functions that return the value.

edit:

Its pair, setData, has the same confusion. It can be read either way, set data from this SColor to the pointer, or from the pointer to this SColor. setDataFrom and getDataInto would clear both up.