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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市翰苫,隨后出現(xiàn)的幾起案子止邮,更是在濱河造成了極大的恐慌,老刑警劉巖奏窑,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件导披,死亡現(xiàn)場離奇詭異,居然都是意外死亡埃唯,警方通過查閱死者的電腦和手機(jī)撩匕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筑凫,“玉大人滑沧,你說我怎么就攤上這事∥∈担” “怎么了滓技?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長棚潦。 經(jīng)常有香客問我令漂,道長,這世上最難降的妖魔是什么丸边? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任叠必,我火速辦了婚禮,結(jié)果婚禮上妹窖,老公的妹妹穿的比我還像新娘纬朝。我一直安慰自己,他們只是感情好骄呼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布共苛。 她就那樣靜靜地躺著判没,像睡著了一般。 火紅的嫁衣襯著肌膚如雪隅茎。 梳的紋絲不亂的頭發(fā)上澄峰,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機(jī)與錄音辟犀,去河邊找鬼俏竞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛堂竟,可吹牛的內(nèi)容都是我干的魂毁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼出嘹,長吁一口氣:“原來是場噩夢啊……” “哼漱牵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起疚漆,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刁赦,沒想到半個(gè)月后娶聘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甚脉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年丸升,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牺氨。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狡耻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猴凹,到底是詐尸還是另有隱情夷狰,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布郊霎,位于F島的核電站沼头,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏书劝。R本人自食惡果不足惜进倍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望购对。 院中可真熱鬧猾昆,春花似錦、人聲如沸骡苞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至么抗,卻和暖如春毅否,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蝇刀。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工螟加, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吞琐。 一個(gè)月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓捆探,卻偏偏與公主長得像,于是被迫代替她去往敵國和親站粟。 傳聞我的和親對象是個(gè)殘疾皇子黍图,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

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