算法转换 RGB 到 HSV 和 HSV 到 RGB 范围0-255的两个

我要寻找颜色空间转换器从 RGB 到 HSV,具体范围0至255为两个颜色空间。

245519 次浏览

这个链接 有你想要的公式。然后,如果你想要它快,那就是性能(数值技术)的问题了。

我已经使用它们很长时间了——现在还不知道它们来自哪里... ... 请注意,除了角度之外,输入和输出都在0到1.0的范围内。

注意: 此代码不对输入进行真正的健全性检查。请谨慎操作!

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

您也可以尝试不使用 float (更快但不太准确)的代码:

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 its range (not 0-360) as that was requested by the author of this question.

我用 HLSL 为我们的渲染引擎写了这个,它没有任何条件:

    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
}

这个应该在这里: 无论如何,它都是有效的。而且和上面的比起来,它看起来还不错。

这是密码

        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是16位精度向量3数据类型,即 float3 hue ()返回一个数据类型(x,y,z) ,例如(r,g,b) ,一半与一半精度相同,8bit,a float4是(r,g,b,a)4值。

@ fins 的回答有一个溢出问题在 Arduio 上,当你把饱和度下降。这里有一些转换为 int 的值,以防止这种情况发生。

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

基于 Patapoms 的 GLSL 着色器版本答案:

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
}

这不是 C,但它确实有用。我在这里看到的所有其他方法都是通过将所有内容包装成六边形的一部分,然后从六边形逼近“角度”来工作的。通过使用余弦从一个不同的方程出发,并求解 h 和 v,你会得到 hsv 和 rgb 之间更好的关系,并且 Tweing 变得更加平滑(代价是它变得更慢)。

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.

下面的代码是用 Lua 编写的。

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

这是一个基于 Agoston 的 计算机图形学和几何模型: 实现和算法 p. 304的 C 实现,其中 H∈[0,360] ,SV∈[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;
}

这是我今天早上根据和上面几乎一样的数学原理写的一篇文章:

/* 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);
}

我创建了一个可能更快的实现,对 RGBS 和 V 使用0-1范围,对 Hue 使用0-6范围(避免划分) ,并将案例分为两类:

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

对于0-255范围,只需 * 255.0 f + 0.5 f,并将其赋给一个无符号字符(或除以255.0得到相反的结果)。

我不是 C + + 开发人员,所以我不会提供代码。但我可以提供简单的 你好算法(rgb2hsv here) ,我目前发现-我更新维基的描述: HSVHLS。主要的改进是对色调函数 r,g,b 进行了仔细的观察,并引入了更简单的形状函数来描述它们(不失精度)。算法-在输入端我们有: h (0-255) ,s (0-255) ,v (0-255)

r = 255*f(5),   g = 255*f(3),   b = 255*f(1)

我们使用如下所述的函数 f

f(n) = v/255 - (v/255)*(s/255)*max(min(k,4-k,1),0)

哪里(mod 可以返回小数部分; k 是浮点数)

k = (n+h*360/(255*60)) mod 6;

下面是 JS: HSVHSL中 SO 中的代码片段/PoV

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

下面是一篇介绍了所有颜色转换算法的 online converter文章。

您可能更喜欢现成的 C 版本,但它应该不会太长,它可以帮助其他人尝试在另一种语言或另一个颜色空间做同样的事情。