【譯】如何使用Android MediaStore裁剪大圖片

yangxi_001發表於2013-11-27

http://my.oschina.net/ryanhoo/blog/86843

譯者:Ryan Hoo

來源:http://www.androidworks.com/crop_large_photos_with_android 

譯者按:在外企工作的半年多中花了不少時間在國外的網站上搜尋資料,其中有一些相當有含金量的文章,我會陸陸續續翻譯成中文,與大家共享之。初次翻譯,“信達雅”三境界恐怕只到信的層次,望大家見諒!

    這篇文章相當經典而實用,想當初我做手機拍照截圖的時候,大多都是在網上抄來抄去的內容,從來沒有人考慮過實際專案中的需求。實際上,拍照傳大圖片,如果用普通方式會耗用極大的記憶體,Android一個App原則上的16M記憶體限制可以一下子被耗光。Android在拍照上有一個隱藏的設計,如果拍照圖片過大,只返回一張縮圖。具體到不同手機,都是不一樣的。

-------------------------------------------------------------------------------------

譯文:

 概述

          我寫這篇文章是為了發表我對MediaStore裁剪圖片功能的一些簡要研究。基本上,如果你要寫一個應用程式,使用已有的Media Gallery並允許使用者在你的應用裡選取他的圖片的一部分(可選功能:人臉識別)。 可以使用一個Intent做到這個,但是也存在著相應的問題,總的來說也缺少這方面的文件告訴我們怎麼實現它。 另外,這篇文章基於2.1並且在Nexus One上做了測試。 Nexus One上的實現似乎被這群人寫在了這裡: Media Gellery for Nexus One  

 反饋

         這篇文章需要使用基於我的研究所寫的程式。如果你對我推薦的實現方案有所改進,請讓我知道。我會相應的更新這篇文章。 

Intent細節

        首先,讓我們探討下Intent以及它的特點。在看了一些程式碼示例以後,我發現我可以很輕鬆的使用如下的Intent呼叫裁剪功能: 
1 Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
2 intent.setType(“image/*”);
3 intent.putExtra(“crop”, “true”);
4
         然而,這是在我缺少附加的文件,不知道這些選項的具體含義等等情況之下的選擇。所以,我將我的研究整理成一個表格 ,並寫了一個演示程式,力圖演示控制此功能的所有可供選項。 
你可以在你的程式中使用使用我的程式碼,並且擴充套件它。我會將之附加在這篇文章上。 
Exta Options Table for image/* crop:
附加選項 資料型別 描述
crop String 傳送裁剪訊號
aspectX int X方向上的比例
aspectY int Y方向上的比例
outputX int 裁剪區的寬
outputY int 裁剪區的高
scale boolean 是否保留比例
return-data boolean 是否將資料保留在Bitmap中返回
data Parcelable 相應的Bitmap資料
circleCrop String 圓形裁剪區域?
MediaStore.EXTRA_OUTPUT ("output") URI 將URI指向相應的file:///...,詳見程式碼示例

         現在,最令人困惑的是MediaStore.EXTRA_OUTPUT以及return-data選項。 
        你主要有兩種方式從這個Intent中取得返回的bitmap:獲取內部資料或者提供一個Uri以便程式可以將資料寫入。 

        方法1:如果你將return-data設定為“true”,你將會獲得一個與內部資料關聯的Action,並且bitmap以此方式返回:(Bitmap)extras.getParcelable("data")。注意:如果你最終要獲取的圖片非常大,那麼此方法會給你帶來麻煩,所以你要控制outputX和outputY保持在較小的尺寸。鑑於此原因,在我的程式碼中沒有使用此方法((Bitmap)extras.getParcelable("data"))。

        下面是CropImage.java的原始碼片段: 

1 // Return the cropped image directly or save it to the specified URI.
2 Bundle myExtras = getIntent().getExtras();
3 if (myExtras != null && (myExtras.getParcelable("data") != null || myExtras.getBoolean("return-data")))
4 {
5     Bundle extras = new Bundle();
6     extras.putParcelable("data", croppedImage);
7     setResult(RESULT_OK,(new Intent()).setAction("inline-data").putExtras(extras));
8     finish();
9 }
          方法2: 如果你將return-data設定為“false”,那麼在onActivityResult的Intent資料中你將不會接收到任何Bitmap,相反,你需要將MediaStore.EXTRA_OUTPUT關聯到一個Uri,此Uri是用來存放Bitmap的。 

但是還有一些條件,首先你需要有一個短暫的與此Uri相關聯的檔案地址,當然這不是個大問題(除非是那些沒有sdcard的裝置)。

         下面是CropImage.java關於操作Uri的原始碼片段: 

01 if (mSaveUri != null) {
02     OutputStream outputStream = null;
03     try {
04         outputStream = mContentResolver.openOutputStream(mSaveUri);
05         if (outputStream != null) {
06             croppedImage.compress(mOutputFormat, 75, outputStream);
07         }
08     catch (IOException ex) {
09         // TODO: report error to caller
10         Log.e(TAG, "Cannot open file: " + mSaveUri, ex);
11     finally {
12         Util.closeSilently(outputStream);
13     }
14     Bundle extras = new Bundle();
15     setResult(RESULT_OK, new Intent(mSaveUri.toString()).putExtras(extras));
16 }

程式碼示例:

        我已經附上了一些程式碼示例,應該可以讓你測試多種配置。請讓我知道它對你是否有用。

程式碼下載: MediaStoreTest 
01 /** Called when the activity is first created. */
02 @Override
03 public void onCreate(Bundle savedInstanceState) {
04     super.onCreate(savedInstanceState);
05     thiz = this;
06     setContentView(R.layout.main);
07     mBtn = (Button) findViewById(R.id.btnLaunch);
08     photo = (ImageView) findViewById(R.id.imgPhoto);
09     mBtn.setOnClickListener(new OnClickListener() {
10  
11         public void onClick(View v) {
12             try {
13                 // Launch picker to choose photo for selected contact
14                 Intent intent = new Intent(Intent.ACTION_GET_CONTENT, null);
15                 intent.setType("image/*");
16                 intent.putExtra("crop""true");
17                 intent.putExtra("aspectX", aspectX);
18                 intent.putExtra("aspectY", aspectY);
19                 intent.putExtra("outputX", outputX);
20                 intent.putExtra("outputY", outputY);
21                 intent.putExtra("scale", scale);
22                 intent.putExtra("return-data", return_data);
23                 intent.putExtra(MediaStore.EXTRA_OUTPUT, getTempUri());
24                 intent.putExtra("outputFormat",
25                         Bitmap.CompressFormat.JPEG.toString()); <span style="color:#48465A;font-family:monospace;font-size:11px;line-height:normal;background-color:#EFEFEF;">                                 // lol, negative boolean noFaceDetection</span> intent.putExtra("noFaceDetection", !faceDetection);
26                 if (circleCrop) {
27                     intent.putExtra("circleCrop"true);
28                 }
29  
30                 startActivityForResult(intent, PHOTO_PICKED);
31             catch (ActivityNotFoundException e) {
32                 Toast.makeText(thiz, R.string.photoPickerNotFoundText,
33                         Toast.LENGTH_LONG).show();
34             }
35         }
36     });
37  
38 }
39  
40 private Uri getTempUri() {
41     return Uri.fromFile(getTempFile());
42 }
43  
44 private File getTempFile() {
45     if (isSDCARDMounted()) {
46  
47         File f = new File(Environment.getExternalStorageDirectory(),
48                 TEMP_PHOTO_FILE);
49         try {
50             f.createNewFile();
51         catch (IOException e) {
52             // TODO Auto-generated catch block
53             Toast.makeText(thiz, R.string.fileIOIssue, Toast.LENGTH_LONG)
54                     .show();
55         }
56         return f;
57     else {
58         return null;
59     }
60 }
61  
62 private boolean isSDCARDMounted() {
63     String status = Environment.getExternalStorageState();
64  
65     if (status.equals(Environment.MEDIA_MOUNTED))
66         return true;
67     return false;
68 }
69  
70 @Override
71 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
72     super.onActivityResult(requestCode, resultCode, data);
73  
74     switch (requestCode) {
75     case PHOTO_PICKED:
76         if (resultCode == RESULT_OK) {
77             if (data == null) {
78                 Log.w(TAG, "Null data, but RESULT_OK, from image picker!");
79                 Toast t = Toast.makeText(this, R.string.no_photo_picked,
80                         Toast.LENGTH_SHORT);
81                 t.show();
82                 return;
83             }
84  
85             final Bundle extras = data.getExtras();
86             if (extras != null) {
87                 File tempFile = getTempFile();
88                 // new logic to get the photo from a URI
89                 if (data.getAction() != null) {
90                     processPhotoUpdate(tempFile);
91                 }
92             }
93         }
94         break;
95     }
96 }
附錄:My comments 
Thank you so much! The tutorial is great! Actually the secret of cropping photos on Android is using Uri if the photo is in large size and using Bitmap if you want but make sure that the bitmap is not too big.(You can use it for cropping avatar or other requirements with a limited size of the photo. Different phones have different limits. Normally if you want to use a bitmap, the size shouldn't be bigger than 300. Otherwise the Uri is suggested.) 

相關文章