無論是用何種語言,只要使用過Windows的GDI+的人對ColorMatrix都不陌生,我的BLOG文章中也多次提到過,並在《GDI+ for VCL基礎 -- 顏色調整矩陣ColorMatrix詳解》一文中對其功能作了較為詳細的講解,雖然自認對ColorMatrix使用已經相當熟練,但對其原理也是知其然而不知其所以然。直到前幾天有位朋友就ColorMatrix實現映像去反功能不正常而問我(見http://topic.csdn.net/u/20080830/20/070c83de-d45b-441f-996e-3c68892855cd.html),我認為不大可能!眾所周知,用RGB主對角線-1矩陣實現映像求反是ColorMatrix重要功能之一,可是我實驗了多次,那張圖片確實不能正常去反。我這人對自己自認熟悉的東西產生懷疑後,就有種不搞明白不罷休的衝動,為此,本人對ColorMatrix的原理作了透徹的解剖,不僅搞清楚了映像去反不正常的原因,還用代碼完整地實現了ColorMatrix功能!
本文主要揭秘GDI+ ColorMatrix調整原理,實現代碼另文介紹(可參見《Delphi影像處理 -- 映像顏色矩陣調整》)。
雖然我的BLOG中已有2篇文章引用了那段經典的關於ColorMatrix功能介紹的文字,但這裡還是再引用一次,作為原理揭秘的開始:
GDI+ 提供用於儲存和操作映像的 Image 和 Bitmap 類。Image 和 Bitmap 對象將每個像素的顏色都儲存為 32 位的數:紅色、綠色、藍色和 alpha 各占 8 位。這四個分量的值都是 0 到 255,其中 0 表示沒有亮度,255 表示最大亮度。alpha 分量指定顏色的透明度:0 表示完全透明,255 表示完全不透明。
顏色向量採用 4 元組形式(紅色、綠色、藍色、alpha)。例如,顏色向量 (0, 255, 0, 255) 表示一種沒有紅色和藍色但綠色達到最大亮度的不透明顏色。
表示顏色的另一種慣例是用數字 1 表示亮度達到最大。使用這種慣例,上一段中描述的顏色將用 (0, 1, 0, 1) 表示。GDI+ 在進行顏色變換時使用以 1 表示最大亮度的慣例。
可通過用 4×4 矩陣乘以這些顏色向量將線性變換(旋轉和縮放等)應用到顏色向量中。但是,您不能使用 4×4 矩陣進行平移(非線性)。如果在每個顏色向量中再添加一個虛擬第 5 座標(例如,數字 1),則可使用 5×5 矩陣應用任何組合形式的線性變換和平移。由線性變換組成的後跟平移的變換稱為仿射變換。
說實話,不僅是剛開始接觸ColorMatrix的時候,就是現在的我看到這段文字,都覺得有點玄,前3段很好理解,特別是後面那段,更是使那些初識GDI+的人覺得ColorMatrix深奧無比:ColorMatrix是怎樣實現顏色的縮放、旋轉、剪下及平移的?靠這些功能能實現映像的哪些效果?或者說,某種效果能用ColorMatrix實現嗎?
下面,就讓我們一步步揭開ColorMatrix的神秘面紗,我相信,當你看完這篇文章後,一定會說:哦,原來如此,這麼簡單!其實,這就是所謂“江湖一張紙,戳破不值一分錢”!很多看起來技術含量很高的東西,被人千方百計保密,申請專利,搞的神秘無比,其實一旦公開,就那麼回事!
要揭秘ColorMatrix,就要解析它的縮放、旋轉、剪下及平移功能是怎麼實現的,為了方便敘述,我畫了個ColorMatrix矩陣圖貼在上面,在下面的表述中,大寫ARGB表示顏色各分量現有的值,而小寫argb表示運算後得到的新值。
1、顏色縮放:顏色縮放很簡單,就是按照給定的比例值,在映像像素現有A、R、G、B各分量數值基礎上計算出新的分量值。這個比例值就是ColorMatrix主對角線除m55外的其它4個值。比如某像素的RGBA值現在分別為255、128、64和255,而主對角線m11 - m44的值分別為0.8、0.5、-1及0.5,那麼該像素新的rgba值應該是:
r = R * m11 = 255 * 0.8 = 204;
g = G * m22 = 128 * 0.5 = 64;
b = B * m33 = 64 * -1 = 192;
a = A * m44 = 255 * 0.5 = 128;
是否很簡單?!可能有初學者說我上面b值計算錯了,64 * -1應該等於-64。沒錯,64用32位元表示為0xFFFFFFC0,無符號位元組飽和取整,取最後8位0xC0,等於192。關於負值運算的問題,後面還要詳細講到的。
2、顏色剪下:一般說來,映像像素R、G、B各分量按照與另一種顏色分量成比例的量來增加或減少顏色分量就是剪下。其實這種表述並不完全,像素的A分量也是參與其中的!
以紅色分量R舉例,如果要按綠色分量G進行剪下,那麼m21就是剪下比例值,m21 * G就得到了G對R的剪下量。同理,m31 * B、m41 * A可分別得到B和A對R的剪下量,將這些剪下量加起來,就是R總的剪下量。用公式表示為:
r = G * m21 + B * m31 + A * m41;
g = R * m12 + B * m32 + A * m42;
b = R * m13 + G * m23 + A * m43;
a = R * m14 + G * m24 + B * m34;
3、顏色旋轉:顏色旋轉的描述比較複雜,就是在映像像素中,用其中的2個分量按照一定的角度圍繞另外1個分量作運算的結果,就是顏色的旋轉。以紅色分量R和綠色分量G圍繞藍色分量G旋轉60度為例:
m11 = cos(60) = 0.5, m12 = sin(60) = 0.866, m21 = -sin(60) = -0.866, m22 = cos(60) = 0.5,那麼,R和G 所得到的旋轉量分別為:
r = R * m11(0.5) + G * m21(-0.866);
g = R * m12(0.866) + G * m22(0.5);
從上面的公式看,所謂的顏色旋轉量,其實就是旋轉的2個分量自身的縮放量加上與對方的剪下量而已!就運算角度看,同其它分量沒有任何關係。
4、顏色平移:上面的縮放、剪下和旋轉屬於顏色的線性變換(都是乘法運算的累積和),而平移是顏色的非線性變換,就是對顏色各分量做一個加法而已:映像像素各分量的平移量用所謂的虛擬位,即第5行的各個值來表示,各分量加上所在列的虛設項目列的值就是顏色平移,其實質就是非線性地調整了該分量的亮度值。用公式表示各分量的平移量:
r = R + m51 * 255;
g = G + m52 * 255;
b = B + m53 * 255;
a = A + m54 * 255;
綜合以上顏色的縮放、旋轉、剪下及平移公式,對於顏色的每個分量R、G、B、A來說,運用ColorMatrix後所得到的實際值r、g、b、a,用公式表示為:
r = R * m11 + G * m21 + B * m31 + A * m41 + m51 * 255;
g = R * m12 + G * m22 + B * m32 + A * m42 + m52 * 255;
b = R * m13 + G * m23 + B * m33 + A * m43 + m53 * 255;
a = R * m14 + G * m24 + B * m34 + A * m44 + m54 * 255;
從技術層面上講,這個公式的含義就是顏色每個分量的新值,等於這個分量在ColorMatrix中對應列的前4行的值與R、G、A、B當前值的乘積之和加上第5行的值與常數255的乘積,而虛擬列(第5列)不起任何作用。
關於ColorMatrix功能的實現原理就這些,最後說說前面所提到的負值運算問題:ColorMatrix的負值運算無論是從原理上還是純技術實現上都很有些麻煩:
a)、在前面縮放舉例中,也就是只有主對角線有值的情況下,b的負值運算為 b = B * m33 = 64 * -1 = 192,這是正確的,但是,如果加上一個剪下量或者平移量,這個值就發生了一些變化。比如在縮放基礎上加上0.1的平移量,這似乎是很簡單的問題,直接用0.1 * 255加上去不就得了!可是,這個看是簡單的問題,卻有2個方案:是在縮放後的192基礎上加平移量0.1 * 255等於218?還是應該做連續運算: b = B * m33 = 64 * -1 + 255 * 0.1= -38(飽和處理後為0)呢?如果是前者,運演算法則上似乎說不過去;如果是後者,感官上難以接受,本來b=192使顏色值“藍藍”的,本想讓它再“藍”一點,而加個平移(亮度)量,可即使這個平移量相當小,例如只平移0.001,卻讓b值變成了0,不僅沒有變的更“藍”,反而使顏色失去了藍色分量!可ColorMatrix偏偏用了使你感官難以接受的方案。其實也難怪設計者,只要順其自然去實現代碼,最後自然就成了這個結果。能夠造成這個結果的,不僅僅是平移量,只要矩陣中主對角線以外的任何一個值不為0,包括不起作用的第5列,都會使主對角線中,具有負值所對應的RGBA分量發生這種變化。具體請看本文中篇的代碼實現。
b)、前面也提到了有朋友用-1矩陣對映像求反不合要求的問題。通過上面的縮放公式計算一下,不難發現,用-1矩陣所得到的映像取反圖只是近似的!為什麼這麼說呢?所謂取反,就是可逆,如果用255減去R(GB)取反後,再用255去減就可還原;用255異和R(GB),不用說也是可逆的,而-1 * R(GB)卻是不完全可逆的:1 * -1飽和取整後為255,2 * -1為254 ......等等;反過來還原:結果是255 * -1 = 1、254 * -1 = 2......等等,從1 - 255都是可還原的,雖然這個還原於原本意義上的去反還原有1的誤差,但從人的感官上,這點誤差還是能接受的,只有“0”這個值是無法復原的,:0 * -1飽和取整還是0,無論是取反,還是還原,“0”依然是“0”,從而破壞了取反圖的協調,使人的感官完全無法接受,例如原來純紅色的像素、取反後應該為青色,可是這裡卻變成了黑色,你能接受嗎?所以只要映像像素RGB任何一個分量為0,那麼這幅圖的取反效果就會出問題!有人可能說,我們都是這麼做的,除了極少數圖片,大部分取反圖看上去還是正常的,難道這些正常圖裡面就沒有RGB分量為0的像素?其實,一幅圖中間歇的分布幾個RGB分量為0的像素,取反後,看上去是不那麼明顯的,所以我們認為這是成功的,只有那些“0”分量集中的部分,才嚴重破壞了圖的協調。
關於ColorMatrix原理上的揭秘就到此,由於我的文化理論水平有限,只能從技術角度來揭秘ColorMatrix的實現原理,說得不好,請原諒!
通過上面對ColorMatrix詳盡的技術解剖,我們推出了它的運算公式,有了這些,用程式碼實現ColorMatrix功能,應該很容易了,本文中篇將介紹ColorMatrix功能的具體實現,相信各位高手應該比我更在行。
如有不同看法和疑問,可寫在文章評論中,本人將儘可能予以解答。歡迎指導和建議,來信郵箱:maozefa@hotmail.com