Szukam konwertera przestrzeni kolorów z RGB na HSV, a konkretnie dla zakresu od 0 do 255 dla obu przestrzeni kolorów.
90
Używam ich od dawna - nie mam pojęcia, skąd się wzięły w tym momencie ... Zwróć uwagę, że wejścia i wyjścia, z wyjątkiem kąta w stopniach, mieszczą się w zakresie od 0 do 1,0.
UWAGA: ten kod nie sprawdza rzeczywistych danych wejściowych. Zachowaj ostrożność!
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;
}
>=
błędu kompilatora i jest to, żedouble == double
w większości kompilatorów jest on nieprawidłowy i niedozwolony. Arytmetyka zmiennoprzecinkowa i pamięć zmiennoprzecinkowa oznacza, że dwie wartości mogą być równe w przybliżeniu , ale nie równe przechowywanej wartości, mimo że formalnie są takie same.abs(double_a - double_b) <= epsilon
Zwykle powinieneś robić tam , gdzie epsilon jest jakąś wartością1e-4
.Możesz także wypróbować ten kod bez liczb zmiennoprzecinkowych (szybszy, ale mniej dokładny):
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; }
Zauważ, że ten algorytm używa
0-255
zakresu (nie0-360
) zgodnie z żądaniem autora tego pytania.źródło
Napisałem to w HLSL dla naszego silnika renderującego, nie ma w nim warunków:
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 }
źródło
Oto implementacja C oparta na grafice komputerowej i modelowaniu geometrycznym Agoston : implementacja i algorytmy s. 304, gdzie H ∈ [0, 360] i 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; }
źródło
to powinno być tutaj: to i tak działa. I wygląda dobrze w porównaniu z powyższymi.
kod hlsl
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 to typ danych vector3 z 16-bitową precyzją, tj. float3 hue () zwraca typ danych (x, y, z) np. (r, g, b), połowa jest taka sama z połowiczną precyzją, 8bit, a float4 to (r, g, b, a) 4 wartości.
źródło
half
,half4
,half3
,float3
, et cetera.@fins odpowiada na problem przepełnienia w Arduio podczas zmniejszania nasycenia. Tutaj jest z niektórymi wartościami zamienionymi na int, aby temu zapobiec.
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; }
źródło
To nie jest C, ale z pewnością działa. Wszystkie inne metody, które tu widzę, działają na zasadzie umieszczania wszystkiego w częściach sześciokąta i przybliżania „kątów” z tego. Zamiast tego zaczynając od innego równania przy użyciu cosinusów i rozwiązując dla hs i v, uzyskuje się o wiele ładniejszą zależność między hsv i rgb, a animacja staje się płynniejsza (kosztem znacznie wolniejszego).
Załóżmy, że wszystko jest zmiennoprzecinkowe. Jeśli rg i b idą od 0 do 1, h wynosi od 0 do 2pi, v od 0 do 4/3, a s od 0 do 2/3.
Poniższy kod jest napisany w Lua. Można go łatwo przetłumaczyć na cokolwiek innego.
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
źródło
Wersja GLSL Shader oparta na odpowiedzi Patapom:
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 }
źródło
Nie jestem programistą C ++, więc nie będę dostarczać kodu. Ale mogę podać prosty algorytm hsv2rgb ( tutaj rgb2hsv ), który obecnie odkrywam - aktualizuję wiki o opis: HSV i HLS . Główne ulepszenie polega na tym, że uważnie obserwuję r, g, b jako funkcje odcienia i wprowadzam prostszą funkcję kształtu do ich opisu (bez utraty dokładności). Algorytm - na wejściu mamy: h (0-255), s (0-255), v (0-255)
r = 255*f(5), g = 255*f(3), b = 255*f(1)
Używamy funkcji f opisanej poniżej
f(n) = v/255 - (v/255)*(s/255)*max(min(k,4-k,1),0)
gdzie (mod może zwrócić część ułamkową; k jest liczbą zmiennoprzecinkową)
k = (n+h*360/(255*60)) mod 6;
Oto fragmenty / PoV w SO w JS: HSV i HSL
źródło
min(k,4-k,1)
. Dlaczego istnieją trzy wartości i co dokładnie się tutaj dzieje? Z góry dziękuję!Oto konwerter online z artykułem po wyjaśnieniu wszystkich algorytmów konwersji kolorów.
Prawdopodobnie wolałbyś gotową wersję C, ale jej zastosowanie nie powinno zająć dużo czasu i może pomóc innym osobom próbującym zrobić to samo w innym języku lub w innej przestrzeni kolorów.
źródło
Ten link zawiera formuły na to, co chcesz. Wtedy jest to kwestia wydajności (techniki numeryczne), jeśli chcesz, aby działała szybko.
źródło
Oto jeden, który właśnie napisałem dziś rano w oparciu o prawie taką samą matematykę jak powyżej:
/* 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); }
źródło
Stworzyłem możliwie szybszą implementację, używając zakresu 0-1 dla RGBS i V oraz zakresu 0-6 dla Hue (unikając podziału) i grupując przypadki na dwie kategorie:
#include <math.h> #include <float.h> void fromRGBtoHSV(float rgb[], float hsv[]) { // for(int i=0; i<3; ++i) // rgb[i] = max(0.0f, min(1.0f, rgb[i])); hsv[0] = 0.0f; hsv[2] = max(rgb[0], max(rgb[1], rgb[2])); const float delta = hsv[2] - min(rgb[0], min(rgb[1], rgb[2])); if (delta < FLT_MIN) hsv[1] = 0.0f; else { hsv[1] = delta / hsv[2]; if (rgb[0] >= hsv[2]) { hsv[0] = (rgb[1] - rgb[2]) / delta; if (hsv[0] < 0.0f) hsv[0] += 6.0f; } else if (rgb[1] >= hsv[2]) hsv[0] = 2.0f + (rgb[2] - rgb[0]) / delta; else hsv[0] = 4.0f + (rgb[0] - rgb[1]) / delta; } } void fromHSVtoRGB(const float hsv[], float rgb[]) { if(hsv[1] < FLT_MIN) rgb[0] = rgb[1] = rgb[2] = hsv[2]; else { const float h = hsv[0]; const int i = (int)h; const float f = h - i; const float p = hsv[2] * (1.0f - hsv[1]); if (i & 1) { const float q = hsv[2] * (1.0f - (hsv[1] * f)); switch(i) { case 1: rgb[0] = q; rgb[1] = hsv[2]; rgb[2] = p; break; case 3: rgb[0] = p; rgb[1] = q; rgb[2] = hsv[2]; break; default: rgb[0] = hsv[2]; rgb[1] = p; rgb[2] = q; break; } } else { const float t = hsv[2] * (1.0f - (hsv[1] * (1.0f - f))); switch(i) { case 0: rgb[0] = hsv[2]; rgb[1] = t; rgb[2] = p; break; case 2: rgb[0] = p; rgb[1] = hsv[2]; rgb[2] = t; break; default: rgb[0] = t; rgb[1] = p; rgb[2] = hsv[2]; break; } } } }
Dla zakresu 0-255 wystarczy * 255,0f + 0,5f i przypisać go do znaku bez znaku (lub podzielić przez 255,0, aby uzyskać odwrotność).
źródło
// This pair of functions convert HSL to RGB and vice-versa. // It's pretty optimized for execution speed typedef unsigned char BYTE typedef struct _RGB { BYTE R; BYTE G; BYTE B; } RGB, *pRGB; typedef struct _HSL { float H; // color Hue (0.0 to 360.0 degrees) float S; // color Saturation (0.0 to 1.0) float L; // Luminance (0.0 to 1.0) float V; // Value (0.0 to 1.0) } HSL, *pHSL; float *fMin (float *a, float *b) { return *a <= *b? a : b; } float *fMax (float *a, float *b) { return *a >= *b? a : b; } void RGBtoHSL (pRGB rgb, pHSL hsl) { // See https://en.wikipedia.org/wiki/HSL_and_HSV // rgb->R, rgb->G, rgb->B: [0 to 255] float r = (float) rgb->R / 255; float g = (float) rgb->G / 255; float b = (float) rgb->B / 255; float *min = fMin(fMin(&r, &g), &b); float *max = fMax(fMax(&r, &g), &b); float delta = *max - *min; // L, V [0.0 to 1.0] hsl->L = (*max + *min)/2; hsl->V = *max; // Special case for H and S if (delta == 0) { hsl->H = 0.0f; hsl->S = 0.0f; } else { // Special case for S if((*max == 0) || (*min == 1)) hsl->S = 0; else // S [0.0 to 1.0] hsl->S = (2 * *max - 2*hsl->L)/(1 - fabsf(2*hsl->L - 1)); // H [0.0 to 360.0] if (max == &r) hsl->H = fmod((g - b)/delta, 6); // max is R else if (max == &g) hsl->H = (b - r)/delta + 2; // max is G else hsl->H = (r - g)/delta + 4; // max is B hsl->H *= 60; } } void HSLtoRGB (pHSL hsl, pRGB rgb) { // See https://en.wikipedia.org/wiki/HSL_and_HSV float a, k, fm1, fp1, f1, f2, *f3; // L, V, S: [0.0 to 1.0] // rgb->R, rgb->G, rgb->B: [0 to 255] fm1 = -1; fp1 = 1; f1 = 1-hsl->L; a = hsl->S * *fMin(&hsl->L, &f1); k = fmod(0 + hsl->H/30, 12); f1 = k - 3; f2 = 9 - k; f3 = fMin(fMin(&f1, &f2), &fp1) ; rgb->R = (BYTE) (255 * (hsl->L - a * *fMax(f3, &fm1))); k = fmod(8 + hsl->H/30, 12); f1 = k - 3; f2 = 9 - k; f3 = fMin(fMin(&f1, &f2), &fp1) ; rgb->G = (BYTE) (255 * (hsl->L - a * *fMax(f3, &fm1))); k = fmod(4 + hsl->H/30, 12); f1 = k - 3; f2 = 9 - k; f3 = fMin(fMin(&f1, &f2), &fp1) ; rgb->B = (BYTE) (255 * (hsl->L - a * *fMax(f3, &fm1))); }
źródło