Algoritmo para converter RGB para HSV e HSV para RGB no intervalo 0-255 para ambos

Eu estou procurando por conversor de espaço de colors de RGB para HSV, especificamente para o intervalo de 0 a 255 para ambos os espaços de colors.

Eu usei estes por um longo tempo – não tenho idéia de onde eles vieram neste momento … Note que as inputs e saídas, com exceção do ângulo em graus, estão no intervalo de 0 a 1,0.

OBSERVAÇÃO: este código não faz nenhuma verificação real das inputs. Prossiga com cuidado!

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; } 

Você também pode tentar este código sem floats (mais rápido, mas menos preciso):

 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; } 

Eu escrevi isso no HLSL para o nosso mecanismo de renderização, ele não tem condições:

  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 } 

isso deve estar aqui: funciona de qualquer maneira. E parece bom em comparação com os acima.

  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 é um tipo de dados vector3 de precisão de 16 bits, isto é, float3 hue () retorna um tipo de dados (x, y, z) eg (r, g, b), metade é igual a metade precisão, 8bit, float4 é (r, g, b, a) 4 valores.

Aqui está uma implementação de C baseada em Computação Gráfica de Agoston e Modelagem Geométrica: Implementação e Algoritmos p. 304, com H ‘ [0, 360] e S , V’ [0, 1].

 #include  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; } 

A resposta do @fins tem um problema de estouro no Arduio quando você reduz a saturação. Aqui está com alguns valores convertidos em int para evitar isso.

 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; } 

Isso não é C, mas certamente funciona. Todos os outros methods que vejo aqui funcionam encheckboxndo tudo em partes de um hexágono e aproximando “ângulos” daquele. Ao começar com uma equação diferente usando cossenos e resolvendo para hs e v, você obtém um relacionamento muito melhor entre hsv e rgb, e a interpolação se torna mais suave (ao custo de ser mais lento).

Suponha que tudo seja um ponto flutuante. Se rg e b vão de 0 a 1, h vai de 0 a 2pi, v vai de 0 a 4/3 e vai de 0 a 2/3.

O código a seguir está escrito em Lua. É facilmente traduzível em qualquer outra coisa.

 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*rg*rb*gb*r)^0.5 local h=atan2(bg,(2*rbg)/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 

Versão do Shader GLSL baseada na resposta de Patapoms:

 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 } 

Este link tem fórmulas para o que você quer. Então é uma questão de performance (técnicas numéricas) se você quiser rápido.

Aqui está uma que eu acabei de escrever esta manhã com base na mesma matemática acima:

 /* 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); } 
 How is this algorithm- void RGB_to_HSV(float R, float G, float B){ float H, S, V; float Max, Min; Max = max(R, G, B); Min= min(R, G, B); V= Max; if(Max == 0){ s=0; } else{ s= Max-Min/Max; } if(s==0){ H = undefined; } else{ if(R == Max){ H = (GB)*60/Max-Min; if(H<0) H+ = 360; } elseif(G==Max){ H = (BR)*(60+120)/Max-Min; } else{ H = (RG)*(60+240)/Max-Min; } } printf('HS V'); } 

Eu criei uma implementação possivelmente mais rápida usando um intervalo de 0-6 de valores para Hue (evitando a divisão) e agrupando os casos em duas categorias:

 #include  #include  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; } } } }