RGB to HSV & HSV to RGB Color Conversions

Standard
Colors-Wallpaper-3

Rainbows seem to be pretty popular lately, so I thought I’d share this.

While working on a graphics assignment for school, I decided I wanted my lights to cycle through the full spectrum of colors. The problem was doing so while using RGB, since the loop can get a bit messy to achieve the effect. That’s when I decided to make an RGB to HSV converter, as well as an HSV to RGB converter. For those of you who don’t know, RGB and HSV are just two different ways of representing a color as a collection of three numbers. RGB stands for Red Channel, Green Channel and Blue Channel, while HSV stands for Hue, Saturation and Brightness (V is used instead of B to avoid confusion with Blue). Below is my implementation, which is the classical one with a slight performance increase trick that I found. I will provide all the links to my resources below.

First, I’d like to show how much easier it is to walk the color spectrum using HSV instead of RGB with a capture from my ImGui widget that I used in my assignment:

HSV3
RGB3

As you can see, using HSV you can simply increase the Hue value by an amount to easily advance through the color spectrum. With RGB, the loop would need to have a bunch of conditionals to determine which slider (R, G or B) would need to be increased/decreased next. Now on to the code.

Here is the Color structure that I’m using, which will make it clearer when looking at the conversion code:

struct Color
{
  union
  {
    struct // RGBA Form
    {
      float r; // Color's Red Channel
      float g; // Color's Green Channel
      float b; // Color's Blue Channel
      float a; // Color's Alpha Channel
    };

    struct // HSVA Form
    {
      float h; // Color's Hue
      float s; // Color's Saturation
      float v; // Color's Brightness
      float a; // Color's Alpha
    };

    float components[4]; // r: components[0], g: components[1], etc.
  };
};

I’m using a union to preserve space while also allowing me to intuitively access the value I’m looking for, instead of assuming that R is H, G is S, and so on.

The first conversion I made was from RGB to HSV. I started using the code from this website, which is written in Java, but it was pretty straightforward to translate to C++. Later, I found a faster way of calculating it on this other website, but I think that algorithm is not as clear to understand for learning purposes, so I just took one of his optimizations for preventing division by zero and left the rest of the code the same. Feel free to read that second article and implement that faster version of the algorithm if performance is an issue for your project.

Remember that these are member functions on the Color struct defined above, so any reference to r, g, b, a, h, s or v are simply the internal variables of the “this” object. Here’s what the code looks like:

// Converts a color from RGBA to HSVA
Color Color::ToHSVA(void) const
{
  Color hsvaColor;

  float min = std::min(r, std::min(g, b));
  float max = std::max(r, std::max(g, b));
  float delta = max - min;

  // Brightness
  hsvaColor.v = max;
  // Saturation
  hsvaColor.s = delta / (max + 1e-20f);
  // Alpha stays the same
  hsvaColor.a = a;

  // Hue
  if (r == max)
    hsvaColor.h = (g - b) / (delta + 1e-20f);     // Between yellow and magenta
  else if (g == max)
    hsvaColor.h = 2 + (b - r) / (delta + 1e-20f); // between cyan and yellow
  else
    hsvaColor.h = 4 + (r - g) / (delta + 1e-20f); // between magenta and cyan

  hsvaColor.h *= 60; // To Degrees
  // Correction if under 360
  if (hsvaColor.h < 0.0f)
    hsvaColor.h += 360.0f;

  return hsvaColor;
}

As you can see, I’m adding 1e-20f to my divisors in order to prevent division by zero and saving myself of having an extra if check for that case. I also treat my Hue as degrees, since it is what ImGui was expecting and I wanted to keep that format.

To convert a color back to RGB from HSV, there is also a classical algorithm that can be used and found on the first website that I linked. Here’s what my C++ implementation looks like:

// Converts a color from HSVA to RGBA
Color Color::ToRGBA(void) const
{
  Color rgbaColor;

  // Achromatic (gray)
  if (s == 0)
  {
    rgbaColor.r = rgbaColor.g = rgbaColor.b = v / 255.0f;
    rgbaColor.a = a;
    return rgbaColor;
  }

  // Conversion values
  float tempH = h;
  tempH /= 60.0f;
  int i = (int)std::floor(tempH);
  float f = tempH - i;
  float p = v * (1 - s);
  float q = v * (1 - s * f);
  float t = v * (1 - s * (1 - f));

  // There are 6 cases, one for every 60 degrees
  switch (i)
  {
    case 0:
      rgbaColor.r = v;
      rgbaColor.g = t;
      rgbaColor.b = p;
      break;

    case 1:
      rgbaColor.r = q;
      rgbaColor.g = v;
      rgbaColor.b = p;
      break;

    case 2:
      rgbaColor.r = p;
      rgbaColor.g = v;
      rgbaColor.b = t;
      break;

    case 3:
      rgbaColor.r = p;
      rgbaColor.g = q;
      rgbaColor.b = v;
      break;

    case 4:
      rgbaColor.r = t;
      rgbaColor.g = p;
      rgbaColor.b = v;
      break;

    // Case 5
    default:
      rgbaColor.r = v;
      rgbaColor.g = p;
      rgbaColor.b = q;
      break;
  }

  // Alpha value stays the same
  rgbaColor.a = a;

  return rgbaColor;
}

Phew, that wasn’t so bad now, was it? It was a fun little thing to program and I managed to achieve exactly what I wanted in my program. Here’s a GIF of my output.

Bunny2

I hope you enjoyed this article!

Advertisement

4 thoughts on “RGB to HSV & HSV to RGB Color Conversions

  1. this is pretty neat, personally I would’ve probably constructed an rgb wheel and used circular traversion of it to achieve the same effect, it’s more computationally expensive at the cost of using less memory.

    • AleHitti

      Wait, which one uses less memory? I would assume the rgb wheel uses up memory, but traversing it is faster than converting rgb to/from hsv, right? It’s just that your comment is worded weirdly šŸ™‚

      • well, if you treat the rgb value as a wheel (takes some hacking) then you can save the extra variables needed to convert to hsv at the cost of cpu time (do note, this is typically not useful in most languages), the hsv conversion is typicall a way more sane option (oooh, or just reading pixels from a texture, if you only need so many colors)
        Regardless, it was an interesting post.

        Specifically the reason the rgb wheel takes less memory is because it has three extreme points and everything in between is interpolated (hence only three memory values are needed) but you’d need to deal with the math of traversing a circle to be able to interpolate the value you want.

Share your thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s