Home » C++ » Algorithm to convert RGB to HSV and HSV to RGB in range 0-255 for both

# Algorithm to convert RGB to HSV and HSV to RGB in range 0-255 for both

Questions:

I am looking for color space converter from RGB to HSV, specifically for the range 0 to 255 for both color spaces.

I’ve used these for a long time – no idea where they came from at this point… Note that the inputs and outputs, except for the angle in degrees, are in the range of 0 to 1.0.

NOTE: this code does no real sanity checking on inputs. Proceed with caution!

``````typedef struct {
double r;       // a fraction between 0 and 1
double g;       // a fraction between 0 and 1
double b;       // a fraction between 0 and 1
} rgb;

typedef struct {
double h;       // angle in degrees
double s;       // a fraction between 0 and 1
double v;       // a fraction between 0 and 1
} hsv;

static hsv   rgb2hsv(rgb in);
static rgb   hsv2rgb(hsv in);

hsv rgb2hsv(rgb in)
{
hsv         out;
double      min, max, delta;

min = in.r < in.g ? in.r : in.g;
min = min  < in.b ? min  : in.b;

max = in.r > in.g ? in.r : in.g;
max = max  > in.b ? max  : in.b;

out.v = max;                                // v
delta = max - min;
if (delta < 0.00001)
{
out.s = 0;
out.h = 0; // undefined, maybe nan?
return out;
}
if( max > 0.0 ) { // NOTE: if Max is == 0, this divide would cause a crash
out.s = (delta / max);                  // s
} else {
// if max is 0, then r = g = b = 0
// s = 0, h is undefined
out.s = 0.0;
out.h = NAN;                            // its now undefined
return out;
}
if( in.r >= max )                           // > is bogus, just keeps compilor happy
out.h = ( in.g - in.b ) / delta;        // between yellow & magenta
else
if( in.g >= max )
out.h = 2.0 + ( in.b - in.r ) / delta;  // between cyan & yellow
else
out.h = 4.0 + ( in.r - in.g ) / delta;  // between magenta & cyan

out.h *= 60.0;                              // degrees

if( out.h < 0.0 )
out.h += 360.0;

return out;
}

rgb hsv2rgb(hsv in)
{
double      hh, p, q, t, ff;
long        i;
rgb         out;

if(in.s <= 0.0) {       // < is bogus, just shuts up warnings
out.r = in.v;
out.g = in.v;
out.b = in.v;
return out;
}
hh = in.h;
if(hh >= 360.0) hh = 0.0;
hh /= 60.0;
i = (long)hh;
ff = hh - i;
p = in.v * (1.0 - in.s);
q = in.v * (1.0 - (in.s * ff));
t = in.v * (1.0 - (in.s * (1.0 - ff)));

switch(i) {
case 0:
out.r = in.v;
out.g = t;
out.b = p;
break;
case 1:
out.r = q;
out.g = in.v;
out.b = p;
break;
case 2:
out.r = p;
out.g = in.v;
out.b = t;
break;

case 3:
out.r = p;
out.g = q;
out.b = in.v;
break;
case 4:
out.r = t;
out.g = p;
out.b = in.v;
break;
case 5:
default:
out.r = in.v;
out.g = p;
out.b = q;
break;
}
return out;
}
``````

Questions:

You can also try this code without floats (faster but less accurate):

``````typedef struct RgbColor
{
unsigned char r;
unsigned char g;
unsigned char b;
} RgbColor;

typedef struct HsvColor
{
unsigned char h;
unsigned char s;
unsigned char v;
} HsvColor;

RgbColor HsvToRgb(HsvColor hsv)
{
RgbColor rgb;
unsigned char region, remainder, p, q, t;

if (hsv.s == 0)
{
rgb.r = hsv.v;
rgb.g = hsv.v;
rgb.b = hsv.v;
return rgb;
}

region = hsv.h / 43;
remainder = (hsv.h - (region * 43)) * 6;

p = (hsv.v * (255 - hsv.s)) >> 8;
q = (hsv.v * (255 - ((hsv.s * remainder) >> 8))) >> 8;
t = (hsv.v * (255 - ((hsv.s * (255 - remainder)) >> 8))) >> 8;

switch (region)
{
case 0:
rgb.r = hsv.v; rgb.g = t; rgb.b = p;
break;
case 1:
rgb.r = q; rgb.g = hsv.v; rgb.b = p;
break;
case 2:
rgb.r = p; rgb.g = hsv.v; rgb.b = t;
break;
case 3:
rgb.r = p; rgb.g = q; rgb.b = hsv.v;
break;
case 4:
rgb.r = t; rgb.g = p; rgb.b = hsv.v;
break;
default:
rgb.r = hsv.v; rgb.g = p; rgb.b = q;
break;
}

return rgb;
}

HsvColor RgbToHsv(RgbColor rgb)
{
HsvColor hsv;
unsigned char rgbMin, rgbMax;

rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b);
rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b);

hsv.v = rgbMax;
if (hsv.v == 0)
{
hsv.h = 0;
hsv.s = 0;
return hsv;
}

hsv.s = 255 * long(rgbMax - rgbMin) / hsv.v;
if (hsv.s == 0)
{
hsv.h = 0;
return hsv;
}

if (rgbMax == rgb.r)
hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin);
else if (rgbMax == rgb.g)
hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin);
else
hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin);

return hsv;
}
``````

Questions:

I wrote this in HLSL for our rendering engine, it has no conditions in it:

``````    float3  HSV2RGB( float3 _HSV )
{
_HSV.x = fmod( 100.0 + _HSV.x, 1.0 );                                       // Ensure [0,1[

float   HueSlice = 6.0 * _HSV.x;                                            // In [0,6[
float   HueSliceInteger = floor( HueSlice );
float   HueSliceInterpolant = HueSlice - HueSliceInteger;                   // In [0,1[ for each hue slice

float3  TempRGB = float3(   _HSV.z * (1.0 - _HSV.y),
_HSV.z * (1.0 - _HSV.y * HueSliceInterpolant),
_HSV.z * (1.0 - _HSV.y * (1.0 - HueSliceInterpolant)) );

// The idea here to avoid conditions is to notice that the conversion code can be rewritten:
//    if      ( var_i == 0 ) { R = V         ; G = TempRGB.z ; B = TempRGB.x }
//    else if ( var_i == 2 ) { R = TempRGB.x ; G = V         ; B = TempRGB.z }
//    else if ( var_i == 4 ) { R = TempRGB.z ; G = TempRGB.x ; B = V     }
//
//    else if ( var_i == 1 ) { R = TempRGB.y ; G = V         ; B = TempRGB.x }
//    else if ( var_i == 3 ) { R = TempRGB.x ; G = TempRGB.y ; B = V     }
//    else if ( var_i == 5 ) { R = V         ; G = TempRGB.x ; B = TempRGB.y }
//
// This shows several things:
//  . A separation between even and odd slices
//  . If slices (0,2,4) and (1,3,5) can be rewritten as basically being slices (0,1,2) then
//      the operation simply amounts to performing a "rotate right" on the RGB components
//  . The base value to rotate is either (V, B, R) for even slices or (G, V, R) for odd slices
//
float   IsOddSlice = fmod( HueSliceInteger, 2.0 );                          // 0 if even (slices 0, 2, 4), 1 if odd (slices 1, 3, 5)
float   ThreeSliceSelector = 0.5 * (HueSliceInteger - IsOddSlice);          // (0, 1, 2) corresponding to slices (0, 2, 4) and (1, 3, 5)

float3  ScrollingRGBForEvenSlices = float3( _HSV.z, TempRGB.zx );           // (V, Temp Blue, Temp Red) for even slices (0, 2, 4)
float3  ScrollingRGBForOddSlices = float3( TempRGB.y, _HSV.z, TempRGB.x );  // (Temp Green, V, Temp Red) for odd slices (1, 3, 5)
float3  ScrollingRGB = lerp( ScrollingRGBForEvenSlices, ScrollingRGBForOddSlices, IsOddSlice );

float   IsNotFirstSlice = saturate( ThreeSliceSelector );                   // 1 if NOT the first slice (true for slices 1 and 2)
float   IsNotSecondSlice = saturate( ThreeSliceSelector-1.0 );              // 1 if NOT the first or second slice (true only for slice 2)

return  lerp( ScrollingRGB.xyz, lerp( ScrollingRGB.zxy, ScrollingRGB.yzx, IsNotSecondSlice ), IsNotFirstSlice );    // Make the RGB rotate right depending on final slice index
}
``````

Questions:

Here’s a C implementation based on Agoston’s Computer Graphics and Geometric Modeling: Implementation and Algorithms p. 304, with H ∈ [0, 360] and S,V ∈ [0, 1].

``````#include <math.h>

typedef struct {
double r;       // ∈ [0, 1]
double g;       // ∈ [0, 1]
double b;       // ∈ [0, 1]
} rgb;

typedef struct {
double h;       // ∈ [0, 360]
double s;       // ∈ [0, 1]
double v;       // ∈ [0, 1]
} hsv;

rgb hsv2rgb(hsv HSV)
{
rgb RGB;
double H = HSV.h, S = HSV.s, V = HSV.v,
P, Q, T,
fract;

(H == 360.)?(H = 0.):(H /= 60.);
fract = H - floor(H);

P = V*(1. - S);
Q = V*(1. - S*fract);
T = V*(1. - S*(1. - fract));

if      (0. <= H && H < 1.)
RGB = (rgb){.r = V, .g = T, .b = P};
else if (1. <= H && H < 2.)
RGB = (rgb){.r = Q, .g = V, .b = P};
else if (2. <= H && H < 3.)
RGB = (rgb){.r = P, .g = V, .b = T};
else if (3. <= H && H < 4.)
RGB = (rgb){.r = P, .g = Q, .b = V};
else if (4. <= H && H < 5.)
RGB = (rgb){.r = T, .g = P, .b = V};
else if (5. <= H && H < 6.)
RGB = (rgb){.r = V, .g = P, .b = Q};
else
RGB = (rgb){.r = 0., .g = 0., .b = 0.};

return RGB;
}
``````

Questions:

this should be on here:
it works anyway. And it looks good compared to the above ones.

``````        float3 Hue(float H)
{
half R = abs(H * 6 - 3) - 1;
half G = 2 - abs(H * 6 - 2);
half B = 2 - abs(H * 6 - 4);
return saturate(half3(R,G,B));
}

half4 HSVtoRGB(in half3 HSV)
{
return half4(((Hue(HSV.x) - 1) * HSV.y + 1) * HSV.z,1);
}
``````

float3 is 16 bit precision vector3 data type, i.e. float3 hue() is returns a data type (x,y,z) e.g. (r,g,b), half is same with half precision, 8bit, a float4 is (r,g,b,a) 4 values.

Questions:

This isn’t C, but it’s certainly does work. All the other methods I see here work by casing everything into parts of a hexagon, and approximating “angles” from that. By instead starting with a different equation using cosines, and solving for h s and v, you get a lot nicer relationship between hsv and rgb, and tweening becomes smoother (at the cost of it being way slower).

Assume everything is floating point. If r g and b go from 0 to 1, h goes from 0 to 2pi, v goes from 0 to 4/3, and s goes from 0 to 2/3.

The following code is written in Lua. It’s easily translatable into anything else.

``````local hsv do
hsv         ={}
local atan2 =math.atan2
local cos   =math.cos
local sin   =math.sin

function hsv.fromrgb(r,b,g)
local c=r+g+b
if c<1e-4 then
return 0,2/3,0
else
local p=2*(b*b+g*g+r*r-g*r-b*g-b*r)^0.5
local h=atan2(b-g,(2*r-b-g)/3^0.5)
local s=p/(c+p)
local v=(c+p)/3
return h,s,v
end
end

function hsv.torgb(h,s,v)
local r=v*(1+s*(cos(h)-1))
local g=v*(1+s*(cos(h-2.09439)-1))
local b=v*(1+s*(cos(h+2.09439)-1))
return r,g,b
end

function hsv.tween(h0,s0,v0,h1,s1,v1,t)
local dh=(h1-h0+3.14159)%6.28318-3.14159
local h=h0+t*dh
local s=s0+t*(s1-s0)
local v=v0+t*(v1-v0)
return h,s,v
end
end
``````

Questions:

@fins’s answer has an overflow issue on Arduio as you turn the saturation down. Here it is with some values converted to int to prevent that.

``````typedef struct RgbColor
{
unsigned char r;
unsigned char g;
unsigned char b;
} RgbColor;

typedef struct HsvColor
{
unsigned char h;
unsigned char s;
unsigned char v;
} HsvColor;

RgbColor HsvToRgb(HsvColor hsv)
{
RgbColor rgb;
unsigned char region, p, q, t;
unsigned int h, s, v, remainder;

if (hsv.s == 0)
{
rgb.r = hsv.v;
rgb.g = hsv.v;
rgb.b = hsv.v;
return rgb;
}

// converting to 16 bit to prevent overflow
h = hsv.h;
s = hsv.s;
v = hsv.v;

region = h / 43;
remainder = (h - (region * 43)) * 6;

p = (v * (255 - s)) >> 8;
q = (v * (255 - ((s * remainder) >> 8))) >> 8;
t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8;

switch (region)
{
case 0:
rgb.r = v;
rgb.g = t;
rgb.b = p;
break;
case 1:
rgb.r = q;
rgb.g = v;
rgb.b = p;
break;
case 2:
rgb.r = p;
rgb.g = v;
rgb.b = t;
break;
case 3:
rgb.r = p;
rgb.g = q;
rgb.b = v;
break;
case 4:
rgb.r = t;
rgb.g = p;
rgb.b = v;
break;
default:
rgb.r = v;
rgb.g = p;
rgb.b = q;
break;
}

return rgb;
}

HsvColor RgbToHsv(RgbColor rgb)
{
HsvColor hsv;
unsigned char rgbMin, rgbMax;

rgbMin = rgb.r < rgb.g ? (rgb.r < rgb.b ? rgb.r : rgb.b) : (rgb.g < rgb.b ? rgb.g : rgb.b);
rgbMax = rgb.r > rgb.g ? (rgb.r > rgb.b ? rgb.r : rgb.b) : (rgb.g > rgb.b ? rgb.g : rgb.b);

hsv.v = rgbMax;
if (hsv.v == 0)
{
hsv.h = 0;
hsv.s = 0;
return hsv;
}

hsv.s = 255 * ((long)(rgbMax - rgbMin)) / hsv.v;
if (hsv.s == 0)
{
hsv.h = 0;
return hsv;
}

if (rgbMax == rgb.r)
hsv.h = 0 + 43 * (rgb.g - rgb.b) / (rgbMax - rgbMin);
else if (rgbMax == rgb.g)
hsv.h = 85 + 43 * (rgb.b - rgb.r) / (rgbMax - rgbMin);
else
hsv.h = 171 + 43 * (rgb.r - rgb.g) / (rgbMax - rgbMin);

return hsv;
}
``````

Questions:

``````vec3 HSV2RGB( vec3 hsv )
{
hsv.x = mod( 100.0 + hsv.x, 1.0 ); // Ensure [0,1[
float   HueSlice = 6.0 * hsv.x; // In [0,6[
float   HueSliceInteger = floor( HueSlice );
float   HueSliceInterpolant = HueSlice - HueSliceInteger; // In [0,1[ for each hue slice
vec3  TempRGB = vec3(   hsv.z * (1.0 - hsv.y), hsv.z * (1.0 - hsv.y * HueSliceInterpolant), hsv.z * (1.0 - hsv.y * (1.0 - HueSliceInterpolant)) );
float   IsOddSlice = mod( HueSliceInteger, 2.0 ); // 0 if even (slices 0, 2, 4), 1 if odd (slices 1, 3, 5)
float   ThreeSliceSelector = 0.5 * (HueSliceInteger - IsOddSlice); // (0, 1, 2) corresponding to slices (0, 2, 4) and (1, 3, 5)
vec3  ScrollingRGBForEvenSlices = vec3( hsv.z, TempRGB.zx );           // (V, Temp Blue, Temp Red) for even slices (0, 2, 4)
vec3  ScrollingRGBForOddSlices = vec3( TempRGB.y, hsv.z, TempRGB.x );  // (Temp Green, V, Temp Red) for odd slices (1, 3, 5)
vec3  ScrollingRGB = mix( ScrollingRGBForEvenSlices, ScrollingRGBForOddSlices, IsOddSlice );
float   IsNotFirstSlice = clamp( ThreeSliceSelector, 0.0,1.0 );                   // 1 if NOT the first slice (true for slices 1 and 2)
float   IsNotSecondSlice = clamp( ThreeSliceSelector-1.0, 0.0,1. );              // 1 if NOT the first or second slice (true only for slice 2)
return  mix( ScrollingRGB.xyz, mix( ScrollingRGB.zxy, ScrollingRGB.yzx, IsNotSecondSlice ), IsNotFirstSlice );    // Make the RGB rotate right depending on final slice index
}
``````

Questions:

This link has formulas for what you want. Then it’s a matter of performance (numerical techniques) if you want it fast.

Questions:

Here’s one which i just wrote this morning based on pretty much the same math as above:

``````/* math adapted from: http://www.rapidtables.com/convert/color/rgb-to-hsl.htm
* reasonably optimized for speed, without going crazy */
void rgb_to_hsv (int r, int g, int b, float *r_h, float *r_s, float *r_v) {
float rp, gp, bp, cmax, cmin, delta, l;
int cmaxwhich, cminwhich;

rp = ((float) r) / 255;
gp = ((float) g) / 255;
bp = ((float) b) / 255;

//debug ("rgb=%d,%d,%d rgbprime=%f,%f,%f", r, g, b, rp, gp, bp);

cmax = rp;
cmaxwhich = 0; /* faster comparison afterwards */
if (gp > cmax) { cmax = gp; cmaxwhich = 1; }
if (bp > cmax) { cmax = bp; cmaxwhich = 2; }
cmin = rp;
cminwhich = 0;
if (gp < cmin) { cmin = gp; cminwhich = 1; }
if (bp < cmin) { cmin = bp; cminwhich = 2; }

//debug ("cmin=%f,cmax=%f", cmin, cmax);
delta = cmax - cmin;

/* HUE */
if (delta == 0) {
*r_h = 0;
} else {
switch (cmaxwhich) {
case 0: /* cmax == rp */
*r_h = HUE_ANGLE * (fmod ((gp - bp) / delta, 6));
break;

case 1: /* cmax == gp */
*r_h = HUE_ANGLE * (((bp - rp) / delta) + 2);
break;

case 2: /* cmax == bp */
*r_h = HUE_ANGLE * (((rp - gp) / delta) + 4);
break;
}
if (*r_h < 0)
*r_h += 360;
}

/* LIGHTNESS/VALUE */
//l = (cmax + cmin) / 2;
*r_v = cmax;

/* SATURATION */
/*if (delta == 0) {
*r_s = 0;
} else {
*r_s = delta / (1 - fabs (1 - (2 * (l - 1))));
}*/
if (cmax == 0) {
*r_s = 0;
} else {
*r_s = delta / cmax;
}
//debug ("rgb=%d,%d,%d ---> hsv=%f,%f,%f", r, g, b, *r_h, *r_s, *r_v);
}

void hsv_to_rgb (float h, float s, float v, int *r_r, int *r_g, int *r_b) {
if (h > 360)
h -= 360;
if (h < 0)
h += 360;
h = CLAMP (h, 0, 360);
s = CLAMP (s, 0, 1);
v = CLAMP (v, 0, 1);
float c = v * s;
float x = c * (1 - fabsf (fmod ((h / HUE_ANGLE), 2) - 1));
float m = v - c;
float rp, gp, bp;
int a = h / 60;

//debug ("h=%f, a=%d", h, a);

switch (a) {
case 0:
rp = c;
gp = x;
bp = 0;
break;

case 1:
rp = x;
gp = c;
bp = 0;
break;

case 2:
rp = 0;
gp = c;
bp = x;
break;

case 3:
rp = 0;
gp = x;
bp = c;
break;

case 4:
rp = x;
gp = 0;
bp = c;
break;

default: // case 5:
rp = c;
gp = 0;
bp = x;
break;
}

*r_r = (rp + m) * 255;
*r_g = (gp + m) * 255;
*r_b = (bp + m) * 255;

//debug ("hsv=%f,%f,%f, ---> rgb=%d,%d,%d", h, s, v, *r_r, *r_g, *r_b);
}
``````