Android 高階UI解密 (二) :Paint濾鏡 與 顏色過濾(矩陣變換)
若是曾經檢視過系統UI的原始碼, 會發現其中使用了一些渲染效果,例如將圖片加上黑白、懷舊的效果,生活中常用的逆天美顏相機,其中的原理就是使用了濾鏡效果、顏色通道過濾。若還要深究其原理組成,便涉及到了高等數學裡的矩陣變換,也就是Android 中的顏色矩陣!此篇文章便來一探究竟如何實現濾鏡和其原理組成。
(關於矩陣這一塊,無需過度深究數學部分,此處為了充分理解渲染效果,只需瞭解大概原理,利用其API完成簡單濾鏡效果。)
其實濾鏡效果就是對影象進行一定的過濾加工處理。例如PS軟體中常見的濾鏡效果:模糊、銳化、素描等等,以上功能便涉及到濾鏡效果的矩陣。使用Paint設定濾鏡效果,可分為以下兩個方面:
- Alpha濾鏡處理
- 顏色RGB的濾鏡處理
以上兩個方面正好對應Paint的兩個重點API,分別是以下:
setMaskFilter(MaskFilter filter)
是基於整個畫面來進行過濾。setColorFilter(ColorFilter filter)
是對每個畫素的顏色進行過濾。
此篇文章分為以上兩個方面來詳細解析濾鏡、顏色過濾的奧祕。
一. Alpha濾鏡處理
Alpha就是對透明度的處理,涉及到MaskFilter這個類,它是一個抽象類:
MaskFilter是在繪製Alpha通道遮罩之前執行轉換的物件的基類。 MaskFilter
的子類可以通過Paint的setMaskFilter方法設定到畫筆中。 而模糊遮罩濾鏡BlurMaskFilter和浮雕遮罩濾鏡EmbossMaskFilter是實現MaskFilter的子類。
1. 模糊遮罩濾鏡BlurMaskFilter
見名識意,此濾鏡類似於一種模糊效果。以構造方法中指定的半徑模糊其邊緣,另外還可指定模糊的風格,模糊其內部、外部、邊框或者本身。
//構造方法
BlurMaskFilter (float radius, BlurMaskFilter.Blur style)
引數說明:
- radius:模糊區域半徑;
- style:模糊的格式 (BlurMaskFilter.Blur 型別)
- INNER:模糊內部邊框,外部不變;
- NORMAL:模糊內外邊框;
- OUTER:內部不變,模糊外部;
- SOLID:在邊界內部畫實體,模糊外面;
注意:注意以上四種類型的解釋差異,模糊內部和模糊內部邊框是不同的!
- 內外部邊框相關:INNER只是簡單模糊其內部邊框,圖片外部呈現淡白色;而NORMAL是直接模糊內外邊框,圖片外部已經呈現圖片邊緣的背景色;
- 內外部相關:OUTER效果比較奇葩,圖片內部空白,外部模糊成圖片邊緣的背景色;SOLID則是保持內部實體,外部模糊成圖片邊緣的背景色。
程式碼測試後的效果圖如下:
2. 浮雕遮罩濾鏡(EmbossMaskFilter)
EmbossMaskFilter(float[] direction, float ambient, float specular, float blurRadius)
引數說明:
- direction:指定光源方向的3個標量[x,y,z]的陣列;
- ambient:指定環境光量強度[0,1];
- specular:指定鏡面反射係數(例如8);
- blurRadius:指定模糊半徑(例如3);
mPaint.setMaskFilter(new EmbossMaskFilter(new float[]{400,100,100}, 0.5f, 60, 80));
canvas.drawBitmap(bitmap, 400, 100, mPaint);
注意:檢視EmbossMaskFilter類的構造方法原始碼,發現其真正建立物件是呼叫了native方法,因此這也表明google在android 的graphics包中準備了一系列的濾鏡,也需要傳入相應的引數,而其中引數的運算是非常複雜的,涉及到矩陣運算。
需要強調的是“矩陣運算”並非只是簡單的公式計算,試想一塊手機螢幕所含的畫素點有多少,假設是1080P,若一張圖片覆蓋整個螢幕,需要處理每一個畫素點,工作量是很大的,為了計算效率而採用了native方法,交由它來完成。
二. 顏色RGB的濾鏡處理
濾鏡的所有處理效果都是通過顏色矩陣的變換實現的,例如生活中常見的美顏相機,它實現的一些特效:高光、復古、黑白等濾鏡。那麼首先來了解何為矩陣?其中涉及到多階矩陣,這裡以二階矩陣為例進行講解。
1. 矩陣簡析
(1)定義
(2)矩陣乘法
矩陣其實就相當於一個二維陣列,而重點則在於矩陣之間的計算,特別是乘法計算與後續濾鏡計算有關,乘法運算如下:
矩陣的乘法計算步驟如下:
- 將第一個矩陣A的第一行,與第二個矩陣B的第一列的數字分別相乘,得到的結果相加,最終的值做為結果矩陣的第(1,1)位置的值(即第一行,第一列)。
- 同樣,A矩陣的第一行與B矩陣的第二列的數字分別相乘然後相加,結果做為結果矩陣第(1,2)位置的值(即第一行第二列)。
- 依次類推。
注意:矩陣A乘以矩陣B和矩陣B乘以矩陣A的結果是不一樣的。
示例如下:
2. 色彩資訊的矩陣表示
魯迅曾經說過(並沒有):矩陣運算對於Android畫素處理的意義極大!
以上在重點強調矩陣中的乘法運算後,接下來將解密其奧妙。顏色的組成為ARGB,這裡先不討論Alpha透明度,以RGB為主。舉個例子:美顏相機中的圖片美白原理就是將紅色、綠色、藍色進行位移,可以獲得不同的效果,而其中的計算則可以藉助矩陣完成。
(1)四階表示
ARGB的四階表示式如下:
如果想將色彩(0,255,0,255)更改為半透明時,可以使用下面的的矩陣運算來表示:
其實顏色變換就是將矩陣看成一套數學模型,便於計算ARGB值。
(2)五階矩陣
任何一個顏色都是三色素(紅綠藍)構成的,也就是RGB。例如黃色是由紅色和綠色形成。
考慮下面這兩個變換需求:
- 紅色分量值更改為原來的2倍;
- 綠色分量增加100;
若要實現以上變換,四階矩陣的乘法無法實現。根據以上ARGB四階矩陣的運算規則,只能進行乘法運算,而無法進行加法運算,因此在四階色彩變換矩陣上增加一個“啞元座標”,來實現所列的矩陣運算,也就是“五階矩陣”。過程如下圖:
第一個矩陣中前四列中任然代表ARGB,而第五列則是分量值,即綠色需要加的100,200 = 1*100+100
。
3. 例項
(1)需求
通過矩陣變換講一個圖片、顏色塊,過濾掉其中的紅色、綠色,只留下藍色。
(2)程式碼
檢視以下程式碼,繪製出以下兩個圖形進行對比:
- 第一個矩形設定其ARGB顏色,整體偏紅色;
- 重點是第二個矩形的顏色過濾器設定:建立其ColorMatrix 矩形變換物件,其中根據上部分公式講解,結合過濾紅色、綠色的需求。因此第一行第一列為R值為0,第二行第二列為G值為0,第三行第三列為B值為1,第四行第四列為A值為1,最後一列是分量值,皆為0。
//=====顏色RGB的濾鏡處理===
mPaint.setColor(Color.argb(255,200,100,100));
canvas.drawRect(200, 200, 400, 400, mPaint);
canvas.translate(400,0);
//五階矩陣,R、G為0,A、B為1,第五列為分量,不需要進行平移為0
ColorMatrix matrix = new ColorMatrix(new float[]{
0,0,0,0,0,
0,0,0,0,0,
0,0,1,0,0,
0,0,0,1,0,
});
//設定顏色過濾器
mPaint.setColorFilter(new ColorMatrixColorFilter(matrix));
canvas.drawRect(200, 200, 400, 400, mPaint);
(3)效果展示
根據以上對比圖實踐成功,將第一個矩形中的顏色(#C86464)過濾,僅留下藍色(#000064)。若純色塊對比不明顯,難以理解“過濾”的概念,直接使用圖片對比,將以上程式碼中的drawRect
改成drawBitmap
即可,效果如下:
注意:其濾鏡的原理還是在於設定的顏色過濾器——矩陣變換,同理可只過濾掉紅色、綠色、藍色或任意組合,都可由矩陣變換完成。其中不僅可以修改ARGB值(乘法),同樣可以修改五階矩陣中代表分量值的第五列(加法),不同的修改方式可以形成各式濾鏡效果。例如美圖秀秀中的各種濾鏡其原理是如此,內部包含大量的濾鏡模板(庫)。
4. 實踐濾鏡效果——色彩運算
以上簡單的舉個例子實踐了矩陣變換,下面來總結歸納其矩陣運算,無非是以下兩種:
- 色彩的平移運算(加法運算)
- 色彩的縮放運算(乘法運算)
以下程式碼實踐5種濾鏡效果來熟悉運用矩陣運算。
(1)反相效果 —— 曝光
常見的照相機中的曝光也就是矩陣運算中的反向,即設原先的ARGB值為100,200,250
,用最大值255減去原來的值,結果為155,55,5
,就是“曝光”。
矩陣運算解析:其餘程式碼同上個程式碼示例相同,這裡主要是矩陣運算方面的變化:反向效果涉及到用255減去原值,因此直接結合乘法與加法(分量值),可實現該結果!
程式碼示例如下:
//曝光效果
ColorMatrix matrix = new ColorMatrix(new float[]{
-1,0,0,0,255,
0,-1,0,0,255,
0,0,-1,0,255,
0,0,0,1,0,
});
圖片展示效果如下:
(2)美白效果 —– 顏色增強
矩陣運算解析:首先需要知道1f是影象原色,即不改變影象濾鏡。若要增強顏色達到一種美白的效果,只需要將RGB值稍加增大即可。
程式碼如下:
//美白效果
ColorMatrix matrix = new ColorMatrix(new float[]{
1.2f,0,0,0,0,
0,1.2f,0,0,0,
0,0,1.2f,0,0,
0,0,0,1.2f,0,
});
圖片展示效果如下:
(3)黑白效果
去色原理:只要把RGB三通道的色彩資訊設定成一樣,即R=G=B,那麼影象就變成了灰色,並且為了保證影象亮度不變,同一個通道中的 R+G+B=1。
例如 0.213+0.715+0.072 = 1
,三個數字是根據色彩光波頻率及色彩心理學計算出來的。(人對色彩的感知是融合色彩顯示和視覺心理成分的,例如你盯著一個純色快看一段時間,再看向別的事物,此時你看的事物都是自帶濾鏡的)
矩陣運算解析:根據以上三個值設定RGB即可得到黑白影象的效果,但是需要將五階矩陣中代表RGB的前四階中代表各列中每一行的值都設定,A無需考慮,最後以列分量也無需考慮。
程式碼如下:
//黑白圖片
ColorMatrix matrix = new ColorMatrix(new float[]{
0.213f, 0.715f,0.072f,0,0,
0.213f, 0.715f,0.072f,0,0,
0.213f, 0.715f,0.072f,0,0,
0, 0, 0, 1f,0,
});
圖片展示效果如下:
(4)色彩反射效果
何為反射效果?例如將影象中紅色的成分替換成綠色的成分,綠色的成分替換成紅色的。
//原始效果
ColorMatrix matrix = new ColorMatrix(new float[]{
1f,0,0,0,0,
0,1f,0,0,0,
0,0,1f,0,0,
0,0,0,1f,0,
});
矩陣運算解析:以上是影象原始效果,即ARGB皆為1F,即使設定了此顏色過濾,影象並無任何改變。與此對比,要實現色彩反射效果,比如紅色和綠色交換—-把第一行和第二行交換即可。
程式碼如下:
//髮色效果(比如紅色和綠色交換----把第一行和第二行交換)
ColorMatrix matrix = new ColorMatrix(new float[]{
0,1f,0,0,0,
1f,0,0,0,0,
0,0,1f,0,0,
0,0,0,1f,0,
});
圖片展示效果如下:
(5)復古效果
矩陣運算解析:這是美顏相機中常見的一款濾鏡形式,矩陣中有特定的演算法模板。
程式碼如下:
//復古風格
ColorMatrix matrix = new ColorMatrix(new float[]{
1/2f,1/2f,1/2f,0,0,
1/3f,1/3f,1/3f,0,0,
1/4f,1/4f,1/4f,0,0,
0,0,0,1f,0,
});
圖片展示效果如下:
三. ColorMatrix 相關詳解
上一點講解了多個有關矩陣變換的例子,對實踐矩陣運算和其產生的效果稍有理解,如果涉及到美容相機或者影象處理的一些效果需求,絕大可能會用到矩陣運算,而矩陣運算肯定涉及到ColorMatrix,此部分內容來詳細解析ColorMatrix。
ColorMatrix就是4x5矩陣,用於轉換點陣圖的RGB顏色和Alpha分量。 該矩陣可以作為單個數組傳遞,並按以下方式處理:
[ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t ]
當應用於顏色[R,G,B,A]時,每個值的範圍是[0, 255],生成的顏色計算如下:
R’ = a*R + b*G + c*B + d*A + e;
G’ = f*R + g*G + h*B + i*A + j;
B’ = k*R + l*G + m*B + n*A + o;
A’ = p*R + q*G + r*B + s*A + t;
1、構造方法
(1)用指定的值陣列建立一個新的Colormatrix
ColorMatrix matrix = new ColorMatrix(new float[]{});
程式碼示例:
ColorMatrix matrix = new ColorMatrix(new float[]{
1.2f,0,0,0,0,
0,1.2f,0,0,0,
0,0,1.2f,0,0,
0,0,0,1.2f,0,
});
(2)建立一個新的Colormatrix,後續設值
ColorMatrix matrix = new ColorMatrix();
float[] scr = {...};
matrix.set(src)
2.設定色彩的縮放函式(矩陣的乘法運算)
setScale(float rScale, float gScale, float bScale, float aScale)
API作用:設定此顏色矩陣按指定的值進行縮放。
引數:分別是設定R、G、B、A相乘的值。
注意:在上一部分的第四點中已經介紹過矩陣變換中兩種重要運算 —— 乘法和加法,並且在以上示例中都是直接修改 4*5 陣列矩陣。Colormatrix提供的此API可以輕易設定R、G、B、A需要相乘的值。另外追蹤其原始碼實現也很簡單,就是根據引數設定值與陣列中對應的R、G、B、A相乘。
3.設定飽和度(矩陣的加法運算)
setSaturation(float sat)
API作用:設定矩陣以影響顏色的飽和度。
引數: sat引數指對映到灰色的值。 1代表原色,0代表灰色,>1則增加飽和度。
API原始碼
public void setSaturation(float sat) {
reset();
float[] m = mArray;
final float invSat = 1 - sat;
final float R = 0.213f * invSat;
final float G = 0.715f * invSat;
final float B = 0.072f * invSat;
m[0] = R + sat; m[1] = G; m[2] = B;
m[5] = R; m[6] = G + sat; m[7] = B;
m[10] = R; m[11] = G; m[12] = B + sat;
}
原始碼剖析
有詳細檢視上一部分內容的讀者,你會發現原始碼中這個三個特殊值很熟悉,它就是在講解濾鏡中黑白效果中有提到的去色原理:R+G+B=1
從而圖片呈現灰色,同時考慮到色彩光波頻率及色彩心理學,計算得出最佳值RGB最佳值:0.213+0.715+0.072 = 1
。因此在此基礎之上,根據引數設定的值來修改RGB值,達到影象飽和度變化!
例項
下面實現一個簡單的demo,在onDraw
方法中設定影象的顏色過濾器,設定飽和度為0,在onTouchEvent
方法中監聽點選處理,每次觸控其飽和度以0.3f 增加,檢視影象變化效果:
(程式碼文末提供,在此不贅述)
效果分析
檢視以上效果符合預期情況,最初設定引數為0,因此影象呈現出灰色,隨著不斷點選,飽和度依次增加,即RGB值逐漸增加,顏色恢復成原色,接著點選,引數值大於1,影象明顯過飽和。
4.色彩旋轉函式
setRotate(int axis, float degrees)
API作用:通過指定的值設定顏色軸上的旋轉。
引數: axis代表旋轉軸,0紅色軸,1綠色,2藍色;degrees代表旋轉的度數。
注意: 類似於上圖中的3D效果,例如這裡指定圍繞B軸選裝,則B值不變,R、G值會隨之改變,而且一圈360度旋轉完會再次恢復到原始顏色影象。
例項
下面實現一個簡單的demo,在onDraw
方法中設定影象的顏色過濾器,設定圍繞R軸旋轉,在onTouchEvent
方法中監聽點選處理,每次觸控其旋轉度數以20f增加,檢視影象變化效果:
(程式碼文末提供,在此不贅述)
效果分析
檢視以上效果,根據設定是圍繞R軸旋轉,隨著度數增加,影象漸漸呈現紅色,最終又慢慢恢復成影象原色。既然將此顏色過濾器指定圍繞R軸,隨著旋轉角度增加,達到某一個臨界點,影象會逐漸過濾掉所有顏色,只剩下紅色。繼續增加,顏色接著改變,直至旋轉到360度恢復成原影象。
5. ColorFilter的子類
ColorFilter類:一個顏色過濾器可以和Paint一起使用來修改用這個顏料繪製的每個畫素的顏色。從它的名字也可知,為繪製設定顏色過濾。顏色過濾就是為繪製的內容設定統一的過濾規則。
Paint.setColorFilter(ColorFilter filter)
一般是通過Paint畫筆設定其顏色過濾器,由於ColorFilter是抽象類,使用的是它的三個子類,如下:
(1)ColorMatrixColorFilter 色彩矩陣的顏色顧慮器
類作用:通過4x5彩色矩陣轉換顏色的彩色濾鏡。 這個濾鏡可以用來改變畫素的飽和度,從YUV轉換到RGB等。
//建構函式
new ColorMatrixColorFilter(ColorMatrix matrix);
構造方法引數:就是**ColorMatrix**4x5矩陣,用於轉換點陣圖的RGB顏色和Alpha分量。
注意:本篇文章第二大部分顏色RGB的濾鏡處理,使用的都是ColorMatrixColorFilter 色彩矩陣的顏色顧慮器,在此無需贅述。
(2) LightingColorFilter 光照顏色過濾器(過濾顏色和增強色彩)
類作用:可用於模擬簡單照明效果的濾色器。 一個LightingColorFilter由兩個引數定義,一個用於將源顏色(稱為colorMultiply)和一個用於新增到源顏色(稱為colorAdd)的顏色相乘。 這個彩色濾光片保持不變的alpha通道。
給定源顏色RGB,由此得出R’G’B’顏色:
R' = R * colorMultiply.R + colorAdd.R
G' = G * colorMultiply.G + colorAdd.G
B' = B * colorMultiply.B + colorAdd.B
//建構函式
new LightingColorFilter(int mul, int add);
構造方法引數: mul是與源RGB相乘的值;add是與源RGB相加的分量值。
注意:此方法就是結合了矩陣運算中的乘法和加法,更加簡化。需要注意的是引數型別雖為int值,但規定為顏色值,即16進位制的值,例如0x00ff00,說白了就是範圍 [0,255]區間的顏色值。
例項
這裡做一個簡單的測試,設定LightingColorFilter 光照顏色過濾器的兩個引數分別為0x00ff00,0x000000。0x00ff00就是一個綠色值(原諒色~),會與RGB源值相乘,而這裡相加的值設定為0,不做修改。因此設定該光照顏色過濾器後,影象整體應該呈現出綠色。
......
mPaint.setColorFilter(new LightingColorFilter(0x00ff00, 0x000000));
canvas.drawBitmap(bitmap, null, new RectF(200, 200, 400, 400*bitmap.getHeight()/bitmap.getWidth()), mPaint);
效果如下:
效果分析
圖片效果與理想效果相符,因此根據此API可輕易完成矩陣變換中的乘法和加法運算,實現需求效果。
(3) PorterDuffColorFilter 圖形混合濾鏡(圖形學理論)
類作用:一種彩色濾光片,可用於使用單色和特定的Porter-Duff複合模式為源畫素著色。 就是使用一個指定的顏色和一種指定的 PorterDuff.Mode 來與繪製物件進行合成。
//建構函式
new PorterDuffColorFilter(int color, PorterDuff.Mode mode);
構造方法引數: color 引數是指定的顏色;mode 引數是指定的 Mode,即PorterDuff.Mode。
注意: PorterDuffColorFilter 和ComposeShader兩者都使用到了PorterDuff.Mode,其中一個是顏色過濾器,一個是著色器,而且PorterDuffColorFilter顏色過濾器只能指定一種顏色作為源,而不是一個 Bitmap。
文章小結
此篇文章的主要是研究Paint的兩個重點API:從Alpha濾鏡處理、顏色RGB的濾鏡處理兩個方面拓展開,其中涉及到了高數知識——矩陣運算,此篇為了研究顏色過濾原理稍作介紹,並實踐展示了幾個濾鏡效果,學會API實際運用。
此篇文章是有關於有關Paint的高階使用,結合上一篇Paint的基本使用,Paint相關知識重點暫時介紹到這裡,下一篇文章將開始歸納Canvas畫布相關內容。
(程式碼整理中,後續會提供)
若有錯誤,歡迎指教~