OSDN Git Service

878b82fc62b12cbacf9125da24ebbe81e2e33b96
[android-x86/packages-apps-Gallery2.git] / src / com / android / gallery3d / filtershow / crop / CropActivity.java
1 /*
2  * Copyright (C) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.gallery3d.filtershow.crop;
18
19 import android.app.ActionBar;
20 import android.app.Activity;
21 import android.app.WallpaperManager;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.Bitmap;
25 import android.graphics.Bitmap.CompressFormat;
26 import android.graphics.BitmapFactory;
27 import android.graphics.BitmapRegionDecoder;
28 import android.graphics.Rect;
29 import android.graphics.RectF;
30 import android.net.Uri;
31 import android.os.AsyncTask;
32 import android.os.Bundle;
33 import android.provider.MediaStore;
34 import android.util.DisplayMetrics;
35 import android.util.Log;
36 import android.view.View;
37 import android.view.View.OnClickListener;
38 import android.view.WindowManager;
39 import android.widget.Toast;
40
41 import com.android.gallery3d.R;
42
43 import java.io.ByteArrayInputStream;
44 import java.io.ByteArrayOutputStream;
45 import java.io.FileNotFoundException;
46 import java.io.IOException;
47 import java.io.InputStream;
48 import java.io.OutputStream;
49
50 /**
51  * Activity for cropping an image.
52  */
53 public class CropActivity extends Activity {
54     private static final String LOGTAG = "CropActivity";
55     public static final String CROP_ACTION = "com.android.camera.action.CROP";
56     private CropExtras mCropExtras = null;
57     private LoadBitmapTask mLoadBitmapTask = null;
58
59     private Bitmap mOriginalBitmap = null;
60     private RectF mOriginalBounds = null;
61     private Uri mSourceUri = null;
62     private CropView mCropView = null;
63     private View mSaveButton = null;
64     private boolean finalIOGuard = false;
65
66     private Intent mResultIntent = null;
67     private static final int SELECT_PICTURE = 1; // request code for picker
68
69     private static final int DEFAULT_COMPRESS_QUALITY = 90;
70     public static final int MAX_BMAP_IN_INTENT = 990000;
71
72     // Flags
73     private static final int DO_SET_WALLPAPER = 1;
74     private static final int DO_RETURN_DATA = 1 << 1;
75     private static final int DO_EXTRA_OUTPUT = 1 << 2;
76
77     private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
78
79     @Override
80     public void onCreate(Bundle savedInstanceState) {
81         super.onCreate(savedInstanceState);
82         Intent intent = getIntent();
83         mResultIntent = new Intent();
84         setResult(RESULT_CANCELED, mResultIntent);
85         mCropExtras = getExtrasFromIntent(intent);
86         if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
87             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
88         }
89
90         setContentView(R.layout.crop_activity);
91         mCropView = (CropView) findViewById(R.id.cropView);
92
93         ActionBar actionBar = getActionBar();
94         actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
95         actionBar.setCustomView(R.layout.filtershow_actionbar);
96
97         View mSaveButton = actionBar.getCustomView();
98         mSaveButton.setOnClickListener(new OnClickListener() {
99             @Override
100             public void onClick(View view) {
101                 startFinishOutput();
102             }
103         });
104
105         if (intent.getData() != null) {
106             mSourceUri = intent.getData();
107             startLoadBitmap(mSourceUri);
108         } else {
109             pickImage();
110         }
111     }
112
113     private void enableSave(boolean enable) {
114         if (mSaveButton != null) {
115             mSaveButton.setEnabled(enable);
116         }
117     }
118
119     @Override
120     protected void onDestroy() {
121         if (mLoadBitmapTask != null) {
122             mLoadBitmapTask.cancel(false);
123         }
124         super.onDestroy();
125     }
126
127     /**
128      * Opens a selector in Gallery to chose an image for use when none was given
129      * in the CROP intent.
130      */
131     private void pickImage() {
132         Intent intent = new Intent();
133         intent.setType("image/*");
134         intent.setAction(Intent.ACTION_GET_CONTENT);
135         startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
136                 SELECT_PICTURE);
137     }
138
139     /**
140      * Callback for pickImage().
141      */
142     @Override
143     public void onActivityResult(int requestCode, int resultCode, Intent data) {
144         if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
145             mSourceUri = data.getData();
146             startLoadBitmap(mSourceUri);
147         }
148     }
149
150     /**
151      * Gets screen size metric.
152      */
153     private int getScreenImageSize() {
154         DisplayMetrics outMetrics = new DisplayMetrics();
155         getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
156         return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
157     }
158
159     /**
160      * Method that loads a bitmap in an async task.
161      */
162     private void startLoadBitmap(Uri uri) {
163         enableSave(false);
164         final View loading = findViewById(R.id.loading);
165         loading.setVisibility(View.VISIBLE);
166         mLoadBitmapTask = new LoadBitmapTask();
167         mLoadBitmapTask.execute(uri);
168     }
169
170     /**
171      * Method called on UI thread with loaded bitmap.
172      */
173     private void doneLoadBitmap(Bitmap bitmap, RectF bounds) {
174         final View loading = findViewById(R.id.loading);
175         loading.setVisibility(View.GONE);
176         mOriginalBitmap = bitmap;
177         mOriginalBounds = bounds;
178         if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
179             RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
180             mCropView.initialize(bitmap, imgBounds, imgBounds, 0);
181             enableSave(true);
182         } else {
183             Log.w(LOGTAG, "could not load image for cropping");
184             cannotLoadImage();
185             setResult(RESULT_CANCELED, new Intent());
186             done();
187         }
188     }
189
190     /**
191      * Display toast for image loading failure.
192      */
193     private void cannotLoadImage() {
194         CharSequence text = getString(R.string.cannot_load_image);
195         Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
196         toast.show();
197     }
198
199     /**
200      * AsyncTask for loading a bitmap into memory.
201      *
202      * @see #startLoadBitmap(Uri)
203      * @see #doneLoadBitmap(Bitmap)
204      */
205     private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
206         int mBitmapSize;
207         Context mContext;
208         Rect mOriginalBounds;
209
210         public LoadBitmapTask() {
211             mBitmapSize = getScreenImageSize();
212             mContext = getApplicationContext();
213             mOriginalBounds = new Rect();
214         }
215
216         @Override
217         protected Bitmap doInBackground(Uri... params) {
218             Bitmap bmap = CropLoader.getConstrainedBitmap(params[0], mContext, mBitmapSize,
219                     mOriginalBounds);
220             return bmap;
221         }
222
223         @Override
224         protected void onPostExecute(Bitmap result) {
225             doneLoadBitmap(result, new RectF(mOriginalBounds));
226         }
227     }
228
229     private void startFinishOutput() {
230         if (finalIOGuard) {
231             return;
232         } else {
233             finalIOGuard = true;
234         }
235         enableSave(false);
236         Uri destinationUri = null;
237         int flags = 0;
238         if (mOriginalBitmap != null && mCropExtras != null) {
239             if (mCropExtras.getExtraOutput() != null) {
240                 destinationUri = mCropExtras.getExtraOutput();
241                 flags |= DO_EXTRA_OUTPUT;
242             }
243             if (mCropExtras.getSetAsWallpaper()) {
244                 flags |= DO_SET_WALLPAPER;
245             }
246             if (mCropExtras.getReturnData()) {
247                 flags |= DO_RETURN_DATA;
248             }
249         }
250         if (flags == 0) {
251             destinationUri = CropLoader.makeAndInsertUri(this, mSourceUri);
252             if (destinationUri != null) {
253                 flags |= DO_EXTRA_OUTPUT;
254             }
255         }
256         if ((flags & FLAG_CHECK) != 0) {
257             RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
258             RectF crop = getBitmapCrop(photo);
259             startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
260                     photo, mOriginalBounds,
261                     (mCropExtras == null) ? null : mCropExtras.getOutputFormat());
262             return;
263         }
264         setResult(RESULT_CANCELED, mResultIntent);
265         done();
266         return;
267     }
268
269     private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
270             RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format) {
271         if (cropBounds == null || photoBounds == null || currentBitmap == null
272                 || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
273                 || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
274                 || photoBounds.height() == 0) {
275             return; // fail fast
276         }
277         if ((flags & FLAG_CHECK) == 0) {
278             return; // no output options
279         }
280         if ((flags & DO_SET_WALLPAPER) != 0) {
281             Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
282         }
283
284         final View loading = findViewById(R.id.loading);
285         loading.setVisibility(View.VISIBLE);
286         BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
287                 photoBounds, currentBitmapBounds);
288         ioTask.execute(currentBitmap);
289     }
290
291     private void doneBitmapIO(boolean success, Intent intent) {
292         final View loading = findViewById(R.id.loading);
293         loading.setVisibility(View.GONE);
294         if (success) {
295             setResult(RESULT_OK, intent);
296         } else {
297             setResult(RESULT_CANCELED, intent);
298         }
299         done();
300     }
301
302     private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> {
303
304         private final WallpaperManager mWPManager;
305         InputStream mInStream = null;
306         OutputStream mOutStream = null;
307         String mOutputFormat = null;
308         Uri mOutUri = null;
309         Uri mInUri = null;
310         int mFlags = 0;
311         RectF mCrop = null;
312         RectF mPhoto = null;
313         RectF mOrig = null;
314         Intent mResultIntent = null;
315
316         public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
317                 RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds) {
318             mOutputFormat = outputFormat;
319             mOutStream = null;
320             mOutUri = destUri;
321             mInUri = sourceUri;
322             mFlags = flags;
323             mCrop = cropBounds;
324             mPhoto = photoBounds;
325             mOrig = originalBitmapBounds;
326             mWPManager = WallpaperManager.getInstance(getApplicationContext());
327             mResultIntent = new Intent();
328
329             if ((flags & DO_EXTRA_OUTPUT) != 0) {
330                 if (mOutUri == null) {
331                     Log.w(LOGTAG, "cannot write file, no output URI given");
332                 } else {
333                     try {
334                         mOutStream = getContentResolver().openOutputStream(mOutUri);
335                     } catch (FileNotFoundException e) {
336                         Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
337                     }
338                 }
339             }
340
341             if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
342                 if (mInUri == null) {
343                     Log.w(LOGTAG, "cannot read original file, no input URI given");
344                 } else {
345                     try {
346                         mInStream = getContentResolver().openInputStream(mInUri);
347                     } catch (FileNotFoundException e) {
348                         Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
349                     }
350                 }
351             }
352         }
353
354         @Override
355         protected Boolean doInBackground(Bitmap... params) {
356             boolean failure = false;
357             Bitmap img = params[0];
358
359             // Set extra for crop bounds
360             if (mCrop != null && mPhoto != null && mOrig != null) {
361                 RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
362                 if (trueCrop != null) {
363                     Rect rounded = new Rect();
364                     trueCrop.roundOut(rounded);
365                     mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
366                 }
367             }
368
369             // Find the small cropped bitmap that is returned in the intent
370             if ((mFlags & DO_RETURN_DATA) != 0) {
371                 assert (img != null);
372                 Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
373                 if (ret != null) {
374                     ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
375                 }
376                 if (ret == null) {
377                     Log.w(LOGTAG, "could not downsample bitmap to return in data");
378                     failure = true;
379                 } else {
380                     mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
381                 }
382             }
383
384             // Do the large cropped bitmap and/or set the wallpaper
385             if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
386                 BitmapRegionDecoder decoder = null;
387                 try {
388                     decoder = BitmapRegionDecoder.newInstance(mInStream, true);
389                 } catch (IOException e) {
390                     Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
391                 }
392                 if (decoder == null) {
393                     failure = true;
394                     return false;
395                 }
396
397                 // Find crop bounds (scaled to original image size)
398                 RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
399                 if (trueCrop == null) {
400                     Log.w(LOGTAG, "cannot find crop for full size image");
401                     failure = true;
402                     return false;
403                 }
404                 Rect roundedTrueCrop = new Rect();
405                 trueCrop.roundOut(roundedTrueCrop);
406
407                 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
408                     Log.w(LOGTAG, "crop has bad values for full size image");
409                     failure = true;
410                     return false;
411                 }
412                 // Do region decoding to get crop bitmap
413                 BitmapFactory.Options options = new BitmapFactory.Options();
414                 options.inMutable = true;
415                 Bitmap crop = decoder.decodeRegion(roundedTrueCrop, options);
416                 decoder.recycle();
417
418                 if (crop == null) {
419                     Log.w(LOGTAG, "cannot region decode file: " + mInUri.toString());
420                     failure = true;
421                     return false;
422                 }
423                 // Get output compression format
424                 CompressFormat cf =
425                         convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
426
427                 // If we only need to output to a URI, compress straight to file
428                 if (mFlags == DO_EXTRA_OUTPUT) {
429                     if (mOutStream == null
430                             || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
431                         Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString());
432                         failure = true;
433                     }
434                 } else {
435                     // Compress to byte array
436                     ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
437                     if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
438
439                         // If we need to output to a Uri, write compressed
440                         // bitmap out
441                         if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
442                             if (mOutStream == null) {
443                                 Log.w(LOGTAG,
444                                         "failed to compress bitmap to file: " + mOutUri.toString());
445                                 failure = true;
446                             } else {
447                                 try {
448                                     mOutStream.write(tmpOut.toByteArray());
449                                 } catch (IOException e) {
450                                     Log.w(LOGTAG,
451                                             "failed to compress bitmap to file: "
452                                                     + mOutUri.toString(), e);
453                                     failure = true;
454                                 }
455                             }
456                         }
457
458                         // If we need to set to the wallpaper, set it
459                         if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
460                             if (mWPManager == null) {
461                                 Log.w(LOGTAG, "no wallpaper manager");
462                                 failure = true;
463                             } else {
464                                 try {
465                                     mWPManager.setStream(new ByteArrayInputStream(tmpOut
466                                             .toByteArray()));
467                                 } catch (IOException e) {
468                                     Log.w(LOGTAG, "cannot write stream to wallpaper", e);
469                                     failure = true;
470                                 }
471                             }
472                         }
473                     } else {
474                         Log.w(LOGTAG, "cannot compress bitmap");
475                         failure = true;
476                     }
477                 }
478             }
479             return !failure; // True if any of the operations failed
480         }
481
482         @Override
483         protected void onPostExecute(Boolean result) {
484             doneBitmapIO(result.booleanValue(), mResultIntent);
485         }
486
487     }
488
489     private void done() {
490         finish();
491     }
492
493     protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
494         RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
495         RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
496         if (crop == null) {
497             return null;
498         }
499         Rect intCrop = new Rect();
500         crop.roundOut(intCrop);
501         return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
502                 intCrop.height());
503     }
504
505     protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
506         if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
507             throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
508         }
509         int shifts = 0;
510         int size = CropMath.getBitmapSize(image);
511         while (size > max_size) {
512             shifts++;
513             size /= 4;
514         }
515         Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
516                 image.getHeight() >> shifts, true);
517         if (ret == null) {
518             return null;
519         }
520         // Handle edge case for rounding.
521         if (CropMath.getBitmapSize(ret) > max_size) {
522             return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
523         }
524         return ret;
525     }
526
527     /**
528      * Gets the crop extras from the intent, or null if none exist.
529      */
530     protected static CropExtras getExtrasFromIntent(Intent intent) {
531         Bundle extras = intent.getExtras();
532         if (extras != null) {
533             return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
534                     extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
535                     extras.getBoolean(CropExtras.KEY_SCALE, true) &&
536                             extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
537                     extras.getInt(CropExtras.KEY_ASPECT_X, 0),
538                     extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
539                     extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
540                     extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
541                     (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
542                     extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
543                     extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
544                     extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
545                     extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
546         }
547         return null;
548     }
549
550     protected static CompressFormat convertExtensionToCompressFormat(String extension) {
551         return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
552     }
553
554     protected static String getFileExtension(String requestFormat) {
555         String outputFormat = (requestFormat == null)
556                 ? "jpg"
557                 : requestFormat;
558         outputFormat = outputFormat.toLowerCase();
559         return (outputFormat.equals("png") || outputFormat.equals("gif"))
560                 ? "png" // We don't support gif compression.
561                 : "jpg";
562     }
563
564     private RectF getBitmapCrop(RectF imageBounds) {
565         RectF crop = mCropView.getCrop();
566         RectF photo = mCropView.getPhoto();
567         if (crop == null || photo == null) {
568             Log.w(LOGTAG, "could not get crop");
569             return null;
570         }
571         RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
572         return scaledCrop;
573     }
574 }