繼上一篇《Android開發(fā)之圖像處理那點事——濾鏡》,這次我們來講一下圖片的變換操作,本篇主要是介紹常見的圖像變換4種操作(平移、縮放、旋轉(zhuǎn)、錯切)
對于圖像的顏色處理,Andriod系統(tǒng)給我們提供一個很方便的顏色矩陣類ColorMatrix,同樣的,Android系統(tǒng)也給圖像的變換處理提供了矩陣類Matrix,相比顏色矩陣的4 * 5,變換矩陣來得更加簡單,它是由一個3 * 3的數(shù)字矩陣組成,我們來了解一下變換原理。
變換矩陣
在上一篇文章中,我們講到像素點的顏色是由RGBA1組成的,分別代表紅、綠、藍、透明度各通道值還有顏色偏移量,在變換矩陣中也是類似的,像素點的位置是由矩陣C(X、Y、L)組成的,分別代表像素點在X、Y軸的位置還有偏移量。

根據(jù)矩陣的乘法,我們可以得出下面的等式:
X1 = a * X + b * Y + c
Y1 = d * X + e * Y + f
L = g * X + h * Y + i
當(dāng)a=1,b和c=0,可以知道X1=1X+0+0=1X,
當(dāng)e=1,d和f=0,可以知道Y1=0+1Y+0=1Y
當(dāng)i=1,g和h=0,可以知道1=0+0+1=1
由此我們可以得到變換矩陣的單位矩陣:

在日常開發(fā)中,我們涉及到的圖像變換大致有平移、縮放、旋轉(zhuǎn)、錯切(很少),這些操作就是對這3 * 3的矩陣做數(shù)值上的調(diào)整,和上一篇講顏色矩陣變化是類似的,不清楚的朋友,具體可以參考上一篇文章的講解,下面我們快速過一下流程。
平移操作
像素點的平移其實就是對像素點的x和y坐標(biāo)進行移動,如下圖,從點p1移動到點p,假設(shè)p1(x1,y1)分別平移了x0和y0距離得到p(x,y),

得到的公式就是:
x = x1 + x0
y = y1 + y0
矩陣所表現(xiàn)出來的形式:

縮放操作
對于單個像素點是不存在縮放操作的,但如果是一系列的像素點,我們就可以根據(jù)x和y軸做一定比例的縮放,假設(shè)縮放比例為K1,得到的公式是:
x = K1 * x0
y = k1 * y0
矩陣所表現(xiàn)出來的形式:

旋轉(zhuǎn)操作
像素點的旋轉(zhuǎn)是圍繞一個點旋轉(zhuǎn)一定的角度得到新的像素點位置,為了便于理解,這里帶上一張圖:

如上圖以原點為中心旋轉(zhuǎn),點(x0,y0)旋轉(zhuǎn)了β°角度到了p(x,y),假設(shè)斜邊為r,根據(jù)三角函數(shù)我們可以知道:
x0 = r * cosα
y0 = r * sinα
那么x和y就是x0和y0旋轉(zhuǎn)了α角度后,再旋轉(zhuǎn)了β角度,根據(jù)三角函數(shù)可以推出:
x = r * cos(α + β) = r * cosα * cosβ - r * sinα * sinβ = x0 * cosβ - y0 * sinβ
y = r * sin(α + β) = r * sinα * cosβ + r * cosα * sinβ = y0 * cosβ + x0 * sinβ
將上面的式子代入,根據(jù)推導(dǎo)公式,我們可以知道矩陣的表現(xiàn)形式為:

錯切操作
這個平時用的比較少,大概了解一下就可以,錯切分為水平錯切和垂直錯切。
水平錯切效果就是讓所有像素點的Y軸坐標(biāo)不變,X軸坐標(biāo)按照比例進行平移,且平移的大小與該點到Y(jié)軸的距離成成正比,計算公式為:
x = x0 + k1 * y0
y = y0
矩陣變現(xiàn)形式為:

垂直錯切讓所有像素點的X軸坐標(biāo)不變,Y軸坐標(biāo)按照比例進行平移,且平移的大小與該點到X軸的距離成成正比,計算公式為:
x = x0
y = y0+ k2 * x0
矩陣變現(xiàn)形式為:

兩個方向都錯切,公式為:
x = x0 + k1 * y0
y = k2 * x0 * y0

根據(jù)以上的矩陣表現(xiàn)方式,我們可以推出圖像的變換規(guī)律為:

Matrix
了解了各種變換原理后,我們來看下Matrix類,這是Android系統(tǒng)給我們提供的圖像變換矩陣類,打開源碼,我們可以看到這9個常量:
public static final int MSCALE_X = 0; //!< use with getValues/setValues
public static final int MSKEW_X = 1; //!< use with getValues/setValues
public static final int MTRANS_X = 2; //!< use with getValues/setValues
public static final int MSKEW_Y = 3; //!< use with getValues/setValues
public static final int MSCALE_Y = 4; //!< use with getValues/setValues
public static final int MTRANS_Y = 5; //!< use with getValues/setValues
public static final int MPERSP_0 = 6; //!< use with getValues/setValues
public static final int MPERSP_1 = 7; //!< use with getValues/setValues
public static final int MPERSP_2 = 8; //!< use with getValues/setValues
我們把它按3 * 3的矩陣形式排開,可以得到:

對比下剛才上面我們推導(dǎo)的矩陣表現(xiàn)形式,怎么樣,是不是更有感覺了,我們可以得到如下信息:
MTRANS_X、MTRANS_Y 決定了平移(Translate)
MSCALE_X、MSCALE_Y 決定了縮放( Scale)
MSCALE_X、MSKEW_X、MSCALE_Y、MSKEW_Y 決定了旋轉(zhuǎn)( Rotate)
MSKEW_X、MSKEW_Y 決定了錯切( Skew)
其實圖像的變換操作無非就是對這個3 * 3的矩陣進行值的變化,而Matrix類只是幫我們封裝好了這些操作,讓我們無需關(guān)心細節(jié),更加專注業(yè)務(wù)的實現(xiàn),我們可以在Matrix中找到各操作對應(yīng)的調(diào)用方法:

簡單的看個小Demo:

上面分別對圖像進行了平移、縮放、旋轉(zhuǎn)、錯切操作,貼一下核心代碼:
Matrix matrix = new Matrix();
matrix.setTranslate(50, 50);//x,y坐標(biāo)平移50像素
canvas.drawBitmap(mBitmap, matrix, null);
Matrix matrix = new Matrix();
matrix.setScale(2, 2);//x,y坐標(biāo)放大原來2倍
canvas.drawBitmap(mBitmap, matrix, null);
Matrix matrix = new Matrix();
matrix.setRotate(20);//x,y坐標(biāo)旋轉(zhuǎn)20度
canvas.drawBitmap(mBitmap, matrix, null);
Matrix matrix = new Matrix();
matrix.setSkew(2, 2);//x,y坐標(biāo)按比例錯切平移2
canvas.drawBitmap(mBitmap, matrix, null);
當(dāng)然這些操作也是可以組合起來的,Matrix里提供了一系列的postXXX,preXXX方法:

其實就是線性代數(shù)中矩陣的左乘和右乘,由于矩陣的乘法是不滿足乘法交換律的,所以變換操作的執(zhí)行順序是對結(jié)果有影響的,繼續(xù)來個小Demo:

實現(xiàn)代碼:
Matrix matrix = new Matrix();
matrix.setTranslate(200, 200);
matrix.postScale(2, 2);
matrix.postRotate(20);
canvas.drawBitmap(mBitmap, matrix, null);

實現(xiàn)代碼:
Matrix matrix = new Matrix();
matrix.setTranslate(200, 200);
matrix.preScale(2, 2);
matrix.preRotate(20);
canvas.drawBitmap(mBitmap, matrix, null);
簡單粗暴可以這樣理解:Matrix操作是一系列的任務(wù)存放在一個隊列里,pre是把當(dāng)前任務(wù)插入隊列前,post是插入隊列后,set則是清空隊列所有任務(wù),再入隊。
這些看似很簡單很基礎(chǔ)的東西卻恰恰是最重要的,它可以創(chuàng)造出很多東西,比如圖片的自由手勢縮放,微博上面的自定義貼紙,美圖秀秀里的相框拼接等效果都離不開它。
源碼下載:
這里附上源碼地址(歡迎Star,歡迎Fork):BeautyImageDemo