5
\$\begingroup\$

I am trying to create a Color Picker similar to that of MS Paint.

Unfortunately, I can't figure out the algorithm for saturation.

enter image description here

This is what my current algorithm creates. Anytime I try to perform a saturated effect going down on the Y axis, it just makes everything after the first line completely red or black.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SFML;
using SFML.Graphics;
using SFML.Window;

namespace Source
{
class ColorWheel : IGameObject
{
    Image image = new Image(255*6,355);
    Color color = new Color(255,0,0);
    int brightness = 255;
    public Sprite sprite = new Sprite();

    public ColorWheel()
    {
        for (int y = 0; y < 255; y++)
        {
            for (int x = 0; x < 255 * 6; x++)
            {
                //Red 255 - Green 0-254
                if (color.R == brightness && color.G < brightness && color.B == 0)
                {
                    color.G += 1;

                    //color.R -= (byte)y;
                    //color.G += (byte)y;
                    //color.B += (byte)y;
                }
                //Green 255 - Red 255-0
                else if (color.R > 0 && color.G == brightness && color.B == 0)
                {
                    color.R -= 1;

                    //color.R -= (byte)y;
                    //color.G -= (byte)y;
                    //color.B += (byte)y;
                }
                //Green 255 - Blue 0-255
                else if (color.R == 0 && color.G == brightness && color.B < brightness)
                {
                    color.B += 1;

                    //color.R += (byte)y;
                    //color.G -= (byte)y;
                    //color.B += (byte)y;
                }
                //Blue 255 - Green 255-0
                else if (color.R == 0 && color.G > 0 && color.B == brightness)
                {
                    color.G -= 1;

                    //color.R += (byte)y;
                    //color.G -= (byte)y;
                    //color.B -= (byte)y;
                }
                //Blue 255 - Red 0-255
                else if (color.R < brightness && color.G == 0 && color.B == brightness)
                {
                    color.R += 1;

                    //color.R += (byte)y;
                    //color.G += (byte)y;
                    //color.B -= (byte)y;
                }
                //Red 255 - Blue 255-0
                else if (color.R == brightness && color.G == 0 && color.B > 0)
                {
                    color.B -= 1;

                    //color.R -= (byte)y;
                    //color.G += (byte)y;
                    //color.B -= (byte)y;
                }

                image.SetPixel((uint)x, (uint)y, color);
            }

                //brightness--;
        }

        Texture texture = new Texture(image);
        sprite.Texture = texture;
    }

    public void Update(double dt)
    {
    }

    public void Render(RenderWindow rWindow)
    {
        rWindow.Draw(sprite);
    }

}

}

\$\endgroup\$
  • 1
    \$\begingroup\$ Have you tried implementing the HSL or HSV conversion algorithms covered here? en.wikipedia.org/wiki/HSL_and_HSV \$\endgroup\$ – DMGregory Dec 16 '13 at 6:42
  • \$\begingroup\$ You might want to Nuget Colormine which does about whatever you want when it comes to color operations/conversions. colormine.org \$\endgroup\$ – Darrell Plank Nov 28 '18 at 14:11
3
\$\begingroup\$

If not going with HSL or a similar method, and staying with RGB, it helps to understand the relationship.

As white is [255, 255, 255], lowering the G and B values similarly will result in a very light red color (shades of pink). When the color becomes [255, 0, 0], the color is a stark, bright, primary red. As that red value is lowered, the red becomes darker until black at 0, 0, 0. Lowering the red before the G and B values reach 0 (i.e. r > g == b) creates a desaturated "greyish" red.

This same process is true for any hue. Green is simply the G value being higher than the R and B values, yellow is the combination of R and G values overwhelming the B.

[255, 255, x] where x is 0 to 255 is a bright to light yellow. [x, x, 0] where x is 0 to 255 is a dark to bright yellow. [x, x, z], where x is variable and z is less than x results in varying brightness of a desaturated yellow.

Thus, hue is essentially the proportion of the two highest of the three RGB values. Saturation is essentially the proportion of that hue to the third value. If the hue is high, the color is bright. If the third color is also high, the color is light. If the hue is low, the color is darker. If the third color is high and the hue is low, the color is a murkier or greyer color.

| improve this answer | |
\$\endgroup\$
6
\$\begingroup\$

If you want to have a colour-picker with Hue on one axis and Saturation on the other, it might be good to think of your colours in terms of the Hue-Saturation-Value (HSV) or Hue-Saturation-Lightness (HSL) colourspaces instead of Red-Green-Blue (RGB).

There's no magic going on here -- HSL and HSV are just another another way of thinking about colour.


Example:

In HSV, (0, 0.5, 0.5) is 0 along the Hue axis (which is pure red), 0.5 along the Saturation (which is a somewhat greyish colour) and 0.5 along the Value (which gives a medium shade). The result is quite an unsaturated red.

Here's what that looks like in GIMP's colour picker:

HSV(0,0.5,0.5) in Gimp's colour picker

The values for H, S and V on the right there are a little confusing because GIMP treats Hue as between 0 and 360 and Saturation and Value between 0 and 100, but the principle is the same.

You can also see on the right what RGB values that would translate to. Apparently, such a red is (128, 64, 64) in RGB.

A different Hue might look like this:

A different hue

Note how the S and V sliders are in the same place. I've only changed the Hue value. You could very easily write some code that steps through the different Hues, as well as through different Saturations to create a 2-dimensional grid of HSL values that represent the colour picker you want.

Note that the RGB sliders have changed a lot! GIMP automatically calculates what the RGB equivalent of an HSV colour is.


Conversion

You'll probably want to convert your HSV colours to RGB equivalents so your renderer can handle them.

This conversion is not too complicated. In fact, there are some really good answers on StackOverflow, along with code you can easily adapt to your language.

However, if you're curious, the general idea behind the conversion (and perhaps a nice analogy to make the idea of a "colourspace" clearer) is this: The same way you can think of RGB as a cube with R, G and B each along one axis of it...

RGB as a cube
Thanks, Wikipedia!

... you can think of the HSV colourspace as a cylinder:

HSV as a cylinder
Wikipedia again.

The conversion essentially stretches and bends that cylinder to match with the cube.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Here is the answer. Please use this as you see fit, freely, as I am glad to post a working class that will create a Color Picker for anyone using C# and SFML.net- but also providing the logic for anyone who wishes to create a Color Picker for C# only.

The solution is simple: http://richnewman.wordpress.com/about/code-listings-and-diagrams/hslcolor-class/

1) Add the HSLColor class from the above link.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using SFML;
using SFML.Graphics;
using SFML.Window;

namespace Source
{
class ColorWheel : IGameObject
{
    Image image = new Image(240, 220);
    HSLColor hslColor = new HSLColor();
    System.Drawing.Color systemColor = new System.Drawing.Color();
    Color pixelcolor = new Color();
    public Sprite sprite = new Sprite();

    public ColorWheel()
    {
        pixelcolor = Color.Red;
        hslColor.SetRGB(pixelcolor.R, pixelcolor.G, pixelcolor.B);

        for (int y = 0; y < 220; y++)
        {
            //pixelcolor = Color.Red;
            //hslColor.SetRGB(pixelcolor.R, pixelcolor.G, pixelcolor.B);
            hslColor.Hue = 0;
                for (int x = 0; x < 240; x++)
                {
                    systemColor = hslColor;
                    pixelcolor.R = systemColor.R;
                    pixelcolor.G = systemColor.G;
                    pixelcolor.B = systemColor.B;
                    image.SetPixel((uint)x, (uint)y, pixelcolor);
                    hslColor.Hue += 1;
                }
                hslColor.Saturation -= (y * 0.01);

        }

        Texture texture = new Texture(image);
        sprite.Texture = texture;
    }


    public void Update(double dt)
    {
    }

    public void Render(RenderWindow rWindow)
    {
        rWindow.Draw(sprite);
    }

}
}

And here is the resulting image, which you are freely allowed to use for anything you want. You do not even need to use the ColorWheel class, but simply use this image and get the pixel at the mouse position to dye your gameobjects as you see fit.

enter image description here

To change the brightness (which would be on the slider) use hslColor.Luminosity in the equation, and it would probably be best to use the ColorWheel class instead of the image, unless you feel confident adjusting the image brightness based on a double integer attached to the slider. A good Color Picker GUI which includes both the image, mouse cursor, and slider with just a few lines of code would be TGUI. Obviously you would want to replace the default image with the ColorWheel class if you were using it, or the ColorWheel image I just provided.

All free to be used by anyone as they see fit.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.