Android 自定義View學習(八)——Matrix知識學習

連接:https://www.jianshu.com/p/11e062284491html

 

Matrix主要用於對圖像的圖形處理。前面學習的ColorMatirx主要是圖像色彩的處理android

學習資料canvas

十分感謝 : )api

1.Martrix 變形矩陣

Matrix是一個3 * 3的矩陣,每一個像素點表達了其座標的X,Y信息數組

圖形矩陣變換ide

處理每一個像素點的計算方法函數

X1 = a * X + b * Y + c
Y1 = d * X + e * Y + f
L  = g * X + h * Y + i

通常,g = h = 0 , i = 1,這時L = g * X + h * Y + i 恆成立,也就是L = i = 1佈局

Matrix的初始化矩陣,對角線爲1,其他爲0post

Matrix初始化矩陣學習

Matrix主要能夠對圖像作4種基本變換

  • Translate 平移變換
  • Rotate 旋轉變換
  • Scale 縮放變換
  • Skew 錯切變換

Matrix類中的方法,主要也是和這四個變換相關,只是對計算過程作了封裝

做用對象是Bitmap而不是Canvas

2. Translate 平移變換

平移變換

紅點p1平移到白點p時,座標值

x = x1 + x0
y = y1 + y0

矩陣的形式:

平移變換矩陣

爲了更好直觀表現,先看原始效果

原始效果

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    //畫筆
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(Color.parseColor("#FF4081"));
    //矩陣
    matrix = new Matrix();
    matrix.setTranslate(100f,100f);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,0,0,null);  
}

在佈局文件中,控件的寬爲match_parent,高爲200dponDraw()方法中,canvas繪製底色爲黃色,又繪製了原始了的bitmapbitmap的大小是沒有控件大的,屏幕右側留下了一塊黃色的區域。此時並無用到matrix

2.1 setTranslate()方法

Matrix中提供了一個setTranslate()方法,很容易就作到平移

簡單修改代碼

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,matrix,null);
    //在(100,100)處畫一個圓  
    //用來輔助查看matrix做用後的座標系
    canvas.drawCircle(100,100,30,paint);
}

平移後

根據小圓能夠看出,matrix的平移對canvas的座標系不會形成影響,不像canvas.traslate()方法。

matrix.setTranslate(100f,100f)bitmapx,y軸上移動了100px

代入到平移的公式中:

平移100個像素

 

最終

x = x1 + 100
y = y1 + 100

而超出了canvas的部分,則再也不顯示

3. Rotate 旋轉變換

旋轉就是一個點圍繞一箇中心點旋轉到新的位置

以原點的爲旋轉中心過程學習:

Rotate旋轉圖示

白點p(x0,yo)繞原點旋轉β°後,獲得紅點p(x,y)

斜邊爲r,角度爲α,利用三角函數,獲得

x0 = r * cosα
y0 = r * sinα

同理,能夠得出

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β

過程其實就是三角函數展開,矩陣的形式就是

旋轉矩陣變換

根據計算結果y = y0 * cosβ + x0 * sinβ,須要注意sinβcosβ在矩陣的位置

上面的狀況是以原點爲旋轉中心,任意點O爲旋轉中心進行旋轉變換,通常有3個步驟:

  1. 將座標原點移動到任意點O
  2. 使用上面的以座標系原點爲中心的旋轉方法進行旋轉
  3. 將座標原點還原

主要就是考慮任意點與原點的座標錯。然而使用setRotate()方法時,並不用考慮過多,都進行了封裝

3.1 setRotate()方法

旋轉方法有兩個重載方法:

1. setRotate(float degrees)
2. setRotate(float degrees, float px, float py)

第一個方法簡單使用,簡單修改2.1中的代碼

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setRotate(15);
}

圍繞左上角旋轉15度

默認以左上角爲旋轉中心,bitmap的寬爲r進行旋轉

第2個方法能夠指定旋轉中心Ofloat px就是O點的X軸座標,float py就是O點的Y軸座標

matrix.setRotate(15,bitmap.getWidth()/2,bitmap.getHeight()/2);

以Bitmap中心爲旋轉中心

bitmap中心爲旋轉中心進行旋轉15度

4.Scale 縮放變換

對於一個像素點來講,不存在縮放的概念,但一個圖像是由不少個像素點組成,將每一個點的座標進行相同比例的縮放後,整個圖像也就有了縮放的效果。

計算公式:

x = K1 * x0
y = k1 * y0

矩陣形式:

矩陣縮放變換

k1 就是要縮放的比例,負值無效,bitmap會不顯示,0~1f縮小,k1 > 1 爲放大

4.1 setScale() 縮放方法

這個方法也有兩個重載方法

1. setScale(float sx, float sy)
2. setScale(float sx, float sy, float px, float py)

根據學習setRotate()方法,這個方法的兩個重載方法比較好理解

第1個方法,簡單使用

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setScale(0.5f,0.5f);
}

縮放二分之一

此時的縮放中心爲bitmap的坐上角

第2個方法,簡單使用

matrix.setScale(0.5f,0.5f,bitmap.getWidth()/2,bitmap.getHeight()/2);

以Bitmap中心縮放

此時就是以Bitmap的中心進行縮放,整個Bitmap的邊緣向中間靠攏

5. Skew 錯切變換

錯切變換skew是一種比較特殊的線性變換,分爲水平錯切和垂直錯切

5.1 水平錯切

水平錯切效果就是讓全部像素點的Y軸座標不變,X軸座標按照比例進行平移,且平移的大小與該點到Y軸的距離成成正比

在座標系中的效果:

水平錯切

計算公式:

x = x0 + k1 * y0
y = y0

矩陣形式:

矩陣水平錯切變換

X軸平移的值,是k1 * y0

5.2 垂直錯切

垂直錯切讓全部像素點的X軸座標不變,Y軸座標按照比例進行平移,且平移的大小與該點到X軸的距離成成正比

在座標系中的效果:

垂直錯切

計算公式:

x = x0 
y = y0+ k2 * x0

矩陣形式:

矩陣垂直錯切變換

5.3 兩個方向都進行錯切

當水平和垂直方向上都作錯切變換時

計算公式:

x = x0 + k1 * y0
y = k2 * x0 * y0

矩陣形式:

水平和垂直都錯切變換

不管水平還垂直錯切,最終的效果其實就是由矩形變做平行四邊形

5.3 setSkew() 錯切方法

這個方法也有兩個重載

1. setSkew(float kx, float ky)
2. setSkew(float kx, float ky, float px, float py)

第2個方法後面兩個參數也是爲了指定錯切的中心

5.3.1 setSkew(float kx, float ky)第一個方法

水平錯切:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setSkew(0.25f,0f);
}

水平錯切效果

kx就是k1,負值,向左切;正值向右切

垂直錯切:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setSkew(0f,0.25f);
}

垂直錯切效果

ky就是k2,負值,向上切;正值,向下切

bitmap最右邊的區域不是錯切的效果,是由於bitmap的寬沒有canvas的寬大,留下的空白區域

兩個方向都錯切:

matrix.setSkew(0.25f,0.25f);

兩個方向錯切

此時能夠明顯看出,錯切的中心點爲bitmap的左上角

5.3.2 setSkew(float kx, float ky, float px, float py) 指定錯切中心

簡單使用:

matrix.setSkew(0.1f,0.1f,bitmap.getWidth()/2,bitmap.getHeight()/2);

指定錯切中心點

bitmap的中心爲錯切中心點

這裏目前並非很理解指定中心點後錯切對座標系的影響

6.矩陣中的元素與四種變換效果的對應關係

矩陣

  • a和e 控制縮放變換
  • b和d 控制錯切變換
  • c和f 控制平移變換
  • a,b,d,e 共同控制旋轉變換

變化過濾

第1行都是影響的X軸,第2行影響的Y

7.關於前乘和後乘

首先,矩陣的乘法不知足乘法的交換規律

Matrix類中,set方法會重置矩陣中的全部值,而prepost不會

7.1 簡單對比

前乘的代碼:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    matrix.setTranslate(100,100);
    matrix.preRotate(15);
}

前乘效果

先進行平移,前乘旋轉15°

後乘,簡單修改代碼:

matrix.postRotate(15);

後乘效果

二者差異,看右下角的區域比較明顯

7.2 嘗試分析

前乘旋轉源碼

旋轉後乘源碼

前乘就是M * R(degrees)後乘就是R(degrees) * M
前乘就對應線性代數矩陣運算的右乘
後乘就對應線性代數矩陣運算的左乘

在矩陣運算中:
M右乘A,就是A * M
M左乘A,就是M * A

簡單記法:右乘從右邊乘進來,左乘從左邊乘進來

7.1中的矩陣:

矩陣分析

7.1中共有3個矩陣,首先平移b旋轉a像素c

在前乘或者後乘以前有一個setTranslate(100,100)設置的矩陣b,前乘後後乘也就是相對於b來講

7.1前乘,計算過程就是:
a右乘b,計算就是b * a,獲得一個新的矩陣NN * c

後乘的過程:
a左乘b,計算就是a * b,獲得一個新的矩陣NN * c

總結:
pre或者post方法前進行設置了哪一個矩陣M,矩陣MM以前全部的矩陣的運算獲得的新矩陣NN就看作當前矩陣,前乘或者後乘就是相對於這個當前矩陣N而言

7.3 補充 2016.09.30 09:09 <p>

根據總結,看下下面的兩個小練習:

//方式1
matrix = new Matrix();
matrix.preRotate(30);
matrix.postTranslate(100f, 100f);

//方式2
matrix = new Matrix();
matrix.postTranslate(100f, 100f);
matrix.preRotate(30);

//方式3
matrix = new Matrix();
matrix.postRotate(30);
matrix.preTranslate(100f, 100f);
  1. 方式1和方式2結果是否相同? 相同
  2. 方式1和方式3結果是否相同? 不一樣

有圖,有真相

3種方式的差異

在看問題前,先了解這樣一個矩陣的知識點,有助於理解問題:

有3個矩陣ABC,相乘,N = A * B * C
從左向右順序計算,第1步,X = A * B,而後N = X * C
從右向左倒序計算,第1步,X = B * C,而後N = A * X

這兩種計算方式是同樣的。

網上有人說,圖形處理時,矩陣的運算是從右向左計算的,這也就是爲啥有pre能夠理解爲先進行計算的一說,但我的感受,從左開始和從右開始計算是同樣的。但從右開始計算更容易理解吧

之因此說矩陣不知足乘法的交換規律,是說A * BB * A

N = A * B * C,從左開始計算和從右開始計算結果同樣的前提就是,要按照矩陣排列時的順序來進行計算

N = A * B,但N * CC * N

下面看問題

問題1:

  • 方式1的矩陣的形式:

方式1矩陣

  • 方式2的矩陣的形式:

方式2

new Matrix()或者mMatrix.reset()獲得的就是一個原始矩陣

兩個矩陣最終形式其實就是一個矩陣,差異能夠經過括號的位置理解,括號內的就是先計算的。方式1是能夠看做從右面開始計算,方式2能夠看作從左面開始計算。但前面說了,計算的順序並會不影響最終的結果

問題2:

方式3的矩陣形式與方式1,2不同:

方式3矩陣

方式3中與方式1,2的差異就是旋轉和平移兩個矩陣交換了位置,而矩陣不知足乘法的交換律,因此方式1和方式3,最終結果就不一樣

8.其餘的方法

Matrix中,方法主要4大類,佔據了絕大部分,setprepostmap開頭的方法

8.1 setPolyToPoly()

這個方法很是強大,經過改變參數,除了能夠實現平移,旋轉,縮放,錯切,還能夠實現透視

這個方法主要是利用肯定矩形4個頂點,根據4個頂點座標的變化來對bitmap進行變換

setPolyToPoly方法

最終的效果主要由srcdst兩個數組進行控制,兩個數組控制4個頂點的座標,srcIndex,dstIndex分別是srcdst的第一個值的角標,pointCount是4個頂點中要使用的個數,最大爲4,0表示不進行操做變換

透視效果,簡單使用:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
    matrix = new Matrix();
    float bWidth = bitmap.getWidth();
    float bHeight = bitmap.getHeight();
    float[] src = {0, 0, 0, bHeight, bWidth, bHeight,bWidth, 0};
    float[] dst = {0 + 150,0, 0, bHeight, bWidth, bHeight, bWidth - 150, 0};
    matrix.setPolyToPoly(src, 0, dst, 0, 4);
}
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,matrix,null);
}

透視效果

float[] srcfloat[] dst中的值必定是成對的出現,由於一個點的座標由(x,y)來肯定,兩兩一對控制對應的一個頂點的座標,最多有4對有效,超過的無效,由於再方法setPolyToPoly()中,最後一個參數不能超過4

數組值和頂點座標點的對應關係:

float[] dst = {f0, f1, f3, f3, f4, f5,f6, f7}

座標和頂點的對應關係

爲了方便看,將bitmap放在了畫布比較靠中心的位置

dst能夠看作是底板,最終要顯示的效果;
src能夠看作是要截取的bitmap的要顯示的有效區域

控制不一樣的點的效果:

  • 1個點,平移
  • 2個點,縮放或者旋轉
  • 3個點,錯切
  • 4個點,透視

8.2 setRectToRect()

setRectToRect(RectF src, RectF dst, ScaleToFit stf)

第一個參數src,截取資源Bitmap的顯示區域
第二個參數dst,底板,顯示的區域
第三個參數stf,模式

谷歌api 給的Demo:

ScaleToFit

FILL: 可能會變換矩形的長寬比,保證變換和目標矩陣長寬一致。
START:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。左上對齊。
CENTER: 保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。
END:保持座標變換前矩形的長寬比,並最大限度的填充變換後的矩形。至少有一邊和目標矩形重疊。右下對齊。

圖和文字說明從androidmatrix最全方法詳解與進階(完整篇)摘抄

簡單使用:

private void init() {
    bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
    matrix = new Matrix();
    int screenWidth  = getResources().getDisplayMetrics().widthPixels;
    int screenHeight = getResources().getDisplayMetrics().heightPixels;
    float bWidth = bitmap.getWidth();
    float bHeight = bitmap.getHeight();
    RectF src = new RectF(0,0,bWidth/2,bHeight/2 );
    RectF dst = new RectF(0,0,screenWidth,screenHeight);
    matrix.setRectToRect(src,dst, Matrix.ScaleToFit.END);
}
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.YELLOW);
    canvas.drawBitmap(bitmap,matrix,null);
}

Matrix.ScaleToFit.END

結合上面的圖,也比較容易理解

8.3 其餘的一些方法

方法名 做用
reset() 將矩陣恢復爲初始化矩陣
boolean invert(Matrix inverse) 反轉當前矩陣
boolean isIdentity() 是否爲初始化矩陣
boolean isAffine() 是否爲仿射矩陣
boolean rectStaysRect() 判斷該矩陣是否能夠將一個矩形依然變換爲一個矩形。當矩陣是單位矩陣,或者只進行平移,縮放,以及旋轉90度的倍數的時候,返回true

仿射變換其實就是二維座標到二維座標的線性變換,保持二維圖形的「平直性」(即變換後直線仍是直線不會打彎,圓弧仍是圓弧)和「平行性」(指保持二維圖形間的相對位置關係不變,平行線仍是平行線,而直線上點的位置順序不變),能夠經過一系列的原子變換的複合來實現,原子變換就包括:平移、縮放、翻轉、旋轉和錯切。這裏除了透視能夠改變z軸之外,其餘的變換基本都是上述的原子變換,因此,只要最後一行是0,0,1則是仿射矩陣。

其餘的方法之後用到了再學習補充

9. 最後

本篇主要就學習4種基本變換操做的方法

本人很菜,有錯誤請指出

感謝學習資料中的大神前輩們

共勉 : )

做者:英勇青銅5 連接:https://www.jianshu.com/p/11e062284491 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。

相關文章
相關標籤/搜索