1. 程式人生 > >Android 高階UI解密 (二) :Paint濾鏡 與 顏色過濾(矩陣變換)

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

注意: PorterDuffColorFilterComposeShader兩者都使用到了PorterDuff.Mode,其中一個是顏色過濾器,一個是著色器,而且PorterDuffColorFilter顏色過濾器只能指定一種顏色作為源,而不是一個 Bitmap。

文章小結

此篇文章的主要是研究Paint的兩個重點API:從Alpha濾鏡處理、顏色RGB的濾鏡處理兩個方面拓展開,其中涉及到了高數知識——矩陣運算,此篇為了研究顏色過濾原理稍作介紹,並實踐展示了幾個濾鏡效果,學會API實際運用。

此篇文章是有關於有關Paint的高階使用,結合上一篇Paint的基本使用,Paint相關知識重點暫時介紹到這裡,下一篇文章將開始歸納Canvas畫布相關內容。

(程式碼整理中,後續會提供)

若有錯誤,歡迎指教~