快速計算Hue色環

來源:互聯網
上載者:User

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.