自定義控件繪制(Paint之ColorMatrix與濾鏡)篇六

內(nèi)容均來自

  1. https://blog.csdn.net/harvic880925/article/details/51187277

矩陣的概念

略,參考源博客

色彩矩陣

對于色彩的存儲,Bitmap類使用一個32位的數(shù)值來保存。紅、綠、藍及透明度各占8位,每一個色彩分量的取值范圍是0-255。透明度為0表示完全透明,為255時,色彩完全可見。

色彩的矩陣表示

四階矩形

一個色彩信息包含R、G、B、Alpha信息,所以,必然要使用一個4階色彩變換矩陣來修改色彩的每一個分量值(如果不想修改,對應(yīng)的數(shù)值給0):

圖片來自源博客

上圖中,每個顏色占據(jù)一行,即占據(jù)一個維度,除了本分量值外,其他為0;
矩陣計算時,每行中的各個數(shù)字與另一矩陣中的一列中的各個數(shù)字分別相乘,然后相加為新值;

注意:對于色彩變換矩陣,這里的色彩順序是R、G、B、A而不是A、R、G、B!
將色彩(0,255,0,255)更改為半透明時,可以使用下面的的矩陣運算來表示:

來自源博客(修改A所在行分量)

五階矩陣

使用四階矩陣完全可以改變圖片的RGBA值了,但考慮一種情況,如果我們只想在原有的R色上增加一些分量呢?
這時,我們就得再多加一階來表示平移變換。
一個既包含線性變換(乘法),又包含平移變換(加法)的組合變換,稱為仿射變換。
使用四階的色彩變換矩陣來修改色彩,只能夠?qū)ι实拿恳粋€分量值進行乘(除)運算,如果要對這些分量值進行加減法的運算(平移變換),只能通過五階矩陣來完成。

比如:
1、紅色分量值更改為原來的2倍(線性);
2、綠色分量增加100(平移);
則使用4階矩陣的乘法無法實現(xiàn),所以,應(yīng)該在四階色彩變換矩陣上增加一個“啞元坐標”,來實現(xiàn)所列的矩陣運算:

來自源博客

Android中的色彩矩陣

色彩變換矩陣的表示形式,是五階的,在默認情況下,色彩變換矩陣的形式:


copy from 源博客

Android中的色彩矩陣是用ColorMatrix類來表示的,使用如下:

// 生成色彩矩陣    
ColorMatrix colorMatrix = new ColorMatrix(new float[]{    
        1, 0, 0, 0, 0,    
        0, 1, 0, 0, 0,    
        0, 0, 1, 0, 0,    
        0, 0, 0, 0.5, 0,    
});    
mPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));    

示例代碼1(單個顏色通道輸出)

val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    setARGB(255, 200, 100, 100)
}

// 繪制原始位圖
canvas.drawRect(0f, 0f, width / 2.toFloat(), 600f, paint)
canvas.translate(width / 2.toFloat(), 0f)

// 生成色彩矩陣(把紅色和藍色都去掉,僅顯示綠色值)
val colorMatrix = ColorMatrix(floatArrayOf(
        0f, 0f, 0f, 0f, 0f,     // RED
        0f, 1f, 0f, 0f, 0f,     // GREEN
        0f, 0f, 0f, 0f, 0f,     // BLUE
        0f, 0f, 0f, 1f, 0f))    // ALPHA
paint.colorFilter = ColorMatrixColorFilter(colorMatrix)
canvas.drawRect(0f, 0f, width / 2.toFloat(), 600f, paint)
效果-過濾了其他顏色,保留綠色

示例代碼2(圖片多顏色單個綠色通道輸出)

private var bitmap: Bitmap
private var paint: Paint

init {
    bitmap = BitmapFactory.decodeResource(context.resources, R.mipmap.chrome)
    paint = Paint(Paint.ANTI_ALIAS_FLAG)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 繪制原始位圖
    canvas.drawBitmap(bitmap, null, Rect(0, 0, 250, 250 * bitmap.height / bitmap.width), paint)

    canvas.translate(0f, 260f)
    // 生成色彩矩陣(把紅色和藍色都去掉,僅顯示綠色值)
    val colorMatrix = ColorMatrix(floatArrayOf(
            0f, 0f, 0f, 0f, 0f,     // RED
            0f, 1f, 0f, 0f, 0f,     // GREEN
            0f, 0f, 0f, 0f, 0f,     // BLUE
            0f, 0f, 0f, 1f, 0f))    // ALPHA
    paint.colorFilter = ColorMatrixColorFilter(colorMatrix)
    canvas.drawBitmap(bitmap, null, Rect(0, 0, 250, 250 * bitmap.height / bitmap.width), paint)
}
效果圖

色彩的幾種運算方式

色彩的平移運算

有了上面的鋪墊,這個理解起來就簡單了;
色彩的平移運算,實際上就是色彩的加法運算。就是在色彩變換矩陣的最后一行加上某個值;這樣可以增加特定色彩的飽和度;

圖片來自源博客

示例:為上圖增大綠色飽和度

// 飽和度綠色
val colorMatrix = ColorMatrix(floatArrayOf(
                1f, 0f, 0f, 0f, 0f,     // RED
                0f, 1f, 0f, 0f, 50f,    // GREEN
                0f, 0f, 1f, 0f, 0f,     // BLUE
                0f, 0f, 0f, 1f, 0f))    // ALPHA
增加綠色飽和度

注意:由于圖片是由一個個像素組成的,所以用每個像素所對應(yīng)的色彩數(shù)組,來乘轉(zhuǎn)換矩陣,結(jié)果就是轉(zhuǎn)換后的當前點的顏色值;所以,在應(yīng)用ColorMatrics后,圖片中每個像素的綠色值都增加了50;

色彩平移除了增加指定顏色飽和度以外,另一個應(yīng)用就是色彩反轉(zhuǎn):

// 色彩反相
val colorMatrix = ColorMatrix(floatArrayOf(
        -1f, 0f, 0f, 0f, 255f,     // RED
        0f, -1f, 0f, 0f, 255f,    // GREEN
        0f, 0f, -1f, 0f, 255f,     // BLUE
        0f, 0f, 0f, 1f, 0f))    // ALPHA
反相對比

色彩的縮放

色彩的縮放運算其實就是色彩的乘法運算。在色彩矩陣對角線上的分別代表R、G、B、A的幾個值,將其分別乘以指定的值。這就是所謂的縮放變換。


圖片來自源博客

可以針對某一個顏色值進行放大縮小運算,但當對R、G、B、A同時進行放大縮小時,就是對亮度進行調(diào)節(jié)!

// 色彩縮放,(全部為增加亮度)
val colorMatrix = ColorMatrix(floatArrayOf(
        1.2f, 0f, 0f, 0f, 0f,     // RED
        0f, 1.2f, 0f, 0f, 0f,    // GREEN
        0f, 0f, 1.2f, 0f, 0f,     // BLUE
        0f, 0f, 0f, 1.2f, 0f))    // ALPHA
色彩縮放-亮度放大1.2倍

縮放變換的特殊應(yīng)用(通道輸出)

上面我們輸出過綠色通道;
由于在色彩變換矩陣中,對角線上的數(shù)的取值范圍是從0-1的,所以當取0時,這個色彩就完全不顯示,所以當我們R、G都取0,而獨有B取1時,就只顯示了藍色,所形成的圖像也就是我們通常說的藍色通道;

各顏色通道下的效果

色彩的旋轉(zhuǎn)運算

RGB色是如何旋轉(zhuǎn)的呢,首先用R、G、B三色建立立體坐標系:

圖片來自源博客

我們可以把一個色彩值看成三維空間里的一個點,色彩值的三個分量可以看成該點的坐標(三維坐標)。我們先不考慮,在三個維度綜合情況下是怎么旋轉(zhuǎn)的,我們先看看,在某個軸做為Z軸,在另兩個軸形成的平面上旋轉(zhuǎn)的情況,下圖分析了,在將藍色軸做為Z軸,僅在紅—綠平面上旋轉(zhuǎn)a度的情況:

圖片來自源博客

在圖中,我們可以看到,在旋轉(zhuǎn)后,原R在R軸的分量變?yōu)?原R*cosa(投影);
但原G分量在旋轉(zhuǎn)后,在R軸上也有了分量,但分量落在了負軸上,所以我們要減去這部分分量,所以最終的結(jié)果是最終的R=原R*cosa-原G*sina;

下面就看下關(guān)于幾種旋轉(zhuǎn)計算及結(jié)果矩陣,(注意:這幾個圖只標記了原X軸色彩分量的旋轉(zhuǎn),沒有把Y軸色彩分量的旋轉(zhuǎn)標記出來)

繞藍色軸旋轉(zhuǎn)a°

圖片來自源博客

對應(yīng)的色彩變換矩陣是:
圖片來自源博客

繞紅色軸旋轉(zhuǎn)a度

image.png

對應(yīng)的色彩變換矩陣是:
image.png

繞綠色軸旋轉(zhuǎn)a度

image.png

對應(yīng)的色彩變換矩陣是:
image.png

當圍繞紅色軸進行色彩旋轉(zhuǎn)時,由于當前紅色軸的色彩是不變的,而僅利用三角函數(shù)來動態(tài)的變更綠色和藍色的顏色值。這種改變就叫做色相調(diào)節(jié)!當圍繞紅色軸旋轉(zhuǎn)時,是對圖片就行紅色色相的調(diào)節(jié);同理,當圍繞藍色顏色軸旋轉(zhuǎn)時,就是對圖片就行藍色色相調(diào)節(jié);當然,當圍繞綠色軸旋轉(zhuǎn)時,就是對圖片進行綠色色相的調(diào)節(jié).

色彩的投射運算

色彩矩陣運算的公式:

源博客中的圖片

紅色運算給單獨拉了出來,紅色標記的那幾個元素a12,a13,a14,在運算中,是利用G、B、A的顏色值的分量來增加紅色值的。

比如:


源博客中的圖片

注意:最終結(jié)果的220=0.2*100+1*200,可見綠色分量在原有綠色分量的基礎(chǔ)上,增加了紅色分量值的0.2倍;利用其它色彩分量的倍數(shù)來更改自己色彩分量的值,這種運算就叫投射運算

下圖陰影部分;對這些值進行修改時,修改所使用的增加值來自于其它色彩分量的信息。

源博客中的圖片

色彩投射的應(yīng)用之 - 黑白照片

// 黑白
colorMatrix = ColorMatrix(floatArrayOf(
      0.213f, 0.715f, 0.072f, 0f, 0f,     // RED
      0.213f, 0.715f, 0.072f, 0f, 0f,     // GREEN
      0.213f, 0.715f, 0.072f, 0f, 0f,    // BLUE
      0f, 0f, 0f, 1f, 0f))    // ALPHA
黑白

彩色轉(zhuǎn)黑白原理:

  1. 只要把RGB三通道的色彩信息設(shè)置成一樣;即:R=G=B,那么圖像就變成了灰色;
  2. 為了保證圖像亮度不變,同一個通道中的R+G+B=1:如:0.213+0.715+0.072=1;

三個數(shù)字的由來:0.213, 0.715, 0.072;
按理說應(yīng)該把RGB平分,都是0.3333333,可以試試0.33,原因還是看源博客吧;

投射運算之色彩反色
利用色彩矩陣將兩個顏色反轉(zhuǎn),這種操作就叫做色彩反色 (如下:紅綠色反轉(zhuǎn))

colorMatrix = ColorMatrix(floatArrayOf(
        0f, 1f, 0f, 0f, 0f,     // RED, 由綠色替換
        1f, 0f, 0f, 0f, 0f,     // GREEN,由紅色替換
        0f, 0f, 1f, 0f, 0f,     // BLUE
        0f, 0f, 0f, 1f, 0f))    // ALPHA
紅綠色反轉(zhuǎn)-左為原圖

投射運算之顏色變舊

        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,1,0 
顏色變舊

ColorMatrix函數(shù)

Android中ColorMatrix自帶了一些函數(shù)來幫我們完成一些調(diào)整飽和度、色彩旋轉(zhuǎn)等操作;

ColorMatrix的構(gòu)造函數(shù):

ColorMatrix()  // new 一個新的
ColorMatrix(float[] src)   // 指定創(chuàng)建
ColorMatrix(ColorMatrix src)   // 以src為源copy 一個

setSaturation - 設(shè)置飽和度

通過色彩的平移運算,可單獨增加R、G、B的飽和度,但如果整體呢,就需要用到 setSaturation函數(shù)了;

// 整體增強顏色飽和度,即同時增強R,G,B的色彩飽和度  
public void setSaturation(float sat) 

參數(shù)float sat:表示把當前色彩飽和度放大的倍數(shù)。取值

  • 0表示完全無色彩,即灰度圖像(黑白圖像;
  • 1時,表示色彩不變動;
  • 大于1時,顯示色彩過度飽和,飽和度放大多少倍;
色彩飽和過渡效果

setScale - 色彩縮放

// 分別對應(yīng)R,G,B,A顏色值的縮放倍數(shù)
public void setScale(float rScale, float gScale, float bScale,float aScale)  

效果如下:


色彩縮放

setRotate-色彩旋轉(zhuǎn)

參考源博客,上面介紹了色彩旋轉(zhuǎn)的原理,涉及到正余弦函數(shù),同時Android封裝好了色彩旋轉(zhuǎn)的函數(shù):

/** 
 * 將旋轉(zhuǎn)圍繞某一個顏色軸旋轉(zhuǎn) degrees 度
 * axis=0 圍繞紅色軸旋轉(zhuǎn) 
 * axis=1 圍繞綠色軸旋轉(zhuǎn) 
 * axis=2 圍繞藍色軸旋轉(zhuǎn) 
 */  
public void setRotate(int axis, float degrees);  

示例代碼

seekBar {
      id = android.R.id.text1
      max = 360   // 整個旋轉(zhuǎn)度數(shù)的范圍是-180到180,360為一個周期
      progress = 180
      onSeekBarChangeListener {
              onProgressChanged { _, progress, _ ->
                      view7.colorMatrix.setRotate(0,progress-180*.10f)
                      view7.postInvalidate()
              }
     }
}.lparams(width = ViewGroup.LayoutParams.MATCH_PARENT)
色彩rotate

ColorMatrix相乘

涉及到矩陣相乘;ColorMatrix矩陣相乘涉及到3個方法:

// matA*matB,然后將結(jié)果做為當前ColorMatrix的值(覆蓋當前)
public void setConcat(ColorMatrix matA, ColorMatrix matB)  

// 就是將當前的矩陣乘以 prematrix
public void preConcat(ColorMatrix prematrix)

// postmatrix*當前矩陣,上面為前乘,這里為后乘
public void postConcat(ColorMatrix postmatrix)

矩陣的前乘 與 后乘 的結(jié)果是不一樣的;

setConcat()

下面兩個顏色矩陣相乘:


圖片來自源博客

因為矩陣相乘的原則是:第一個矩陣的列數(shù)需等于第2個矩陣的行數(shù),明顯這里不是這樣的,那如何相乘呢?
Android提供了一個方案,讓這兩個矩陣相乘,就是把第一個矩陣的最后一列單獨拿出來,另外加到結(jié)果上;


圖片來自源博客

參考源博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容