RGB三原色是基於人肉眼對光線的生理作用。人眼內有三種椎狀體“對這三種光線頻率所能感受的帶寬最大,也能獨立刺激這三種顏色的受光體”,因此RGB稱為三原色。比如,黃色波長的光對人眼的刺激效果,和紅色與綠色同時刺激人眼相同,所以,對人來說R+G=yellow,即(255,255,0).
相對於RGB,HSB(也叫HSV)模式更便於描述人眼對與顏色的感覺。如圖的HSB椎形坐標,橫截面從下往上亮度值從0%到100%遞增;橫截面的中心點是灰色的,隨着半徑增大,飽和度從0增大到100%;色相取值0°~360°,代表截面上紅、黃、綠、青、藍、粉紅的顏色變化。
HSB坐標系最頂部的截面最外圈,飽和度和亮度都是100%,隨着色相從0°到360°變化,RGB值的變化如上圖,紅色、綠色、藍色分別位於0°、120°、240°;0°到60°之間綠色分量均勻增加,60°到120°之間紅色分量均勻減少,以此類推。將
這個演示中的S和B調到100%,調整H的值觀察RGB的變換就能發現這個規律。這時RGB中最大值一定是255(因為亮度為100%),最小值一定是0(如果不是,比如RGB=(10,20,255),可以看成在(0,10,255)分量的基礎上加上一個灰色分量(10,10,10),加灰色后飽和度就不是100%了)。
HSB轉RGB
要算出(h,s,b)對應的(r,g,b),可以分3步,以HSB=(130°,50%,80%)為例說明:
1、先算出(h,100%,100%)對應的(r',g',b')。
先固定色相,色環圖上色相H∈[-60°,60°]時紅色分量最大,H∈[60°,180°]時綠色分量最大,H∈[180°,300°]時藍色分量最大,此時B=100%,所以RGB的最大分量為255。
先算出HSB=(130°,100%,100%)對應的(r',g',b'):H=130°,在[60°,180°]區間,所以綠色分量為g'=255,紅色分量r'=0;進一步地,130°處於[120°,180°],在這60°的區間上,色環上藍色分量對應地從0遞增到255,所以b'=(130°-120°)/60°*255=43。所以(r',g',b')=(0,255,43)
2、固定色相后再調整飽和度,算出(h,s,100%)對應的(r'',g'',b'')。
在亮度B=100%時,從
演示中發現,飽和度S降低,即“不飽和度”(1-S)升高,會使得RGB與最大值255相差的部分對應增大,RGB三個分量越趨於相同就使得圖像越灰。所以,
r''=r'+(255-r')*(1-S) .................................... ①
g''和b''用同樣的方法求出。當然,r'g'b'中的最大值不會變化。(r'',g'',b'')=(128,255,149)
3、最后算出(h,s,b)對應的(r,g,b)。
最后調整亮度,只要依照亮度值的百分比縮小就行了,(r,g,b)=(r'',g'',b'')*80%=(102,204,119)就是HSB=(130°,50%,80%)對應的RGB

public float[] hsb2rgb(float[] hsb) { float[] rgb= new float[3]; //先令飽和度和亮度為100%,調節色相h for(int offset=240,i=0;i<3;i++,offset-=120) { //算出色相h的值和三個區域中心點(即0°,120°和240°)相差多少,然后根據坐標圖按分段函數算出rgb。但因為色環展開后,紅色區域的中心點是0°同時也是360°,不好算,索性將三個區域的中心點都向右平移到240°再計算比較方便 float x=Math.abs((hsb[0]+offset)%360-240); //如果相差小於60°則為255 if(x<=60) rgb[i]=255; //如果相差在60°和120°之間, else if(60<x && x<120) rgb[i]=((1-(x-60)/60)*255); //如果相差大於120°則為0 else rgb[i]=0; } //在調節飽和度s for(int i=0;i<3;i++) rgb[i]+=(255-rgb[i])*(1-hsb[1]); //最后調節亮度b for(int i=0;i<3;i++) rgb[i]*=hsb[2]; return rgb; }
RGB轉HSB
這個從HSB轉RGB倒推回去就會很容易知道亮度和飽和度的算法
亮度:B=max(R,G,B)/255
飽和度:1-S=min(R,G,B)/255,所以飽和度S=1-min(R,G,B)/255
色相:先算出(r,g,b)對應的飽和度和亮度均為100%時的(r',g',b'),然后既可以根據色環的規律求出色相了。
r'、g'、b'的序號用0、1、2表示,max、min、mid分別是r'g'b'中最大值、最小值和中間值;設maxIndex、minIndex、midIndex分別是r'g'b'中最大值、最小值和中間值的序號。首先將色相h定位到(maxIndex*120°)然后根據中間值算出偏移量(mid/255*60°),既
h=(maxIndex*120°)±(mid/255*60°)....................................②
關於②中的±號的確定方法:將當0、1、2順時針寫成一圈,當midIndex在maxIndex的順時針方向(既minIndex在maxIndex的逆時針方向)時取正,否則取負,原因看色環就能知道。所以±號可以用(maxIndex-minIndex+3)%3是否等於1來判斷,是取正,否則取負。②式可以改寫成:
h=(maxIndex*120°)+((maxIndex-minIndex+3)%3==1?1:-1)*(mid/255*60°)....................................③
以(r,g,b)=(102,204,85)為例,前兩步已求出亮度為80%,飽和度為50%。當亮度和飽和度均為100%而色相不變時,最大值g'一定是255;最小值r'一定是0;中間值b'按照上面HSB轉RGB的后兩步反推回去,先得出亮度為100%時b''=102/80%=128,再由①式得出飽和度為100%時b'=43。
因為g'=255最大,先定位到色環上的120°的位置,因為藍色分量不為0,所以要從120°順時針偏移b'/255*60°=10°,即H=130°

public float[] rgb2hsb(float[] rgb) { float[] hsb = new float[3]; float[] rearranged = rgb.clone(); int maxIndex = 0,minIndex = 0; float tmp; //將rgb的值從小到大排列,存在rearranged數組里 for(int i=0;i<2;i++) { for(int j=0;j<2-i;j++) if(rearranged[j]>rearranged[j+1]) { tmp=rearranged[j+1]; rearranged[j+1]=rearranged[j]; rearranged[j]=tmp; } } //rgb的下標分別為0、1、2,maxIndex和minIndex用於存儲rgb中最大最小值的下標 for(int i=0;i<3;i++) { if(rearranged[0]==rgb[i]) minIndex=i; if(rearranged[2]==rgb[i]) maxIndex=i; } //算出亮度 hsb[2]=rearranged[2]/255.0f; //算出飽和度 hsb[1]=1-rearranged[0]/rearranged[2]; //算出色相 hsb[0]=maxIndex*120+60* (rearranged[1]/hsb[1]/rearranged[2]+(1-1/hsb[1])) *((maxIndex-minIndex+3)%3==1?1:-1); //防止色相為負值 hsb[0]=(hsb[0]+360)%360; return hsb; }