Cannot set hue to 1.0f

Hello everyone!

I am trying to set the hue values of a color between 0.0f and 1.0f.
However, when 1.0f is reached, it gets set back to 0.0f.
Is a color’s hue clamped between 0.0f and 1.0f, not including 1.0f?
How would I include 1.0f?

This is a demo code that shows the issue:

Color32 color1 = new Color32(255,0,0,255);
float color1_hue = 0.0f;
float color1_saturation = 0.0f;
float color1_value = 0.0f;

void Start () {

    Color.RGBToHSV(color1,out color1_hue,out color1_saturation,out color1_value);
    Debug.Log("color1_hue: " + color1_hue.ToString());

    color1_hue = 0.25f;
    color1 = Color.HSVToRGB(color1_hue,color1_saturation,color1_value);
    Color.RGBToHSV(color1,out color1_hue,out color1_saturation,out color1_value);
    Debug.Log("color1_hue: " + color1_hue.ToString());

    color1_hue = 0.5f;
    color1 = Color.HSVToRGB(color1_hue,color1_saturation,color1_value);
    Color.RGBToHSV(color1,out color1_hue,out color1_saturation,out color1_value);
    Debug.Log("color1_hue: " + color1_hue.ToString());

    color1_hue = 0.75f;
    color1 = Color.HSVToRGB(color1_hue,color1_saturation,color1_value);
    Color.RGBToHSV(color1,out color1_hue,out color1_saturation,out color1_value);
    Debug.Log("color1_hue: " + color1_hue.ToString());

    color1_hue = 1.0f;
    color1 = Color.HSVToRGB(color1_hue,color1_saturation,color1_value);
    Color.RGBToHSV(color1,out color1_hue,out color1_saturation,out color1_value);
    Debug.Log("color1_hue: " + color1_hue.ToString());

    color1_hue = 1.01f;
    color1 = Color.HSVToRGB(color1_hue,color1_saturation,color1_value);
    Color.RGBToHSV(color1,out color1_hue,out color1_saturation,out color1_value);
    Debug.Log("color1_hue: " + color1_hue.ToString());

}

When running this code, I get the following messages in the Console:
“color1_hue: 0”
“color1_hue: 0.2503268”
“color1_hue: 0.5”
“color1_hue: 0.7496732”
“color1_hue: 0”
“color1_hue: 0.009803922”

Best wishes,
Shu

First take a look at HSV color space:

Note that it’s a cylinder, where Hue is the radial portion of it.

Meaning Hue is actually an angle… but instead of 0 to 360 degrees, we’re just saying 0% to 100% (0->1).

Now lets expand out your code some:

    Color32 color1 = new Color32(255, 0, 0, 255);
    float color1_hue = 0.0f;
    float color1_saturation = 0.0f;
    float color1_value = 0.0f;

    void Start()
    {

        Color.RGBToHSV(color1, out color1_hue, out color1_saturation, out color1_value);
        Debug.Log(color1.ToString() + " --:-- " + color1_hue.ToString("0.00") + ", " + color1_saturation.ToString("0.00") + ", " + color1_value.ToString("0.00"));

        color1_hue = 0.25f;
        color1 = Color.HSVToRGB(color1_hue, color1_saturation, color1_value);
        Color.RGBToHSV(color1, out color1_hue, out color1_saturation, out color1_value);
        Debug.Log(color1.ToString() + " --:-- " + color1_hue.ToString("0.00") + ", " + color1_saturation.ToString("0.00") + ", " + color1_value.ToString("0.00"));

        color1_hue = 0.5f;
        color1 = Color.HSVToRGB(color1_hue, color1_saturation, color1_value);
        Color.RGBToHSV(color1, out color1_hue, out color1_saturation, out color1_value);
        Debug.Log(color1.ToString() + " --:-- " + color1_hue.ToString("0.00") + ", " + color1_saturation.ToString("0.00") + ", " + color1_value.ToString("0.00"));

        color1_hue = 0.75f;
        color1 = Color.HSVToRGB(color1_hue, color1_saturation, color1_value);
        Color.RGBToHSV(color1, out color1_hue, out color1_saturation, out color1_value);
        Debug.Log(color1.ToString() + " --:-- " + color1_hue.ToString("0.00") + ", " + color1_saturation.ToString("0.00") + ", " + color1_value.ToString("0.00"));

        color1_hue = 1.0f;
        color1 = Color.HSVToRGB(color1_hue, color1_saturation, color1_value);
        Color.RGBToHSV(color1, out color1_hue, out color1_saturation, out color1_value);
        Debug.Log(color1.ToString() + " --:-- " + color1_hue.ToString("0.00") + ", " + color1_saturation.ToString("0.00") + ", " + color1_value.ToString("0.00"));

        color1_hue = 1.01f;
        color1 = Color.HSVToRGB(color1_hue, color1_saturation, color1_value);
        Color.RGBToHSV(color1, out color1_hue, out color1_saturation, out color1_value);
        Debug.Log(color1.ToString() + " --:-- " + color1_hue.ToString("0.00") + ", " + color1_saturation.ToString("0.00") + ", " + color1_value.ToString("0.00"));

    }

Print out all the information.

You’ll get:

RGBA(255, 0, 0, 255) --:-- 0.00, 1.00, 1.00
RGBA(127, 255, 0, 255) --:-- 0.25, 1.00, 1.00
RGBA(0, 255, 255, 255) --:-- 0.50, 1.00, 1.00
RGBA(127, 0, 255, 255) --:-- 0.75, 1.00, 1.00
RGBA(255, 0, 0, 255) --:-- 0.00, 1.00, 1.00
RGBA(255, 15, 0, 255) --:-- 0.01, 1.00, 1.00

Note that when you set the hue to 1, and then call HSVtoRGB, you get red back.

That’s the colour you started with.

When you pass red into RGBToHSV, you get <0,1,1> for the values.

Why?

Because red is at hue value 0.

AND

red is at hue value 1.

Just like how 0 and 360 degrees are equal.

They’re the SAME spot on the radial/circle.

Unity needs to pick one or the other when it returns the HSV converted from RGB, and it picks to use 0.

If you fed in 1,1,1 to HSVToRGB you get the same result as if you passed in 0,1,1. And actually that’s exactly what you do (you did pass in 1,1,1).

So yeah… this is expected behaviour.

4 Likes

That’s actually interesting to see. I haven’t messed with hue and those other values myself. But I noticed the hue slider went up to 359, so it makes sense now.

@lordofduct
Thanks for your detailed answer!
The thing is, I am currently working on a HSV color picker that uses the hue value for lerping the position of a UI element that shows the currently selected hue on a slider. While it worked without issues for saturation and value, picking the hue value of 1 showed an undesired effect. It would jump back to 0 and show on the original position rather than on the slider’s end position.
I got rid of this problem with limiting the hue value to 0.9999f. This still results in a full red hue without any UI problems.

It’s weird, though, that it automatically jumps back to 0.0f, isn’t it?
Even with your explanation, why would Unity convert the value 1 back to 0?
Since they are the same, 1 could just be accepted as well?

Why would it be weird that it jumps back to 0?

You’re calling a method, it has to return something, it is unaware of the previous time you called the method.

If I asked you to measure the angle of something, and 0 and 360 are BOTH valid responses. And lets presume you have no short term memory. Why should I assume that you should say 360 just because the last time I asked you it was 359?

Now… of course there are ways to work around this.

What does your code look like? Maybe we can show you how to tween/lerp the values to that it makes up for this issue.

1 Like

@lordofduct
I see! From my understanding, when I set a value, it must be stored somewhere.
And when I read that value later on, it is just the one that has been stored.
However, Unity probably doesn’t store the values just like that?

Because if Unity doesn’t change the values that I write, shouldn’t I get the same values back when I read?
I guess it’s just because floats >= 1.0f are not valid values for hue? So if float >= 1.0f, Unity subtracts the whole number of the input value and stores the remaining float?

I already got a solution for the scenario that I have been working on: I just set the hue value to 0.9999f instead of 1.0f.
Here is a sketch of what I have been trying to do:

float huePercentage = 0.0f;
RectTransform uiHueMarkerTransform;
float uiHueMarkerLeft = -200.0f;
float uiHueMarkerRight = 50.0f;

while (settingHue) {

    // [...] (setting huePercentage, which is just the hue value from RGBtoHSV)

    uiHueMarkerTransform.anchoredPosition.x =
    Mathf.Lerp(uiHueMarkerLeft,uiHueMarkerRight,huePercentage);

}

The fix was to do something like this:if (huePercentage >= 1.0f) {huePercentage = 0.9999f;}