uCrop圖片裁剪

来源:https://www.cnblogs.com/ganchuanpu/archive/2018/03/01/8490518.html
-Advertisement-
Play Games

uCrop使用 github地址 https://github.com/Yalantis/uCrop然後clone或下載到本地,運行之。 效果預覽 app/build.gradle AndroidManifest.xml 這裡theme可以改成自己的 配置uCrop 其他配置 onActivityR ...


uCrop使用

github地址

https://github.com/Yalantis/uCrop
然後clone或下載到本地,運行之。

效果預覽

app/build.gradle

compile 'com.yalantis:ucrop:1.5.0'

AndroidManifest.xml

1 <activity
2     android:name="com.yalantis.ucrop.UCropActivity"
3     android:screenOrientation="portrait"
4     android:theme="@style/Theme.AppCompat.Light.NoActionBar" />

這裡theme可以改成自己的

配置uCrop

 1  /**
 2   * 啟動裁剪
 3   * @param activity 上下文
 4   * @param sourceFilePath 需要裁剪圖片的絕對路徑
 5   * @param requestCode 比如:UCrop.REQUEST_CROP
 6   * @param aspectRatioX 裁剪圖片寬高比
 7   * @param aspectRatioY 裁剪圖片寬高比
 8   * @return
 9   */
10 public static String startUCrop(Activity activity, String sourceFilePath, 
11     int requestCode, float aspectRatioX, float aspectRatioY) {
12     Uri sourceUri = Uri.fromFile(new File(sourceFilePath));
13     File outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
14     if (!outDir.exists()) {
15         outDir.mkdirs();
16     }
17     File outFile = new File(outDir, System.currentTimeMillis() + ".jpg");
18     //裁剪後圖片的絕對路徑
19     String cameraScalePath = outFile.getAbsolutePath();
20     Uri destinationUri = Uri.fromFile(outFile);
21     //初始化,第一個參數:需要裁剪的圖片;第二個參數:裁剪後圖片
22     UCrop uCrop = UCrop.of(sourceUri, destinationUri);
23     //初始化UCrop配置
24     UCrop.Options options = new UCrop.Options();
25     //設置裁剪圖片可操作的手勢
26     options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL);
27     //是否隱藏底部容器,預設顯示
28     options.setHideBottomControls(true);
29     //設置toolbar顏色
30     options.setToolbarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));
31     //設置狀態欄顏色
32     options.setStatusBarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));
33     //是否能調整裁剪框
34     options.setFreeStyleCropEnabled(true);
35     //UCrop配置
36     uCrop.withOptions(options);
37     //設置裁剪圖片的寬高比,比如16:9
38     uCrop.withAspectRatio(aspectRatioX, aspectRatioY);
39     //uCrop.useSourceImageAspectRatio();
40     //跳轉裁剪頁面
41     uCrop.start(activity, requestCode);
42     return cameraScalePath;
43 }

其他配置

 1 //設置Toolbar標題
 2 void setToolbarTitle(@Nullable String text)
 3 //設置裁剪的圖片格式
 4 void setCompressionFormat(@NonNull Bitmap.CompressFormat format)
 5 //設置裁剪的圖片質量,取值0-100
 6 void setCompressionQuality(@IntRange(from = 0) int compressQuality)
 7 //設置最多縮放的比例尺
 8 void setMaxScaleMultiplier(@FloatRange(from = 1.0, fromInclusive = false) float maxScaleMultiplier)
 9 //動畫時間
10 void setImageToCropBoundsAnimDuration(@IntRange(from = 100) int durationMillis)
11 //設置圖片壓縮最大值
12 void setMaxBitmapSize(@IntRange(from = 100) int maxBitmapSize)
13 //是否顯示橢圓裁剪框陰影
14 void setOvalDimmedLayer(boolean isOval) 
15 //設置橢圓裁剪框陰影顏色
16 void setDimmedLayerColor(@ColorInt int color)
17 //是否顯示裁剪框
18 void setShowCropFrame(boolean show)
19 //設置裁剪框邊的寬度
20 void setCropFrameStrokeWidth(@IntRange(from = 0) int width)
21 //是否顯示裁剪框網格
22 void setShowCropGrid(boolean show) 
23 //設置裁剪框網格顏色
24 void setCropGridColor(@ColorInt int color)
25 //設置裁剪框網格寬
26 void setCropGridStrokeWidth(@IntRange(from = 0) int width)

onActivityResult

經過裁剪,返回結果,這裡我一般只需要裁剪後的圖片絕對路徑(調用上面startUCrop,即返回圖片路徑),然後調介面上傳伺服器。

1 @Override
2 public void onActivityResult(int requestCode, int resultCode, Intent data) {
3     if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) {
4         final Uri resultUri = UCrop.getOutput(data);
5     } else if (resultCode == UCrop.RESULT_ERROR) {
6         final Throwable cropError = UCrop.getError(data);
7     }
8 }

uCrop源碼淺析

uCrop源碼能學習的東西有很多,比如左右滑的標尺,不過我們這裡源碼淺析只關註裁剪部分。

類關係

首先有個大概瞭解:

GestureCropImageView:負責監聽各種手勢
CropImageView:主要完成圖片裁剪工作,和判斷裁剪圖片是否充滿裁剪框
TransformImageView:負責圖片旋轉、縮放、位移操作

入口

由上面的效果圖可知,點擊右上角,調用裁剪操作,代碼如下:

 1 @Override
 2 public boolean onOptionsItemSelected(MenuItem item) {
 3     if (item.getItemId() == R.id.menu_crop) {
 4         cropAndSaveImage();
 5     }
 6     return super.onOptionsItemSelected(item);
 7 }
 8 //裁剪和保存圖片
 9 protected void cropAndSaveImage() {
10     ……
11     mGestureCropImageView.cropAndSaveImage(mCompressFormat, mCompressQuality, new BitmapCropCallback() {
12         @Override
13         public void onBitmapCropped(@NonNull Uri resultUri) {
14             setResultUri(resultUri, mGestureCropImageView.getTargetAspectRatio());
15             finish();
16         }
17         @Override
18         public void onCropFailure(@NonNull Throwable t) {
19             setResultError(t);
20             finish();
21         }
22     });
23 }

這裡調用了GestureCropImageView&cropAndSaveImage方法,如下:

 1 /**
 2  * @param compressFormat  圖片壓縮格式
 3  * @param compressQuality 圖片壓縮質量
 4  * @param cropCallback    圖片壓縮回調
 5  */
 6 public void cropAndSaveImage(@NonNull Bitmap.CompressFormat compressFormat, int                                             compressQuality,@Nullable BitmapCropCallback cropCallback) {
 7     //取消所有動畫
 8     cancelAllAnimations();
 9     //判斷裁剪圖片是否充滿裁剪框
10     setImageToWrapCropBounds(false);
11     //進行裁剪
12     new BitmapCropTask(getViewBitmap(), mCropRect, RectUtils.trapToRect(mCurrentImageCorners),
13             getCurrentScale(), getCurrentAngle(),
14             mMaxResultImageSizeX, mMaxResultImageSizeY,
15             compressFormat, compressQuality,
16             getImageInputPath(), getImageOutputPath(),
17             cropCallback).execute();
18 }

裁剪之前

setImageToWrapCropBounds

裁剪之前,先判斷裁剪圖片是否充滿裁剪框,如果沒有,進行移動和縮放讓其充滿。

 1 public void setImageToWrapCropBounds(boolean animate) {
 2     //mBitmapLaidOut圖片載入OK,isImageWrapCropBounds()檢查圖片是否充滿裁剪框
 3     if (mBitmapLaidOut && !isImageWrapCropBounds()) {
 4         //當前圖片中心X點
 5         float currentX = mCurrentImageCenter[0];
 6         //當前圖片中心Y點
 7         float currentY = mCurrentImageCenter[1];
 8         //當前圖片縮放值
 9         float currentScale = getCurrentScale();
10         //差量
11         float deltaX = mCropRect.centerX() - currentX;
12         float deltaY = mCropRect.centerY() - currentY;
13         float deltaScale = 0;
14         //臨時矩陣重置
15         mTempMatrix.reset();
16         //臨時矩陣移動
17         mTempMatrix.setTranslate(deltaX, deltaY);
18         //複製到新的數組
19         final float[] tempCurrentImageCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length);
20         //將此矩陣應用於二維點的數組,並編寫轉換後的指向數組的點
21         mTempMatrix.mapPoints(tempCurrentImageCorners);
22         //再檢查圖片是否充滿裁剪框
23         boolean willImageWrapCropBoundsAfterTranslate = isImageWrapCropBounds(tempCurrentImageCorners);
24         if (willImageWrapCropBoundsAfterTranslate) {
25             //圖片縮進的數組
26             final float[] imageIndents = calculateImageIndents();
27             deltaX = -(imageIndents[0] + imageIndents[2]);
28             deltaY = -(imageIndents[1] + imageIndents[3]);
29         } else {
30             RectF tempCropRect = new RectF(mCropRect);
31             mTempMatrix.reset();
32             mTempMatrix.setRotate(getCurrentAngle());
33             mTempMatrix.mapRect(tempCropRect);
34             //獲取裁剪圖片的邊
35             final float[] currentImageSides = RectUtils.getRectSidesFromCorners(mCurrentImageCorners);
36             deltaScale = Math.max(tempCropRect.width() / currentImageSides[0],
37                     tempCropRect.height() / currentImageSides[1]);
38             deltaScale = deltaScale * currentScale - currentScale;
39         }
40         if (animate) {
41             //移動或縮放圖片(有動畫效果)
42             post(mWrapCropBoundsRunnable = new WrapCropBoundsRunnable(
43                     CropImageView.this, mImageToWrapCropBoundsAnimDuration, currentX, currentY, deltaX, deltaY,
44                     currentScale, deltaScale, willImageWrapCropBoundsAfterTranslate));
45         } else {
46             //移動圖片
47             postTranslate(deltaX, deltaY);
48             if (!willImageWrapCropBoundsAfterTranslate) {
49                 //縮放圖片
50                 zoomInImage(currentScale + deltaScale, mCropRect.centerX(), mCropRect.centerY());
51             }
52         }
53     }
54 }

進行裁剪

裁剪放到了非同步,即BitmapCropTask繼承AsyncTask,先設置原始圖片resizeScale值,然後通過ExifInterface保存新的圖片,即裁剪後的圖片

  1 public class BitmapCropTask extends AsyncTask<Void, Void, Throwable> {
  2     
  3     ……
  4     /**
  5      * @param viewBitmap          裁剪圖片bitmap
  6      * @param cropRect            裁剪矩形
  7      * @param currentImageRect    當前圖片矩形
  8      * @param currentScale        當前圖片縮放值
  9      * @param currentAngle        當前圖片角度
 10      * @param maxResultImageSizeX 圖片裁剪後最大寬值
 11      * @param maxResultImageSizeY 圖片裁剪後最大高值
 12      * @param compressFormat      圖片裁剪的格式
 13      * @param compressQuality     圖片裁剪的質量
 14      * @param imageInputPath      裁剪圖片路徑
 15      * @param imageOutputPath     圖片裁剪後路徑
 16      * @param cropCallback        裁剪回調
 17      */
 18     public BitmapCropTask(@Nullable Bitmap viewBitmap,
 19                           @NonNull RectF cropRect, @NonNull RectF currentImageRect,
 20                           float currentScale, float currentAngle,
 21                           int maxResultImageSizeX, int maxResultImageSizeY,
 22                           @NonNull Bitmap.CompressFormat compressFormat, int compressQuality,
 23                           @NonNull String imageInputPath, @NonNull String imageOutputPath,
 24                           @Nullable BitmapCropCallback cropCallback) {
 25       ……
 26     }
 27     @Override
 28     @Nullable
 29     protected Throwable doInBackground(Void... params) {
 30         if (mViewBitmap == null || mViewBitmap.isRecycled()) {
 31             return new NullPointerException("ViewBitmap is null or already recycled");
 32         }
 33         if (mCurrentImageRect.isEmpty()) {
 34             return new NullPointerException("CurrentImageRect is empty");
 35         }
 36         //設置resizeScale值
 37         float resizeScale = resize();
 38         try {
 39             //裁剪
 40             crop(resizeScale);
 41             //回收
 42             mViewBitmap.recycle();
 43             mViewBitmap = null;
 44         } catch (Throwable throwable) {
 45             return throwable;
 46         }
 47         return null;
 48     }
 49     private float resize() {
 50         //初始Options
 51         final BitmapFactory.Options options = new BitmapFactory.Options();
 52         //查詢該點陣圖,而無需分配存儲器,可獲取outHeight(圖片原始高度)和 outWidth(圖片的原始寬度)
 53         options.inJustDecodeBounds = true;
 54         //裁剪圖片解碼
 55         BitmapFactory.decodeFile(mImageInputPath, options);
 56         //原始圖片和裁剪後圖片比值
 57         float scaleX = options.outWidth / mViewBitmap.getWidth();
 58         float scaleY = options.outHeight / mViewBitmap.getHeight();
 59         float resizeScale = Math.min(scaleX, scaleY);
 60         mCurrentScale /= resizeScale;
 61         //初始化值為1
 62         resizeScale = 1;
 63         if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) {
 64             float cropWidth = mCropRect.width() / mCurrentScale;
 65             float cropHeight = mCropRect.height() / mCurrentScale;
 66             if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) {
 67                 scaleX = mMaxResultImageSizeX / cropWidth;
 68                 scaleY = mMaxResultImageSizeY / cropHeight;
 69                 //設置resizeScale,如果是2就是高度和寬度都是原始的一半
 70                 resizeScale = Math.min(scaleX, scaleY);
 71                 mCurrentScale /= resizeScale;
 72             }
 73         }
 74         return resizeScale;
 75     }
 76     private boolean crop(float resizeScale) throws IOException {
 77         //ExifInterface這個介面提供了圖片文件的旋轉,gps,時間等信息,從原始圖片讀出Exif標簽
 78         ExifInterface originalExif = new ExifInterface(mImageInputPath);
 79         int top = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale);
 80         int left = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale);
 81         int width = Math.round(mCropRect.width() / mCurrentScale);
 82         int height = Math.round(mCropRect.height() / mCurrentScale);
 83         //複製圖片
 84         boolean cropped = cropCImg(mImageInputPath, mImageOutputPath,
 85                 left, top, width, height, mCurrentAngle, resizeScale,
 86                 mCompressFormat.ordinal(), mCompressQuality);
 87         if (cropped) {
 88             //拿到裁剪後圖片
 89             copyExif(originalExif, width, height);
 90         }
 91         return cropped;
 92     }
 93     @SuppressWarnings("JniMissingFunction")
 94     native public boolean cropCImg(String inputPath, String outputPath,
 95                                    int left, int top, int width, int height, float angle, float resizeScale,
 96                                    int format, int quality) throws IOException, OutOfMemoryError;
 97     /**
 98      * @param originalExif 原始圖片Exif
 99      * @param width        裁剪後圖片寬
100      * @param height       裁剪後圖片高
101      * @throws IOException 是否異常
102      */
103     public void copyExif(ExifInterface originalExif, int width, int height) throws IOException {
104         //Exif標簽數組
105         String[] attributes = new String[]{
106                 ExifInterface.TAG_APERTURE,
107                 ……
108         };
109         //指定裁剪後圖片路徑,初始化新的ExifInterface
110         ExifInterface newExif = new ExifInterface(mImageOutputPath);
111         String value;
112         for (String attribute : attributes) {
113             value = originalExif.getAttribute(attribute);
114             if (!TextUtils.isEmpty(value)) {
115                 //設置Exif標簽
116                 newExif.setAttribute(attribute, value);
117             }
118         }
119         newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width));
120         newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height));
121         newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "0");
122         //保存
123         newExif.saveAttributes();
124     }
125     @Override
126     protected void onPostExecute(@Nullable Throwable t) {
127         if (mCropCallback != null) {
128             if (t == null) {
129                 //介面回調,over
130                 mCropCallback.onBitmapCropped(Uri.fromFile(new File(mImageOutputPath)));
131             } else {
132                 mCropCallback.onCropFailure(t);
133             }
134         }
135     }
136 }

總結

uCrop功能強大,對於我來說,有很多東西值得學習,難點如Rect包含問題(其實這塊還不是很理解),新知識如ExifInterface操作圖片,BitmapFactory顯示圖片的知識點溫故等,還有自定義左右滑的標尺,都是不錯的學習源碼。拋磚引玉至此,over。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • java ElasticSearch訪問控制上存在以下多種接入實現方式: 1)基於ES JAVA API實現;2)基於ES HTTP API實現;3)基於Spring Data ES實現; 那麼問題來了,到底哪種方式最好,靈活性、擴展性和完備性等更勝一籌呢? 為了找到最權威的答案,搜索了各大權威技術 ...
  • 在Windows Server 2012平臺使用命令啟動監聽服務時遇到了TNS-12560 & TNS-00530錯誤。 C:\Users>lsnrctl start GEW_LISTENER LSNRCTL for 32-bit Windows: Version 11.2.0.1.0 - Prod... ...
  • 在資料庫領域,回顧2017這一年,精彩紛呈,熱點不斷,而且不乏標誌性的事件發生,如Oracle提出的自治資料庫這樣的概念,把資料庫技術帶入一個新世界。再如NDBC(中國電腦學會資料庫學術年會)慶祝四十華誕、阿裡入股MariaDB、國內類Aurora架構的產品爭相發佈、資料庫事務處理等核心技術的原創... ...
  • 透明網關概念 ORACLE透明網關(Oracle Transparent Gateway)可以解決ORACLE資料庫和非ORACLE資料庫交互數據的需求。在一個異構的分散式環境中,通過ORACLE透明網關可以訪問其他類型資料庫,例如DB2,SQL Server、Sybase....。這個類似於SQL... ...
  • 如果想在一個已經建好的表中添加一列,可以用以下代碼: alter table 表名 add column 列名 varchar(20) not null; 這條語句會向已有的表中加入一列,這一列在表的最後一列位置。如果我們希望添加在指定的一列,可以用: alter table 表名 add colu ...
  • 去年買了一本講SqlServer的書,這幾天把這本塵封已久的書拿了出來,準備按照上面的目錄擼一遍。 簡單的看了下這本書的目錄結構,一共是九個部分 資料庫安裝 建立資料庫和處理數據 高級T SQL T SQL編程 企業數據管理 SQL Server安全 監視和審計 性能調整和優化 商業智能 資料庫文件 ...
  • iPhone將具有支持不同類型多線程API的能力,這些API包括:POSIX線程,NSObject,NSThread和NSOperation. iPhone操作系統是一個真正的搶占式,多任務操作系統,允許開發人員創建多線程應用。 拿著一個iPhone時,我手中實際是 一個配備Unix操作系統的搶占式 ...
  • 首先說一個很有意思的問題:一塊720p的屏幕和1080p的屏幕那個大? 這個問題很有代表性,如果手機豎著放,720p=720px*1280px,而1080p=1080px*1920px;那麼在寬度上,前者是720個像素,後者是1080個像素,後者一定比前者大嗎? 答案是否定的,後者不一定比前者大,還 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...