您的位置:首页 > 产品设计 > UI/UE

快速计算Hue色环

2006-11-04 01:12 204 查看
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的整数部分和分数部分
inti,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的整数部分和分数部分
inti,ifract;//当前值
LPBYTEpby=(LPBYTE)lpBuf;

ASSERT(lpBuf!=0);
ASSERT(huesize>=6);

//12位精度的饱和度与亮度
if(fS<0)fS=0;
elseif(fS>1)fS=1;
if(fV<0)fV=0;
elseif(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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: