Android Matrix 帶你掌控雷電

Matrix 是什么

Matrix 擁有一個(gè) 3 * 3 矩陣,這個(gè)矩陣用于坐標(biāo)變換。這個(gè)矩陣定義如下

Matrix.PNG

Matrix 有個(gè)方法 isAffine(),判斷矩陣是否為仿射矩陣。那么是什么仿射矩陣呢?下面一段話來自百度百科

仿射變換:它是一種二維坐標(biāo)到二維坐標(biāo)之間的線性變換,保持二維圖形的“平直性”(變換后,直線還是直線,圓弧還是圓弧)、“平行性”(保持二維圖形間的相對位置不變,平等線還是平等線,但是向量夾角可以發(fā)生變化)。仿射變換可以通過一系列的原子變換的復(fù)合來實(shí)現(xiàn),包括平移(Translate)、縮放(Scale)、翻轉(zhuǎn)(Flip),旋轉(zhuǎn)(Rotation)和錯(cuò)切(Skew)。此類變換可以用一個(gè) 3*3的矩陣來表示,其最后一行為(0,0,1)。

Matrix 操作的仿射變換操作有平移(Translate),縮放(Scale),旋轉(zhuǎn)(Rotate),錯(cuò)切(Skew),所以這些操作對應(yīng)的矩陣最后 一行永遠(yuǎn)是(0,0,1)。

當(dāng)然 Matrix 并不只是操作仿射變換,它還可以操作透視變換(Perspective),也就是矩陣最后一行操作的就是透視變換 。所以仿射變換其實(shí)就是透視變換的一種特殊情況。Matrix.setPolyToPoly() 提供了透視變換的操作,但是畢竟不是專業(yè)處理圖像的,所以只能帶大家入個(gè)門吧。

下面先從數(shù)學(xué)角度推導(dǎo)下 Matrix 操作的四種彷射變換對應(yīng)的矩陣是什么。

平移的矩陣

一個(gè)點(diǎn) P 平移到 P' ,在坐標(biāo)系表示如下


Translate.PNG

用數(shù)學(xué)公式表示

Translate_Math.PNG

轉(zhuǎn)換為坐標(biāo)系表示

Translate_Matrix1.PNG

中間的3 * 3 矩陣就是平移對應(yīng)的 Matix , ?x 就是 x 軸的增量,?y 就是 y 軸的增量。

旋轉(zhuǎn)矩陣

旋轉(zhuǎn)是有中心點(diǎn)的,如果把 Matrix 應(yīng)用到圖片,默認(rèn)旋轉(zhuǎn)點(diǎn)是圖片的左上角,也就是圖片的坐標(biāo)原點(diǎn)(0,0)。

繞坐標(biāo)原點(diǎn)旋轉(zhuǎn)矩陣

Rotate_XY.PNG

用數(shù)學(xué)公式表示

Rotate_Math.PNG

用矩陣表示

Rotate_Matrix1.PNG

中間的 3 * 3 矩陣就是繞坐標(biāo)原點(diǎn)旋轉(zhuǎn)的 Matrix。

繞非坐標(biāo)原點(diǎn)旋轉(zhuǎn)矩陣

如果圖片并非繞它的坐標(biāo)原點(diǎn)(左上角)旋轉(zhuǎn),而是繞任意點(diǎn)旋轉(zhuǎn),例如 圖片中心點(diǎn) (x1, y1),那么這個(gè)又如何計(jì)算呢?

這里我們先用數(shù)學(xué)思維思考,我們可以把(x1,y1) 作為坐標(biāo)原點(diǎn),那么 P 的表示應(yīng)該為 (x0-x1, y0-y1),P' 的坐標(biāo)為 (x-x1, y-y1),那么我們再應(yīng)用旋轉(zhuǎn)點(diǎn)是坐標(biāo)原點(diǎn)的矩陣公式,就應(yīng)該這樣寫

Rotate_Matrix1.PNG

再簡化下

Rotate_Matrix2.PNG

是不是現(xiàn)在感覺比較明朗,等式右邊第一個(gè)和第三個(gè)矩陣就是平移矩陣,我們在后面會(huì)看到如何生成這三個(gè)矩陣的結(jié)果。

我們再來理解下這個(gè)矩陣,最后兩個(gè)矩陣相乘代表坐標(biāo)原點(diǎn)移動(dòng)到(x1,y1),最后三個(gè)矩陣代表以(x1, y1)為原點(diǎn)旋轉(zhuǎn),最后四個(gè)矩陣代表坐標(biāo)原點(diǎn)從(x1, y1)移動(dòng)到(0,0),這樣是不是好記一些。

縮放矩陣

圖片都是由像素點(diǎn)構(gòu)成的,如果一個(gè)圖片放大 k 倍,可以看作每個(gè)點(diǎn)的 x 和 y 坐標(biāo)值放大 k 倍。當(dāng)然縮放也有中心點(diǎn)的。圖片默認(rèn)的縮放中心為圖片的左上角,也就是圖片的原點(diǎn)(0,0)。

原點(diǎn)為中心的縮放

用數(shù)學(xué)表示如下

Scale_Math.PNG

用矩陣表示如下

Scale_Matrix.PNG

中間的矩陣就是繞原點(diǎn)綻放的 Matrix

非原點(diǎn)的縮放

這與非原點(diǎn)的旋轉(zhuǎn)的數(shù)學(xué)思維是一樣的,這里直接給出矩陣

Scale_Matrix1.PNG

中間的三個(gè)矩陣相乘就是非原點(diǎn)縮放 Matrix

錯(cuò)切矩陣

錯(cuò)切分為 x 軸和 y 軸的錯(cuò)切。

x 軸錯(cuò)切矩陣

Skew_X.PNG

x 軸錯(cuò)切,是保持坐標(biāo)的 y 軸值不變,x 軸值的做線性變換 ,表示如下

Skew_Math.PNG

斜率為 1/k

矩陣表示如下

Skew_MatrixX.PNG

矩陣表示用到的是 k,而不是斜率 1 / k,因此 k 越大,圖形錯(cuò)切的越大。

y 軸錯(cuò)切矩陣

Skew_Y.PNG

y軸錯(cuò)切,就是 x 軸的值不變,y 軸的值做線性變換,矩陣就不用我再推理吧,表示如下

Skew_MatrixY.PNG

x 軸 y 軸的錯(cuò)切矩陣

綜合 x 軸 和 y 軸錯(cuò)切,統(tǒng)一表示如下

Skew_MatrixXY.PNG

kx 表示 x 軸的錯(cuò)切值,ky 表示 y 軸的錯(cuò)切值。kx,ky 越大,圖形錯(cuò)切的越大。


看完了可惡的數(shù)學(xué)公式,我們就懂得了原理 ,現(xiàn)在用 Matrix 的 API 來測試測試吧。

每種變換都有 setXx() ,postXx(),preXx() 方法來設(shè)置相應(yīng)的變換 。如 Translate,有 Matrix.setTranslate(),Matirx.postTranslate(),Matrix.preTranslate()。我將會(huì)在代碼中解釋這些到底怎么用,請大家多注意,因?yàn)榇蟛糠秩藭?huì)用錯(cuò)。

默認(rèn)顯示一個(gè) launcher 圖標(biāo)

我們先顯示一個(gè)不做 Matrix 處理的圖標(biāo)

/**
 * Created by David Chow on 2016/12/6.
 */

public class MatrixView extends View {

    private Matrix mMatrix;
    private Bitmap mBitmap;

    public MatrixView(Context context) {
        this(context, null);
    }

    public MatrixView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
        mMatrix = new Matrix();
        Log.d("david", mMatrix.toString());
    }


    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }
}
Default.PNG

我們看看這個(gè)初始的 Matrix 矩陣是什么

Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 0.0][0.0, 0.0, 1.0]}

這就是一個(gè) 3 * 3 單位矩陣 ,因此它不論前乘還是后乘都無所謂。

平移

    @Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", "translate matrix : " + mMatrix.toString());
    }
Translate.PNG

我們看到了圖像平移了,這個(gè)時(shí)候,我們打印 Log,可以看到矩陣的值為

translate matrix : Matrix{[1.0, 0.0, 200.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}

我們可以看到 Matrix.setTranslate() 把單位矩陣重置為 平移的矩陣(上面數(shù)學(xué)分析得出的平移矩陣)

那么用理論的的矩陣這樣表示


Translate.PNG

這樣與我們上面理論是不是就一致了,這個(gè)時(shí)候我們心情是不是稍微好點(diǎn)了,因?yàn)槲覀兝碚摻K于在實(shí)際中得到驗(yàn)證了。后面我們將不再去這個(gè)用矩陣來驗(yàn)證理論,我們只打印相應(yīng)的 Log 即可。

縮放

    @Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        mMatrix.setScale(3, 3);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

Log打印如下

 Matrix{[3.0, 0.0, 0.0][0.0, 3.0, 0.0][0.0, 0.0, 1.0]}

就算我們先用了一個(gè)無關(guān)的 Matrix.setTranslate() , 后面的 Matrix.setScale() 還是會(huì)重置矩陣為縮放的矩陣。這點(diǎn)大家要記住,免得以后沒有達(dá)到效果,卻不知道問題在哪里了。

Scale.PNG

默認(rèn)的縮放中心是圖片的左上角,也就是(0,0),當(dāng)然我們也可以調(diào)整縮放中心位置

    @Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setScale(3, 3,mBitmap.getWidth()/2,mBitmap.getHeight()/2);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

現(xiàn)在縮放中心的位置為 Bitmap 的中心位置,現(xiàn)在看下效果

Scale1.PNG

從效果看,確實(shí)是根據(jù)中心點(diǎn)縮放,以致圖片的左上角超出了屏幕顯示。
我們打印Log看下

Matrix{[3.0, 0.0, -126.0][0.0, 3.0, -126.0][0.0, 0.0, 1.0]}

那么與我們上面理論分析是不是相符,就留給大家去驗(yàn)證了。

Matrix.setRotate()旋轉(zhuǎn)

旋轉(zhuǎn)也是有中心點(diǎn)的,先看看個(gè)繞圖片中心旋轉(zhuǎn)的情況

    @Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setRotate(45, mBitmap.getWidth() / 2, mBitmap.getHeight() / 2);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

Log打印如下

Matrix{[0.70710677, -0.70710677, 63.0][0.70710677, 0.70710677, -26.095451][0.0, 0.0, 1.0]}

驗(yàn)證理論還是交給大家了~~

Rotate_Default.png

Matrix.setSinCos() 也是設(shè)置旋轉(zhuǎn)的,只是它不是設(shè)置角度,而是設(shè)置角度的 sin 和 cos 值。

當(dāng)然默認(rèn)的旋轉(zhuǎn)中心也是圖片的左上角

    @Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        mMatrix.setScale(3, 3);
        mMatrix.setRotate(30);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

同樣,setRotate() 覆蓋了前面的 setScale(), setTranslate()

打印下矩陣信息

 Matrix{[0.8660254, -0.5, 0.0][0.5, 0.8660254, 0.0][0.0, 0.0, 1.0]}

根據(jù)我們上面講的,這個(gè)里面的小數(shù)值 ,對應(yīng)角度為 30 的 sin 或者 cos 值,大家可以自己用計(jì)算器算算~~

現(xiàn)在看下效果圖


Rotate.PNG

我們發(fā)現(xiàn)這個(gè)移出了屏幕了,因此現(xiàn)在我們來平移下

    @Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200); // 單位 pixel
        mMatrix.setScale(3, 3);
        mMatrix.setRotate(30);
        mMatrix.preTranslate(200, 200);
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }
Rotate_Translate_Error.PNG

一個(gè)奇怪的現(xiàn)象就產(chǎn)生了,X 方向的平移呢?其實(shí)這是因?yàn)榫仃嚦朔ú粷M足交換律的原因。mMatrix.preTranslate() 意思是用 mMatrix 矩陣前乘 translate 矩陣,數(shù)據(jù)表示如下

Rotate_Translate.png

這個(gè)結(jié)果就不用我算了吧,很顯然實(shí)際偏移的并不是200,200。那么我們?nèi)绾巫屗刃D(zhuǎn)30°,又正常偏移200,200呢。 我們可以讓 mMatrix 后乘 translate

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawLine(0, 0, 200, 200, mPaint);
        mMatrix.setRotate(30);
        mMatrix.postTranslate(200, 200);
        canvas.drawBitmap(mBitmap, mMatrix, null);
    }

為了看到我們確實(shí)是偏移了200,200,我畫了一條紅色的線

Rotate_PostTranslate.png

那么我們用數(shù)學(xué)來表達(dá)下

Post_Translate.png

大家計(jì)算下,是不是既旋轉(zhuǎn)了又平移了200,200?

我用這個(gè)例子是為了讓大家理解 preXx() 和 postXx() 的區(qū)別,希望大家自己動(dòng)手試試加深理解。
再給大家一個(gè) Tip,如果想要平移達(dá)到效果,最后調(diào)用 postTranslate()。

Matrix.setSkew() 錯(cuò)切

先用默認(rèn)中心點(diǎn),即圖片的左上角進(jìn)行錯(cuò)切。為了看到效果,我們先平移200,200,再進(jìn)行錯(cuò)切變換

mMatrix.setTranslate(200, 200);
mMatrix.preSkew(1, 0);
canvas.drawBitmap(mBitmap, mMatrix, null);
Skew.png

這里我并沒有遵循上面的例子最后用 postTranslate(),這是因?yàn)楦鶕?jù)矩陣的特性,這樣剛好不影響平移,我們可以打印Log看看矩陣

Matrix{[1.0, 1.0, 200.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}

我沒說錯(cuò)吧,但是如果你再操作下,就有問題了,例如例如 postScale(3,3),就會(huì)平移 600,600了。當(dāng)然如果你最后還是調(diào)用 postTranslate(200,200) ,會(huì)正常平移400,400。

大家別看我說的很簡單,自動(dòng)寫的時(shí)候就會(huì)遇到各種問題,因此大家在看的時(shí)候還是多動(dòng)手。

再看看中心點(diǎn)不是原點(diǎn)的錯(cuò)切,例如用圖片的左下角來錯(cuò)切。

    @Override
    protected void onDraw(Canvas canvas) {
        mMatrix.setTranslate(200, 200);
        mMatrix.preSkew(1, 0, 0, mBitmap.getHeight());
        canvas.drawBitmap(mBitmap, mMatrix, null);
        Log.d("david", mMatrix.toString());
    }

打印Log

Matrix{[1.0, 1.0, 74.0][0.0, 1.0, 200.0][0.0, 0.0, 1.0]}

理論驗(yàn)證繼續(xù)留給大家了~~

思考

我們使用 Matrix API 的時(shí)候,我們只是一名 Developer,但是我們要把自己當(dāng)做一個(gè) Designer,我們思考下,其實(shí)我們可以利用矩陣做很多想要的效果,因?yàn)槲覀兛梢杂?Matrix.setValues() 來設(shè)置自己想要的矩陣 ,因此我們可以設(shè)計(jì)對稱效果,倒影效果等等,這就要大家去發(fā)掘啦~~

結(jié)束

這篇文章大家入門 Matrix,當(dāng)然這是為了我后面文章打基礎(chǔ)的,還是那句話,多動(dòng)手,如果遇到問題解決不了,歡迎大家留言討論。 如果大家覺得還不錯(cuò),可以點(diǎn)個(gè)贊,甚至來一波關(guān)注不惜留戀_, what's a nice day~~

參考文章
http://blog.csdn.net/cquwentao/article/details/51445269
http://baike.baidu.com/link?url=D4AHXGFs4yjlXg74jY1xU5shk1z--hyAe28ynSWaadyI0IrVQSYh6ueJpgpHbJwk8mGdrWEscclauzzMo81vv6qb__77JQdpSmgcgF6S1HnYJDHDdEGRquy3sCYf7UPa
http://baike.baidu.com/link?url=sqH8yi74VwnyBq3Uhr_tn6pCF9lHNZWmRF0j_4hc3gLy45vPBFM9eGnN3BrpUou8jiPaXtDxg_B7WvNGEXep8UuQr7q8tHk8DTWv34FcKKK-1iQy5W7-2IBPGxsab5Wr

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評(píng)論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,417評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評(píng)論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,267評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,471評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,698評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評(píng)論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,204評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,436評(píng)論 2 378

推薦閱讀更多精彩內(nèi)容