android圖片裁剪拼接實現(一):Matrix基本使用

NO IMAGE

一、前文

  之前有個朋友委託我實現一個圖片拼接的組件,感覺挺有意思,於是週末花了些時間去研究了下,其實拼接這一步並不難,但是我在研究中發現了Matrix這個東西,非常好的東西。為此,我竟然拾起了多年沒有動過的線性代數。

二、原理

  要徹底搞懂matrix還是需要一定的線性代數上面的理解,不過對於基本使用,瞭解到矩陣乘法就足夠了。
  在android座標系中,分為x、y和z三個軸,分別代表了長、寬、高三個維度。如下圖所示

android圖片裁剪拼接實現(一):Matrix基本使用

  在android中,使用三維座標(x,y,z)組成一個行列式與一個三階行列式進行矩陣乘法。

android圖片裁剪拼接實現(一):Matrix基本使用

  圖中顯示的使用初始座標組成的矩陣與單位矩陣進行矩陣乘法。矩陣乘法使用可以參考矩陣乘法
  Martix會把輸入進來的矩陣帶入到其內部的矩陣中進行計算,最終輸出新的矩陣,來達到對圖形形態的處理。

三、基本方法的使用

  Matrix提供的基本方法有三種模式,

  1. setXXX()方法,例如 setRotate(),setScale()
  2. preXXX()方法,例如 preRotate(),preScale()
  3. postXXX()方法,例如 postRotate(),postScale()

    其中,setXXX()會先將矩陣重置為單位矩陣,然後再進行矩陣變幻
    preXXX()和postXXX()方法會牽扯到矩陣的前乘和後乘,如果瞭解了矩陣乘法規則,就會明白矩陣前乘和後乘得出來的結果是不一樣的,不過一般情況下都會選擇使用post方法,後乘。
    其中還有擴展方法比如:
  4. mapRect(rect) / mapRect(desRect,orgRect)
      第一個方法即使用原始矩陣代入運算,會將返回的矩陣直接覆蓋在傳入的矩陣中
      第二個方法則是對於需要保存原始矩陣的情況下,會把原始矩陣的計算結果賦值到指定的矩陣中
  5. setRectToRect(src,des,stf)
      這個方法相當於將原始矩陣填充到目標矩陣中,所以也就要求兩個矩陣都是有值的。其中填充模式由第三個參數決定。

        /**
    * 獨立縮放X和Y,直到和src的rect和目標rect確切的匹配。這可能會改變原始rect的寬高比
    */
    FILL(0),
    /**
    * 在保持原有寬高比的情況下計算出一個合適的縮放比例,但也會確保原始rect合適的填入目標rect,
    * 最終會把開始的一個邊與目標的開始邊左邊對齊
    */
    START(1),
    /**
    * 與START類似,不過最終結果會儘可能居中
    */
    CENTER(2),
    /**
    * 與START類似,不過最終結果會儘可能靠右邊
    */
    END(3);
    
  6. invert(inverse)
      反轉矩陣,可以應用到類似倒影一類的實現中
  7. setPolyToPoly(src,srcIndex,dst,dstIndex,pointCount)
      這是一個比較神奇的方法。隨著pointCount點數量,可以對原始矩陣進行平移、旋轉、錯切、翻頁效果。功能非常強大。

    此外,關於Matrix還有顏色變幻等效果,更多擴展用法後面會講到。

四、實踐到自定義view中

  寫一個自定義view,最重要的是要了解view的繪製過程。簡單的繪製流程如下

android圖片裁剪拼接實現(一):Matrix基本使用

其中不帶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) {
// ...
}
}

這裡面跟本文無關的方法都隱藏了,隨後會講到.
那麼我們來看看效果

android圖片裁剪拼接實現(一):Matrix基本使用

使用方法,跟目錄gradle裡面添加:

repositories {
...
maven { url 'https://jitpack.io' }
}

app.gradle中添加:

compile 'com.github.Kongdy:ImageStitching:v1.0.0'

本文代碼:github.com/Kongdy/Imag…
個人github地址:github.com/Kongdy
個人掘金主頁:juejin.im/user/595a64…
csdn主頁:blog.csdn.net/u014303003

相關文章

Python讓你的Web應用程飛起來全家桶之Sanic

讓你的Python(Web應用)飛起來,(異步/協程)全家桶

vuevuexvuerouter後臺項目——權限路由(超詳細簡單版)

基本數據結構梳理