1. 程式人生 > >android圖片裁剪拼接實現(一):Matrix基本使用

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

一、前文

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

二、原理

  要徹底搞懂matrix還是需要一定的線性代數上面的理解,不過對於基本使用,瞭解到矩陣乘法就足夠了。

  在android座標系中,分為x、y和z三個軸,分別代表了長、寬、高三個維度。如下圖所示

android座標系


  在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的繪製過程。簡單的繪製流程如下

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中的RTSPRTSP簡介

背景: 我在最近的專案中遇到了使用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的基礎工具包

一站式學習WiresharkWireshark基本用法

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

linuxlinux基本命令-常用系統工作命令

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