怎么样计算Hue色环?
File: FastHue.txt
Name: 快速计算Hue色环
Author: zyl910
Blog: http://blog.csdn.net/zyl910/
Version: V1.00
Updata: 2006-11-3
下载(注意修改下载后的扩展名)
一、HSV色彩空间
H: 色调(Hue)。范围: [0, 360) 0度: 红色,RGB:(255, 0, 0), 255:R, 0:B,G+ 60度: 黄色,RGB:(255,255, 0),255:G, 0:B, R- 120度: 绿色,RGB:( 0,255, 0),255:G, 0:R,B+ 180度: 青色,RGB:( 0,255,255),255:B, 0:R,G- 240度: 蓝色,RGB:( 0, 0,255),255:B, 0:G,R+ 300度: 紫色,RGB:(255, 0,255),255:R, 0:G,B- 360度: 红色,RGB:(255, 0, 0),255:R, 0:B,G+ 在这些标准的颜色值之间的颜色是通过线性插值得到的。如30度的橙色,它是0度红色与60度黄色之间的颜色,所以它的RGB值是 (255, 0, 0)*50% + (255,255, 0)*50% = (255,127.5,0)。 由于在同一个60度区间中的颜色值只有一个分量不同,所以只需要对一个分量进行线性插值。
S: 饱和度(Saturation)。范围: [0%, 100%]。是 H所代表的颜色 与 白色 混合的比率。 假设某个颜色的H分量为30、S分量为80%(、V分量为100%),它的RGB值是: (255,127.5,0)*80% + (255, 255, 255)*20% = (255,153,51)
V: 亮度( Value 或 Brightness,所以有时也叫HSB)。范围: [0%, 100%]。是 H、S所代表的颜色 与 黑色 混合的比率。 假设某个颜色的H分量为30、S分量为80%、V分量为60%,它的RGB值是: (255,153,51)*60% + (0,0,0)*40% = (255,153,51)*60% = (153,91.8,30.6)
也就是说计算步骤是:先根据H算出纯色颜色值,然后根据S将结果与白色混合,再根据V将结果与黑色混合。
二、快速计算Hue色环
2.1分析[0,60)区间
我们先观察一下[0,60)区间的颜色值: 0: R=255, B=0, G = 0 * 255 / 60 = 0/60 = 0 + 0/60 1: R=255, B=0, G = 1 * 255 / 60 = 255/60 = 4 + 15/60 2: R=255, B=0, G = 2 * 255 / 60 = 510/60 = 8 + 30/60 3: R=255, B=0, G = 3 * 255 / 60 = 765/60 = 12 + 45/60 4: R=255, B=0, G = 4 * 255 / 60 = 1020/60 = 17 + 0/60 ... 56: R=255, B=0, G = 56 * 255 / 60 = 14280/60 = 238 + 0/60 57: R=255, B=0, G = 57 * 255 / 60 = 14535/60 = 242 + 15/60 58: R=255, B=0, G = 58 * 255 / 60 = 14790/60 = 246 + 30/60 59: R=255, B=0, G = 59 * 255 / 60 = 15045/60 = 250 + 45/60 60: R=255, B=0, G = 60 * 255 / 60 = 15300/60 = 255 + 0/60
由于RGB分量的最大值是255、区间的尺寸是60,所以计算公式为:G = i * 255 / 60
最终结果我写成带分数形式,因为这种形式比较容易理解——整数部分是就是RGB值。至于分数部分,可以使用四舍五入的,但我个人觉得不进行舍入处理显得更平均一些。 可以看出,由于是线性插值,下一个比上一个的多出了 255/60(或 4 + 15/60)。最终到达60时,恰好整数部分为255、分数部分为0。 于是我们得到这样的算法:
整数部分 = 0 分数部分 = 0 while(整数部分 < 255){ 绘制像素(RGB(255, 整数部分, 0)) 整数部分 += 4 // 255/60 = 4 + 15/60 分数部分 += 15 if (分数部分 >= 60) { 分数部分 -= 60 整数部分++ } }
是不是感觉有点像Bresenham算法。我就是在看懂Bresenham算法时,才发现自己这才开始理解有理数的。有理数是两个数字的比值(分子和分母),写成假分数或带分数形式是最容易理解的,生活上惯用的带小数写法反而有堵塞思维之嫌。
2.2分析[60,120)区间
先前的[0,60)区间的G分量是增长的,对于像[60,120)区间这样的R分量减少的区间又该怎么呢? 我们来观察一下: 60: G=255, B=0, R = 255 - ( 60 - 60) * 255 / 60 = 255 - 0 * 255 / 60 = 255 - 0/60 = 255 - ( 0 + 0/60) 61: G=255, B=0, R = 255 - ( 61 - 60) * 255 / 60 = 255 - 1 * 255 / 60 = 255 - 255/60 = 255 - ( 4 + 15/60) 62: G=255, B=0, R = 255 - ( 62 - 60) * 255 / 60 = 255 - 2 * 255 / 60 = 255 - 510/60 = 255 - ( 8 + 30/60) 63: G=255, B=0, R = 255 - ( 63 - 60) * 255 / 60 = 255 - 3 * 255 / 60 = 255 - 765/60 = 255 - ( 12 + 45/60) 64: G=255, B=0, R = 255 - ( 64 - 60) * 255 / 60 = 255 - 4 * 255 / 60 = 255 - 1020/60 = 255 - ( 17 + 0/60) ... 116: G=255, B=0, R = 255 - (116 - 60) * 255 / 60 = 255 - 56 * 255 / 60 = 255 - 14280/60 = 255 - (238 + 0/60) 117: G=255, B=0, R = 255 - (117 - 60) * 255 / 60 = 255 - 57 * 255 / 60 = 255 - 14535/60 = 255 - (242 + 15/60) 118: G=255, B=0, R = 255 - (118 - 60) * 255 / 60 = 255 - 58 * 255 / 60 = 255 - 14790/60 = 255 - (246 + 30/60) 119: G=255, B=0, R = 255 - (119 - 60) * 255 / 60 = 255 - 59 * 255 / 60 = 255 - 15045/60 = 255 - (250 + 45/60) 120: G=255, B=0, R = 255 - (120 - 60) * 255 / 60 = 255 - 60 * 255 / 60 = 255 - 15300/60 = 255 - (255 + 0/60)
由于现在是[60,120)区间,且现在是减少,所以计算公式为:R = (i-60) * 255 / 60 = (120 - i) * 255 / 60
可以看出计算带分数的方法是一样的,只是在绘制时R分量为“255 - 带分数”而已
2.3处理任意宽度的算法
刚才我们分析了 [0,60)区间 和 [60,120)区间 的Hue色环。对于其他区间,计算颜色值的方法是一样的,只不过所填写的RGB分量不同而已。所以我们应该考虑编写一个完整的计算Hue色环的办法。
如果单纯是生成宽度是360的Hue色环的话,那我们没必要写程序,只用一个有360个元素的数组来查表就行了,所以我们需要的能处理任意宽度的算法。由于用户输入的色环宽度值不一定是6的倍数,所以每个区间的长度不是整数。 先回顾一下我们分析[0,60)区间时,说“由于RGB分量的最大值是255、区间的尺寸是60,所以计算公式为:G = i * 255 / 60”。如果我们将这两个系数同时放大6倍,那么式子变为“G = i * (255*6) / 360”。根据比例性质,结果与原来的式子相同。所以任意宽度下的计算公式为:G = i * (255*6) / huesize
然后我们考虑如何设计函数。由于现在Windows平台很流行,所以我希望程序直接输出真彩色的DIB(设备无关位图)位图数据。为了适应不同情况(24位或32位),我又提供了cbPixel参数已得知每个像素所占字节。 最终代码是: // 计算Hue色环 // Return: 成功返回非0,失败返回0。 // Args: // lpBuf: 真彩色DIB位图数据缓冲区 // cbPixel: 一个像素所占字节 // huesize: Hue色环的宽度 BOOLMakeHue(LPVOIDlpBuf, intcbPixel, inthuesize) { intvalue, fract; // (255*6)/huesize 的整数部分和分数部分 int i, ifract; // 当前值 LPBYTEpby = (LPBYTE)lpBuf;
ASSERT(lpBuf != 0); ASSERT(huesize > 0);
// (255*6)/huesize 的整数部分和分数部分 value = (255*6) / huesize; fract = (255*6) % huesize; i = ifract = 0;
// red ~ yellow: [0, 60) while(i < 255){ // Draw pby[2] = 0xff; pby[1] = i; pby[0] = 0x00; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }; i -= 255;
// yellow ~ green: [60, 120) while(i < 255){ // Draw pby[2] = 0xff - i; pby[1] = 0xff; pby[0] = 0x00; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }; i -= 255;
// green ~ cyan: [120, 180) while(i < 255){ // Draw pby[2] = 0x00; pby[1] = 0xff; pby[0] = i; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }; i -= 255;
// cyan ~ blue: [180, 240) while(i < 255){ // Draw pby[2] = 0x00; pby[1] = 0xff - i; pby[0] = 0xff; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }; i -= 255;
// blue ~ magenta: [240, 300) while(i < 255){ // Draw pby[2] = i; pby[1] = 0x00; pby[0] = 0xff; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }; i -= 255;
// magenta ~ red: [300, 360) while(i < 255){ // Draw pby[2] = 0xff; pby[1] = 0x00; pby[0] = 0xff - i; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }; //i -= 255;
returnFALSE; }
我承认这样的代码不够简洁,因为计算i的方式是一样,只是绘制RGB值的代码不同而已,这样不同完全可以通过查表法解决。但是那样做不利于编译优化(索引是动态的),影响速度。
三、快速生成指定饱和度和亮度下的Hue色环
既然是指定了饱和度和亮度,那么需要根据s、v计算最终的颜色值。 注意每个RGB分量都是单独计算的,即每个分量都进行了如下的变换: f(x) = (x*s + 255*(1-s)) * v = (255 + (x-255)*s)*v = (255 - (255-x)*s)*v
由于浮点运算很慢,所以我们需要整数算法。Windows系统是32位操作系统,所以整数是32位。RGB分量是8位,(32-8) / 2 = 24 / 2 = 12,所以s和v可以有12位精度: is = (DWORD)(s * 1<<12) iv = (DWORD)(v * 1<<12) f(x) = (255 - (255-x) * is / (1<<12)) * iv / (1<<12) = ((255<<12 - (255-x) * is) / (1<<12)) * iv / (1<<12) = (255<<12 - (255-x) * is) * iv / (1<<24) = ((255<<12 - (255-x) * is) * iv) >> 24
由于RGB分量的取值范围是[0,255],所以我们还可以查表优化。 最终代码: // 计算指定饱和度和亮度时的Hue色环 // Return: 成功返回非0,失败返回0。 // Args: // lpBuf: 真彩色DIB位图数据缓冲区 // cbPixel: 一个像素所占字节 // huesize: Hue色环的宽度 // fS: 饱和度,[0,1]。对数值做饱和处理 // fV: 亮度度,[0,1]。对数值做饱和处理 BOOLMakeHueEx(LPVOIDlpBuf, intcbPixel, inthuesize, floatfS, floatfV) { BYTEtbl[0x100];// 颜色值映射表格 DWORDiS, iV;// 12位精度的饱和度与亮度 intvalue, fract; // (255*6)/huesize 的整数部分和分数部分 int i, ifract; // 当前值 LPBYTEpby = (LPBYTE)lpBuf;
ASSERT(lpBuf != 0); ASSERT(huesize >= 6);
// 12位精度的饱和度与亮度 if (fS < 0)fS = 0; else if (fS > 1) fS = 1; if (fV < 0)fV = 0; else if (fV > 1) fV = 1; iS = (DWORD)(fS * (1<<12)); iV = (DWORD)(fV * (1<<12));
// 亮度为0——黑色 if (iV == 0) { while(huesize > 0) { pby[2] = 0; pby[1] = 0; pby[0] = 0; pby += cbPixel; huesize--; } returnTRUE; }
// 计算 颜色值映射表格 for(i=0; i<=0xff; i++) { tbl[i] = (BYTE)( (((255<<12) - (255-i) * iS) * iV + (1<<23)) >> 24 );// "+ 1<<23" 是为了四舍五入 }
// (255*6)/huesize 的整数部分和分数部分 value = (255*6) / huesize; fract = (255*6) % huesize; i = ifract = 0;
// red ~ yellow: [0, 60) do{ // Draw pby[2] = tbl[0xff]; pby[1] = tbl[i]; pby[0] = tbl[0x00]; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }while(i < 255); i -= 255;
// yellow ~ green: [60, 120) do{ // Draw pby[2] = tbl[0xff - i]; pby[1] = tbl[0xff]; pby[0] = tbl[0x00]; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }while(i < 255); i -= 255;
// green ~ cyan: [120, 180) do{ // Draw pby[2] = tbl[0x00]; pby[1] = tbl[0xff]; pby[0] = tbl[i]; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }while(i < 255); i -= 255;
// cyan ~ blue: [180, 240) do{ // Draw pby[2] = tbl[0x00]; pby[1] = tbl[0xff - i]; pby[0] = tbl[0xff]; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }while(i < 255); i -= 255;
// blue ~ magenta: [240, 300) do{ // Draw pby[2] = tbl[i]; pby[1] = tbl[0x00]; pby[0] = tbl[0xff]; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }while(i < 255); i -= 255;
// magenta ~ red: [300, 360) do{ // Draw pby[2] = tbl[0xff]; pby[1] = tbl[0x00]; pby[0] = tbl[0xff - i]; pby += cbPixel; // Next i += value; ifract += fract; if (ifract >= huesize) { ifract -= huesize; i++; } }while(i < 255); i -= 255;
returnFALSE; }
本文地址:http://www.45fan.com/dnjc/67567.html