android圖片裁剪拼接實現(一):Matrix基本使用
一、前文
之前有個朋友委託我實現一個圖片拼接的元件,感覺挺有意思,於是週末花了些時間去研究了下,其實拼接這一步並不難,但是我在研究中發現了Matrix這個東西,非常好的東西。為此,我竟然拾起了多年沒有動過的線性代數。
二、原理
要徹底搞懂matrix還是需要一定的線性代數上面的理解,不過對於基本使用,瞭解到矩陣乘法就足夠了。
在android座標系中,分為x、y和z三個軸,分別代表了長、寬、高三個維度。如下圖所示
在android中,使用三維座標(x,y,z)組成一個行列式與一個三階行列式進行矩陣乘法。
圖中顯示的使用初始座標組成的矩陣與單位矩陣進行矩陣乘法。矩陣乘法使用可以參考
Martix會把輸入進來的矩陣帶入到其內部的矩陣中進行計算,最終輸出新的矩陣,來達到對圖形形態的處理。
三、基本方法的使用
Matrix提供的基本方法有三種模式,
1. setXXX()方法,例如 setRotate(),setScale()
2. preXXX()方法,例如 preRotate(),preScale()
3. postXXX()方法,例如 postRotate(),postScale()
其中,setXXX()會先將矩陣重置為單位矩陣,然後再進行矩陣變幻
preXXX()和postXXX()方法會牽扯到矩陣的前乘和後乘,如果瞭解了矩陣乘法規則,就會明白矩陣前乘和後乘得出來的結果是不一樣的,不過一般情況下都會選擇使用post方法,後乘。
其中還有擴充套件方法比如:
1. mapRect(rect) / mapRect(desRect,orgRect)
第一個方法即使用原始矩陣代入運算,會將返回的矩陣直接覆蓋在傳入的矩陣中
第二個方法則是對於需要儲存原始矩陣的情況下,會把原始矩陣的計算結果賦值到指定的矩陣中
2. setRectToRect(src,des,stf)
這個方法相當於將原始矩陣填充到目標矩陣中,所以也就要求兩個矩陣都是有值的。其中填充模式由第三個引數決定。
java
/**
* 獨立縮放X和Y,直到和src的rect和目標rect確切的匹配。這可能會改變原始rect的寬高比
*/
FILL(0),
/**
* 在保持原有寬高比的情況下計算出一個合適的縮放比例,但也會確保原始rect合適的填入目標rect,
* 最終會把開始的一個邊與目標的開始邊左邊對齊
*/
START(1),
/**
* 與START類似,不過最終結果會盡可能居中
*/
CENTER(2),
/**
* 與START類似,不過最終結果會盡可能靠右邊
*/
END(3);
3. invert(inverse)
反轉矩陣,可以應用到類似倒影一類的實現中
4. setPolyToPoly(src,srcIndex,dst,dstIndex,pointCount)
這是一個比較神奇的方法。隨著pointCount點數量,可以對原始矩陣進行平移、旋轉、錯切、翻頁效果。功能非常強大。
此外,關於Matrix還有顏色變幻等效果,更多擴充套件用法後面會講到。
四、實踐到自定義view中
寫一個自定義view,最重要的是要了解view的繪製過程。簡單的繪製流程如下
其中不帶on的方法都為排程方法,不可被重寫,這些方法裡面會把前期一些必要的資料準備出來,帶on字首的方法都是實際進行處理的方法。
measure方法是測量控制元件大小的,layout是用來佈局,根據measure測量的結果,把其中每個元素在其內部進行位置的計算。最後會執行的draw方法,draw也分為draw和onDraw,可以根據自己需求來改寫對應的方法。
其中,onMeasure的方法如下所示:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// measure child img
final int maxImgWidth = getMeasuredWidth();
final int maxImgHeight = getMeasuredHeight();
final int measureWidthSize = MeasureSpec.getSize(widthMeasureSpec);
final int measureHeightSize = MeasureSpec.getSize(heightMeasureSpec);
int totalImageHeight = 0;
// 縮放和旋轉影響size的交給measure
for (int i = 0; i < imgList.size(); i++) {
ImageData imageData = imgList.get(i);
final int imgOrgWidth = imageData.getImgWidth();
final int imgOrgHeight = imageData.getImgHeight();
int imgRotateWidth;
int imgRotateHeight;
if (imageData.scale > 0) {
imageData.matrix.setScale(imageData.scale, imageData.scale);
} else {
final float sizeProportion = (float) imgOrgWidth / imgOrgHeight;
if (imgOrgHeight > imgOrgWidth) {
if (measureHeightSize == MeasureSpec.EXACTLY &&
imgOrgHeight > maxImgHeight) {
imgRotateWidth = (int) (maxImgHeight * sizeProportion);
imgRotateHeight = maxImgHeight;
} else {
imgRotateWidth = imgOrgWidth;
imgRotateHeight = imgOrgHeight;
}
} else {
if (imgOrgWidth > maxImgWidth) {
imgRotateHeight = (int) (maxImgWidth / sizeProportion);
imgRotateWidth = maxImgWidth;
} else {
imgRotateWidth = imgOrgWidth;
imgRotateHeight = imgOrgHeight;
}
}
// resize
imageData.reSize(imgRotateWidth, imgRotateHeight);
}
// rotate
imageData.matrix.mapRect(imageData.drawRect, imageData.orgRect);
imageData.matrix.postRotate(imageData.rotateAngle, imageData.drawRect.centerX(),
imageData.drawRect.top + (imageData.drawRect.height() * 0.5f));
imageData.matrix.mapRect(imageData.drawRect, imageData.orgRect);
totalImageHeight += imageData.drawRect.height();
}
switch (measureHeightSize) {
// wrap_content
case MeasureSpec.AT_MOST:
setMeasuredDimension(MeasureSpec.makeMeasureSpec(maxImgWidth,
measureWidthSize), MeasureSpec.makeMeasureSpec(totalImageHeight,
measureHeightSize));
break;
// match_parent or accurate num
case MeasureSpec.EXACTLY:
setMeasuredDimension(MeasureSpec.makeMeasureSpec(maxImgWidth,
measureHeightSize));
break;
case MeasureSpec.UNSPECIFIED:
setMeasuredDimension(MeasureSpec.makeMeasureSpec(maxImgWidth,
measureWidthSize), MeasureSpec.makeMeasureSpec(totalImageHeight,
measureHeightSize));
break;
}
}
所有影響尺寸計算相關的方法都會放到這個measure裡面進行計算,比如scale和rotate,都會影響size大小。所以在這裡計算完成後,好在layout中進行正確的佈局。
layout中的程式碼如下:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
// measure child layout
int cursorTop = top;
int mid = (right - left) >> 1;
for (int i = 0; i < imgList.size(); i++) {
final ImageData imageData = imgList.get(i);
// fix layout translate
imageData.matrix.mapRect(imageData.drawRect, imageData.orgRect);
int translateTop = (int) (cursorTop + (imageData.orgRect.top -
imageData.drawRect.top));
int translateLeft = (int) (mid - imageData.drawRect.centerX());
imageData.matrix.postTranslate(translateLeft, translateTop);
imageData.matrix.mapRect(imageData.drawRect, imageData.orgRect);
cursorTop = (int) imageData.drawRect.bottom;
}
}
兩個方法中,要做到Matrix多效果疊加,切記要保留一個bitmap最原始的矩陣,然後再接下來的計算中需要用到當前尺寸的時候,使用Martix計算出臨時的尺寸對其進行計算。
兩個方法中,Bitmap被封裝到一個ImageData類裡面,進行物件化,這樣可以更好的管理Bitmap的處理和資料記錄。
ImageData如下:
public class ImageData {
public ImageData(Bitmap bitmap) {
this.bitmap = bitmap;
this.matrix = new Matrix();
orgRect.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
}
// 預設置0
float scale = 0f;
// 0點在3點鐘方向,達到垂直居中的效果,需要置為-90度
float rotateAngle = -90f;
RectF drawRect = new RectF();
RectF orgRect = new RectF();
Bitmap bitmap;
Matrix matrix;
private float distanceStub = 0f;
private float angleStub = 0f;
public Bitmap getBitmap() {
return bitmap;
}
public RectF getDrawRect() {
return drawRect;
}
public int getImgWidth() {
return bitmap.getWidth();
}
public int getImgHeight() {
return bitmap.getHeight();
}
public void layout(int l, int t, int r, int b) {
drawRect.set(l, t, r, b);
}
void reSize(int w, int h) {
int orgWidth = bitmap.getWidth();
int orgHeight = bitmap.getHeight();
// 計算縮放比例
float scaleWidth = ((float) w) / orgWidth;
float scaleHeight = ((float) h) / orgHeight;
scale = (scaleWidth + scaleHeight) * 0.5f;
matrix.postScale(scale, scale);
}
void clearMatrixCache() {
matrix.reset();
}
void setScale(float scale) {
this.scale = scale;
}
float getScale() {
return this.scale;
}
void setRotateAngle(float angle) {
this.rotateAngle = angle;
}
float getRotateAngle() {
return this.rotateAngle;
}
/**
* imageData的觸控處理事件
*
* @param e 觸控事件
*/
protected void onTouchEvent(MotionEvent e) {
// ...
}
private float getPointDistance(MotionEvent e) {
// ...
}
private float getPointAngle(MotionEvent e) {
// ...
}
}
這裡面跟本文無關的方法都隱藏了,隨後會講到.
那麼我們來看看效果
使用方法,跟目錄gradle裡面新增:
repositories {
...
maven { url 'https://jitpack.io' }
}
app.gradle中新增:
compile 'com.github.Kongdy:ImageStitching:v1.0.0'
相關推薦
android圖片裁剪拼接實現(一):Matrix基本使用
一、前文 之前有個朋友委託我實現一個圖片拼接的元件,感覺挺有意思,於是週末花了些時間去研究了下,其實拼接這一步並不難,但是我在研究中發現了Matrix這個東西,非常好的東西。為此,我竟然拾起了多年沒有動過的線性代數。 二、原理 要徹底搞懂mat
資料結構實現(一):動態陣列(C++版)
資料結構實現(一):動態陣列(C++版) 1. 概念及基本框架 2. 基本操作程式實現 2.1 增加操作 2.2 刪除操作 2.3 修改操作 2.4 查詢操作 2.5 其他操作 3. 演算法複雜度分析
JAVA高階基礎(8)---Set的典型實現(一):HashSet
HHashSet 注:更多詳細方法請自行在 API 上查詢 HashSet 是由hash表(hashMap)支援,不保證元素的迭代順恆久不變,允許存在null值,元素不允許重複,同時,不是執行緒安全的 HashSet是基於HashMap實現的。 &n
Java常用的八種排序演算法與程式碼實現(一):氣泡排序法、插入排序法、選擇排序法
這三種排序演算法適合小規模資料排序 --- 共同點:基於比較,時間複雜度均為O(n2),空間複雜度均為O(1)(原地排序演算法) 不同點:插入排序和氣泡排序是穩定的排序演算法,選擇排序不是 --- 穩定排序演算法:可以保持數值相等的兩個物件,在排序之
SVM全系列:從原理到python實現(一):SVM原理
前言 本文開始主要介紹一下SVM的分類原理以及SVM的數學匯出和SVM在Python上的實現。借鑑了許多文章,會在後面一一指出,如果有什麼不對的希望能指正。 一、 SVM簡介 首先看到SVM是在斯坦福的機器學習課程上,SVM是作為分類器在logisticregr
Android studio中NDK開發(一):CMakeLists.txt編寫入門
自定義變數 主要有隱式定義和顯式定義兩種。 隱式定義的一個例子是PROJECT指令,它會隱式的定義< projectname >_BINARY_DIR和< projectname >_SOURCE_DIR兩個變數;顯式定義使用SE
Android自定義圖表庫(一):圓形進度圖
效果預覽 自定義View第一步:確認View的大小 無論是自定義一個View還是ViewGroup我們必須得先為其制定在不同MeasureSpecMode下的大小,我這裡就不講解什麼繪製原始碼了什麼的,我們就直接實戰。 我們在onMeasure中需要呼叫se
Android MediaPlayer中的RTSP(一):RTSP簡介
背景: 我在最近的專案中遇到了使用Android的MediaPlayer來進行RTSP播放的場景。但對於RTSP這種流媒體協議,其實Android原生的播放器支援得不是很好,所以有許多需要修改的地方。 本文主要簡單介紹RTSP協議及其在MediaPlayer
ASP.NET Core Web API下事件驅動型架構的實現(一):一個簡單的實現
很長一段時間以來,我都在思考如何在ASP.NET Core的框架下,實現一套完整的事件驅動型架構
Shiro實現(一): SSM整合筆記實現登入,授權功能
開篇 本專案已經上傳github,建議對照程式碼理解 本篇主要講Shiro框架與SSM框架結合,實現登入和授權功能 利用spring 的aop切面思想,很簡單得融合Shiro許可權框架 程式碼 需要明白兩個點: 通過Subject.login() 登入成
Python3+Selenium2完整的自動化測試框架實現(一):自動化測試環境搭建
添加 在線安裝 自動化 eight str rain 中間 自動打開 發的 1 環境搭建準備 (1) 下載Python3版本的安裝包,直接官網下載即可:Python官網:https://www.python.org/ (2) 下載Python的基礎工具包
一站式學習Wireshark(一):Wireshark基本用法
11g 實現 alt href ascii 根據 無線網絡 完成 analyze 按照國際慣例,從最基本的說起。 抓取報文: 下載和安裝好Wireshark之後,啟動Wireshark並且在接口列表中選擇接口名,然後開始在此接口上抓包。例如,如果想要在無線網絡上抓取流量
Scala筆記整理(一):scala基本知識
大數據 Scala [TOC] Scala簡介 Scala是一門多範式(multi-paradigm)的編程語言,設計初衷是要集成面向對象編程和函數式編程的各種特性。 Scala運行在Java虛擬機上,並兼容現有的Java程序。 Scala源代碼被編譯成Java字節碼,所以它可以運行於JVM之上,並
Docker 學習筆記(一):Docker 基本命令 和 用 Dockerfile build 一個 JDK 映象
本文件為學習筆記,部分內容將持續更新。 注:本人信仰用最簡單的方式去做一些事,怎麼簡單怎麼來,也許不求甚解。 Docker 基本命令 docker version 獲取 docke
linux(一):linux基本命令-常用系統工作命令
1.man 幫助指令,可檢視Linux中指令幫助、配置檔案幫助、程式設計幫助等資訊 按鍵 用處 空格鍵 向下翻一頁 PaGe down 向下翻一頁 PaGe up 向上翻一頁
Linux學習(一):命令基本使用
文章目錄 常用Linux命令的基本使用 1.`cd` 切換資料夾(change directory) 2.`pwd` 檢視當前目錄所在路徑(print wrok directory) 3.`ls` 檢視當前目錄內容(list)
手把手做一個JSP入門程式(一):程式基本介紹(JSP)
胡扯 說好的不學jsp,結果今天還是學了。主要還是為了後面的java後臺的學習啦。為了更好的掌握知識,那我們就來寫一個簡單的jsp入門程式吧!這只是一個簡單的入門小程式,所以就沒有太多強大的功能。入門啦,入門啦。對了,由於是作為一個入門程式,所以裡面會有較
微控制器入門基礎篇(一):Keil基本操作
Keil基本操作 文/阿丘 2018/3/28一、概述 工欲善其事必先利其器。Keil uVersion 4.0(後文簡稱為Keil 4.0)是微控制器程式開發的整合開發環境(IDE),集成了C編譯器、巨集彙編、聯結器、庫管理和一個功能強大的模擬偵錯程式。
TensorFlow學習筆記(一):TF基本操作
一.TensorFlow基本執行流程如下: 使用圖 (graph) 來表示計算任務. 在被稱之為 會話 (Session) 的上下文 (context) 中執行圖. 使用 tensor 表示資料. 通過 變數 (Variable) 維護狀態. 使用 f
Python學習筆記(一):最基本的HelloWorld
1、Python和Java、c++等語言並沒有太大的區別,對於剛接觸這門語言的我來說,遠沒有接觸prolog等語言這樣的差異。唯一的不同,僅僅是和javascript類似。 2、基本環境配置,無,基本直接安裝就可以使用了,Java好歹需要配置環境變數 3、入門的HelloW