The HSV-RGB
Transform Pair
The following routines are written in C (with C++ comments). See the closely related
HWB-RGB Transform
Pair.
#define RETURN_HSV(h, s, v) {HSV.H = h; HSV.S = s; HSV.V = v; return HSV;}
#define RETURN_RGB(r, g, b) {RGB.R = r; RGB.G = g; RGB.B = b; return RGB;}
#define UNDEFINED -1
// Theoretically, hue 0 (pure red) is identical to hue 6 in these transforms. Pure
// red always maps to 6 in this implementation. Therefore UNDEFINED can be
// defined as 0 in situations where only unsigned numbers are desired.
typedef struct {float R, G, B;} RGBType;
typedef struct {float H, S, V;} HSVType;
HSVType
RGB_to_HSV( RGBType RGB ) {
// RGB are each on [0, 1]. S and V are returned on [0, 1] and H is | |
// returned on [0, 6]. Exception: H is returned UNDEFINED if S==0. | |
float R = RGB.R, G = RGB.G, B = RGB.B, v, x, f; | |
int i; | |
HSVType HSV; | |
x = min(R, G, B); | |
v = max(R, G, B); | |
if(v == x) RETURN_HSV(UNDEFINED, 0, v); | |
f = (R == x) ? G - B : ((G == x) ? B - R : R - G); | |
i = (R == x) ? 3 : ((G == x) ? 5 : 1); | |
RETURN_HSV(i - f /(v - x), (v - x)/v, v); |
}
Note: The algorithm above for hue is essentially
H = (R==x)? 3+(g-b) : (G==x)? 5+(b-r) : 1+(r-g),
where r = (v-R)/(v-x) etc. Mr Yu Chun Fang of the Chinese Academy of Sciences noticed, in an email to me of 10 Nov 2000, that this is the same as
H = (R==v)? 0+(b-g) : (G==v)? 2+(r-b) : 4+(g-r).
He had seen my algorithm published in an unnamed Chinese book in this form. The forms are, indeed, equivalent.
Note added 8 July 2010: Richard C. Zulch points out that the Fang-reported version is not equivalent in one fine point: For pure red, my version gives H=6.0, but the other version gives H=0.0. Therefore the trick of using 0 for UNDEFINED does not work in that version.
Note added 28 July 2010: Daniel Seres generalizes the versions that lead to hue on [0, 6) rather than on (0, 6] as my original version has it. I quote him directly:
Replace lines:
f = (R == x) ? G - B : ((G == x) ? B - R : R - G); i = (R == x) ? 3 : ((G == x) ? 5 : 1); with lines:
f = (R == x) ? G - B : ((B == x) ? R - G : B - R); i = (R == x) ? 3 : ((B == x) ? 1 : 5);
Alternatively, you can also put lines:
i = (B == x) ? R - G : ((R == x) ? G - B : B - R); i = (B == x) ? 1 : ((R == x) ? 3 : 5); or even:
i = (B == x) ? R - G : ((G == x) ? B - R : G - B); i = (B == x) ? 1 : ((G == x) ? 5 : 3); the only criterion is:
in each second line, condition with a value of 1 must precede condition with a value of 5.
Practically, in the old version of the algorithm, which ends up calculating hue as
5 - (B - R) / (v - x), hue gets a value of 5 - (-1) = 6, in the "fixed" version of the algorithm, which ends up calculating hue as 1 - (R - G) / (v - x), hue becomes 1 - 1 = 0.
Thanks to all the contributors above. I think you get the idea on the variations that can lead to hue on [0, 6) or on (0, 6], the latter allowing you to use the UNDEFINED is 0 trick.
RGBType
HSV_to_RGB( HSVType HSV ) {
// H is given on [0, 6] or UNDEFINED. S and V are given on [0, 1]. | ||
// RGB are each returned on [0, 1]. | ||
float h = HSV.H, s = HSV.S, v = HSV.V, m, n, f; | ||
int i; | ||
RGBType RGB; | ||
if (h == UNDEFINED) RETURN_RGB(v, v, v); | ||
i = floor(h); | ||
f = h - i; | ||
if ( !(i&1) ) f = 1 - f; // if i is even | ||
m = v * (1 - s); | ||
n = v * (1 - s * f); | ||
switch (i) { | ||
case 6: | ||
case 0: RETURN_RGB(v, n, m); | ||
case 1: RETURN_RGB(n, v, m); | ||
case 2: RETURN_RGB(m, v, n) | ||
case 3: RETURN_RGB(m, n, v); | ||
case 4: RETURN_RGB(n, m, v); | ||
case 5: RETURN_RGB(v, m, n); | ||
} |
}