Algorithm to convert RGB to HSV and HSV to RGB in range 0-255 for both
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; }
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;}
Note that this algorithm uses 0-255
as it's range (not 0-360
) as that was requested by the author of this question.
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 }