zoukankan      html  css  js  c++  java
  • 快速计算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色环的宽度
    BOOL MakeHue(LPVOID lpBuf, int cbPixel, int huesize)
    {
     int value, fract; // (255*6)/huesize 的整数部分和分数部分
     int i, ifract; // 当前值
     LPBYTE pby = (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;
     return FALSE;
    }

      我承认这样的代码不够简洁,因为计算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]。对数值做饱和处理
    BOOL MakeHueEx(LPVOID lpBuf, int cbPixel, int huesize, float fS, float fV)
    {
     BYTE tbl[0x100]; // 颜色值映射表格
     DWORD iS, iV; // 12位精度的饱和度与亮度
     int value, fract; // (255*6)/huesize 的整数部分和分数部分
     int i, ifract; // 当前值
     LPBYTE pby = (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--;
      }
      return TRUE;
     }
     // 计算 颜色值映射表格
     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;
     return FALSE;
    }
    作者:zyl910
    版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0.
  • 相关阅读:
    P3373 线段树模板
    由AC自动机引发的灵感
    瞎子摸象序
    Outer Join的where条件
    将 转移单 自动发货
    获取某个结点的所有层
    瞎子摸象库存篇
    js修改select列表选项中的值
    JS仿flash动态切换(横向,带分页器控制,自动正反向循环轮播)
    js操作select下拉列表的一些通用代码
  • 原文地址:https://www.cnblogs.com/zyl910/p/2186635.html
Copyright © 2011-2022 走看看