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.content.res.Configuration;
25 import android.graphics.Bitmap;
26 import android.graphics.Bitmap.CompressFormat;
27 import android.graphics.BitmapFactory;
28 import android.graphics.BitmapRegionDecoder;
29 import android.graphics.Canvas;
30 import android.graphics.Matrix;
31 import android.graphics.Paint;
32 import android.graphics.Rect;
33 import android.graphics.RectF;
34 import android.net.Uri;
35 import android.os.AsyncTask;
36 import android.os.Bundle;
37 import android.provider.MediaStore;
38 import android.util.DisplayMetrics;
39 import android.util.Log;
40 import android.view.View;
41 import android.view.View.OnClickListener;
42 import android.view.WindowManager;
43 import android.widget.Toast;
44 
45 import com.android.gallery3d.R;
46 import com.android.gallery3d.common.Utils;
47 import com.android.gallery3d.filtershow.cache.ImageLoader;
48 import com.android.gallery3d.filtershow.tools.SaveImage;
49 
50 import java.io.ByteArrayInputStream;
51 import java.io.ByteArrayOutputStream;
52 import java.io.FileNotFoundException;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.OutputStream;
56 
57 /**
58  * Activity for cropping an image.
59  */
60 public class CropActivity extends Activity {
61     private static final String LOGTAG = "CropActivity";
62     public static final String CROP_ACTION = "com.android.camera.action.CROP";
63     private CropExtras mCropExtras = null;
64     private LoadBitmapTask mLoadBitmapTask = null;
65 
66     private int mOutputX = 0;
67     private int mOutputY = 0;
68     private Bitmap mOriginalBitmap = null;
69     private RectF mOriginalBounds = null;
70     private int mOriginalRotation = 0;
71     private Uri mSourceUri = null;
72     private CropView mCropView = null;
73     private View mSaveButton = null;
74     private boolean finalIOGuard = false;
75 
76     private static final int SELECT_PICTURE = 1; // request code for picker
77 
78     private static final int DEFAULT_COMPRESS_QUALITY = 90;
79     /**
80      * The maximum bitmap size we allow to be returned through the intent.
81      * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
82      * have some overhead to hit so that we go way below the limit here to make
83      * sure the intent stays below 1MB.We should consider just returning a byte
84      * array instead of a Bitmap instance to avoid overhead.
85      */
86     public static final int MAX_BMAP_IN_INTENT = 750000;
87 
88     // Flags
89     private static final int DO_SET_WALLPAPER = 1;
90     private static final int DO_RETURN_DATA = 1 << 1;
91     private static final int DO_EXTRA_OUTPUT = 1 << 2;
92 
93     private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
94 
95     @Override
onCreate(Bundle savedInstanceState)96     public void onCreate(Bundle savedInstanceState) {
97         super.onCreate(savedInstanceState);
98         Intent intent = getIntent();
99         setResult(RESULT_CANCELED, new Intent());
100         mCropExtras = getExtrasFromIntent(intent);
101         if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
102             getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
103         }
104 
105         setContentView(R.layout.crop_activity);
106         mCropView = (CropView) findViewById(R.id.cropView);
107 
108         ActionBar actionBar = getActionBar();
109         if (actionBar != null) {
110             actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
111             actionBar.setCustomView(R.layout.filtershow_actionbar);
112 
113             View mSaveButton = actionBar.getCustomView();
114             mSaveButton.setOnClickListener(new OnClickListener() {
115                 @Override
116                 public void onClick(View view) {
117                     startFinishOutput();
118                 }
119             });
120         }
121         if (intent.getData() != null) {
122             mSourceUri = intent.getData();
123             startLoadBitmap(mSourceUri);
124         } else {
125             pickImage();
126         }
127     }
128 
enableSave(boolean enable)129     private void enableSave(boolean enable) {
130         if (mSaveButton != null) {
131             mSaveButton.setEnabled(enable);
132         }
133     }
134 
135     @Override
onDestroy()136     protected void onDestroy() {
137         if (mLoadBitmapTask != null) {
138             mLoadBitmapTask.cancel(false);
139         }
140         super.onDestroy();
141     }
142 
143     @Override
onConfigurationChanged(Configuration newConfig)144     public void onConfigurationChanged (Configuration newConfig) {
145         super.onConfigurationChanged(newConfig);
146         mCropView.configChanged();
147     }
148 
149     /**
150      * Opens a selector in Gallery to chose an image for use when none was given
151      * in the CROP intent.
152      */
pickImage()153     private void pickImage() {
154         Intent intent = new Intent();
155         intent.setType("image/*");
156         intent.setAction(Intent.ACTION_GET_CONTENT);
157         startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
158                 SELECT_PICTURE);
159     }
160 
161     /**
162      * Callback for pickImage().
163      */
164     @Override
onActivityResult(int requestCode, int resultCode, Intent data)165     public void onActivityResult(int requestCode, int resultCode, Intent data) {
166         if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
167             mSourceUri = data.getData();
168             startLoadBitmap(mSourceUri);
169         }
170     }
171 
172     /**
173      * Gets screen size metric.
174      */
getScreenImageSize()175     private int getScreenImageSize() {
176         DisplayMetrics outMetrics = new DisplayMetrics();
177         getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
178         return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
179     }
180 
181     /**
182      * Method that loads a bitmap in an async task.
183      */
startLoadBitmap(Uri uri)184     private void startLoadBitmap(Uri uri) {
185         if (uri != null) {
186             enableSave(false);
187             final View loading = findViewById(R.id.loading);
188             loading.setVisibility(View.VISIBLE);
189             mLoadBitmapTask = new LoadBitmapTask();
190             mLoadBitmapTask.execute(uri);
191         } else {
192             cannotLoadImage();
193             done();
194         }
195     }
196 
197     /**
198      * Method called on UI thread with loaded bitmap.
199      */
doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation)200     private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) {
201         final View loading = findViewById(R.id.loading);
202         loading.setVisibility(View.GONE);
203         mOriginalBitmap = bitmap;
204         mOriginalBounds = bounds;
205         mOriginalRotation = orientation;
206         if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
207             RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
208             mCropView.initialize(bitmap, imgBounds, imgBounds, orientation);
209             if (mCropExtras != null) {
210                 int aspectX = mCropExtras.getAspectX();
211                 int aspectY = mCropExtras.getAspectY();
212                 mOutputX = mCropExtras.getOutputX();
213                 mOutputY = mCropExtras.getOutputY();
214                 if (mOutputX > 0 && mOutputY > 0) {
215                     mCropView.applyAspect(mOutputX, mOutputY);
216 
217                 }
218                 float spotX = mCropExtras.getSpotlightX();
219                 float spotY = mCropExtras.getSpotlightY();
220                 if (spotX > 0 && spotY > 0) {
221                     mCropView.setWallpaperSpotlight(spotX, spotY);
222                 }
223                 if (aspectX > 0 && aspectY > 0) {
224                     mCropView.applyAspect(aspectX, aspectY);
225                 }
226             }
227             enableSave(true);
228         } else {
229             Log.w(LOGTAG, "could not load image for cropping");
230             cannotLoadImage();
231             setResult(RESULT_CANCELED, new Intent());
232             done();
233         }
234     }
235 
236     /**
237      * Display toast for image loading failure.
238      */
cannotLoadImage()239     private void cannotLoadImage() {
240         CharSequence text = getString(R.string.cannot_load_image);
241         Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
242         toast.show();
243     }
244 
245     /**
246      * AsyncTask for loading a bitmap into memory.
247      *
248      * @see #startLoadBitmap(Uri)
249      * @see #doneLoadBitmap(Bitmap)
250      */
251     private class LoadBitmapTask extends AsyncTask<Uri, Void, Bitmap> {
252         int mBitmapSize;
253         Context mContext;
254         Rect mOriginalBounds;
255         int mOrientation;
256 
LoadBitmapTask()257         public LoadBitmapTask() {
258             mBitmapSize = getScreenImageSize();
259             mContext = getApplicationContext();
260             mOriginalBounds = new Rect();
261             mOrientation = 0;
262         }
263 
264         @Override
doInBackground(Uri... params)265         protected Bitmap doInBackground(Uri... params) {
266             Uri uri = params[0];
267             Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize,
268                     mOriginalBounds, false);
269             mOrientation = ImageLoader.getMetadataRotation(mContext, uri);
270             return bmap;
271         }
272 
273         @Override
onPostExecute(Bitmap result)274         protected void onPostExecute(Bitmap result) {
275             doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
276         }
277     }
278 
startFinishOutput()279     protected void startFinishOutput() {
280         if (finalIOGuard) {
281             return;
282         } else {
283             finalIOGuard = true;
284         }
285         enableSave(false);
286         Uri destinationUri = null;
287         int flags = 0;
288         if (mOriginalBitmap != null && mCropExtras != null) {
289             if (mCropExtras.getExtraOutput() != null) {
290                 destinationUri = mCropExtras.getExtraOutput();
291                 if (destinationUri != null) {
292                     flags |= DO_EXTRA_OUTPUT;
293                 }
294             }
295             if (mCropExtras.getSetAsWallpaper()) {
296                 flags |= DO_SET_WALLPAPER;
297             }
298             if (mCropExtras.getReturnData()) {
299                 flags |= DO_RETURN_DATA;
300             }
301         }
302         if (flags == 0) {
303             destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri);
304             if (destinationUri != null) {
305                 flags |= DO_EXTRA_OUTPUT;
306             }
307         }
308         if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
309             RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
310             RectF crop = getBitmapCrop(photo);
311             startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
312                     photo, mOriginalBounds,
313                     (mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation);
314             return;
315         }
316         setResult(RESULT_CANCELED, new Intent());
317         done();
318         return;
319     }
320 
startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri, RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format, int rotation)321     private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
322             RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format,
323             int rotation) {
324         if (cropBounds == null || photoBounds == null || currentBitmap == null
325                 || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
326                 || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
327                 || photoBounds.height() == 0) {
328             return; // fail fast
329         }
330         if ((flags & FLAG_CHECK) == 0) {
331             return; // no output options
332         }
333         if ((flags & DO_SET_WALLPAPER) != 0) {
334             Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
335         }
336 
337         final View loading = findViewById(R.id.loading);
338         loading.setVisibility(View.VISIBLE);
339         BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
340                 photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
341         ioTask.execute(currentBitmap);
342     }
343 
doneBitmapIO(boolean success, Intent intent)344     private void doneBitmapIO(boolean success, Intent intent) {
345         final View loading = findViewById(R.id.loading);
346         loading.setVisibility(View.GONE);
347         if (success) {
348             setResult(RESULT_OK, intent);
349         } else {
350             setResult(RESULT_CANCELED, intent);
351         }
352         done();
353     }
354 
355     private class BitmapIOTask extends AsyncTask<Bitmap, Void, Boolean> {
356 
357         private final WallpaperManager mWPManager;
358         InputStream mInStream = null;
359         OutputStream mOutStream = null;
360         String mOutputFormat = null;
361         Uri mOutUri = null;
362         Uri mInUri = null;
363         int mFlags = 0;
364         RectF mCrop = null;
365         RectF mPhoto = null;
366         RectF mOrig = null;
367         Intent mResultIntent = null;
368         int mRotation = 0;
369 
370         // Helper to setup input stream
regenerateInputStream()371         private void regenerateInputStream() {
372             if (mInUri == null) {
373                 Log.w(LOGTAG, "cannot read original file, no input URI given");
374             } else {
375                 Utils.closeSilently(mInStream);
376                 try {
377                     mInStream = getContentResolver().openInputStream(mInUri);
378                 } catch (FileNotFoundException e) {
379                     Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
380                 }
381             }
382         }
383 
BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags, RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation, int outputX, int outputY)384         public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
385                 RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
386                 int outputX, int outputY) {
387             mOutputFormat = outputFormat;
388             mOutStream = null;
389             mOutUri = destUri;
390             mInUri = sourceUri;
391             mFlags = flags;
392             mCrop = cropBounds;
393             mPhoto = photoBounds;
394             mOrig = originalBitmapBounds;
395             mWPManager = WallpaperManager.getInstance(getApplicationContext());
396             mResultIntent = new Intent();
397             mRotation = (rotation < 0) ? -rotation : rotation;
398             mRotation %= 360;
399             mRotation = 90 * (int) (mRotation / 90);  // now mRotation is a multiple of 90
400             mOutputX = outputX;
401             mOutputY = outputY;
402 
403             if ((flags & DO_EXTRA_OUTPUT) != 0) {
404                 if (mOutUri == null) {
405                     Log.w(LOGTAG, "cannot write file, no output URI given");
406                 } else {
407                     try {
408                         mOutStream = getContentResolver().openOutputStream(mOutUri);
409                     } catch (FileNotFoundException e) {
410                         Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
411                     }
412                 }
413             }
414 
415             if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
416                 regenerateInputStream();
417             }
418         }
419 
420         @Override
doInBackground(Bitmap... params)421         protected Boolean doInBackground(Bitmap... params) {
422             boolean failure = false;
423             Bitmap img = params[0];
424 
425             // Set extra for crop bounds
426             if (mCrop != null && mPhoto != null && mOrig != null) {
427                 RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
428                 Matrix m = new Matrix();
429                 m.setRotate(mRotation);
430                 m.mapRect(trueCrop);
431                 if (trueCrop != null) {
432                     Rect rounded = new Rect();
433                     trueCrop.roundOut(rounded);
434                     mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
435                 }
436             }
437 
438             // Find the small cropped bitmap that is returned in the intent
439             if ((mFlags & DO_RETURN_DATA) != 0) {
440                 assert (img != null);
441                 Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
442                 if (ret != null) {
443                     ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
444                 }
445                 if (ret == null) {
446                     Log.w(LOGTAG, "could not downsample bitmap to return in data");
447                     failure = true;
448                 } else {
449                     if (mRotation > 0) {
450                         Matrix m = new Matrix();
451                         m.setRotate(mRotation);
452                         Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(),
453                                 ret.getHeight(), m, true);
454                         if (tmp != null) {
455                             ret = tmp;
456                         }
457                     }
458                     mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
459                 }
460             }
461 
462             // Do the large cropped bitmap and/or set the wallpaper
463             if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
464                 // Find crop bounds (scaled to original image size)
465                 RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
466                 if (trueCrop == null) {
467                     Log.w(LOGTAG, "cannot find crop for full size image");
468                     failure = true;
469                     return false;
470                 }
471                 Rect roundedTrueCrop = new Rect();
472                 trueCrop.roundOut(roundedTrueCrop);
473 
474                 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
475                     Log.w(LOGTAG, "crop has bad values for full size image");
476                     failure = true;
477                     return false;
478                 }
479 
480                 // Attempt to open a region decoder
481                 BitmapRegionDecoder decoder = null;
482                 try {
483                     decoder = BitmapRegionDecoder.newInstance(mInStream, true);
484                 } catch (IOException e) {
485                     Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
486                 }
487 
488                 Bitmap crop = null;
489                 if (decoder != null) {
490                     // Do region decoding to get crop bitmap
491                     BitmapFactory.Options options = new BitmapFactory.Options();
492                     options.inMutable = true;
493                     crop = decoder.decodeRegion(roundedTrueCrop, options);
494                     decoder.recycle();
495                 }
496 
497                 if (crop == null) {
498                     // BitmapRegionDecoder has failed, try to crop in-memory
499                     regenerateInputStream();
500                     Bitmap fullSize = null;
501                     if (mInStream != null) {
502                         fullSize = BitmapFactory.decodeStream(mInStream);
503                     }
504                     if (fullSize != null) {
505                         crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
506                                 roundedTrueCrop.top, roundedTrueCrop.width(),
507                                 roundedTrueCrop.height());
508                     }
509                 }
510 
511                 if (crop == null) {
512                     Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
513                     failure = true;
514                     return false;
515                 }
516                 if (mOutputX > 0 && mOutputY > 0) {
517                     Matrix m = new Matrix();
518                     RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
519                     if (mRotation > 0) {
520                         m.setRotate(mRotation);
521                         m.mapRect(cropRect);
522                     }
523                     RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
524                     m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
525                     m.preRotate(mRotation);
526                     Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
527                             (int) returnRect.height(), Bitmap.Config.ARGB_8888);
528                     if (tmp != null) {
529                         Canvas c = new Canvas(tmp);
530                         c.drawBitmap(crop, m, new Paint());
531                         crop = tmp;
532                     }
533                 } else if (mRotation > 0) {
534                     Matrix m = new Matrix();
535                     m.setRotate(mRotation);
536                     Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
537                             crop.getHeight(), m, true);
538                     if (tmp != null) {
539                         crop = tmp;
540                     }
541                 }
542                 // Get output compression format
543                 CompressFormat cf =
544                         convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
545 
546                 // If we only need to output to a URI, compress straight to file
547                 if (mFlags == DO_EXTRA_OUTPUT) {
548                     if (mOutStream == null
549                             || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
550                         Log.w(LOGTAG, "failed to compress bitmap to file: " + mOutUri.toString());
551                         failure = true;
552                     } else {
553                         mResultIntent.setData(mOutUri);
554                     }
555                 } else {
556                     // Compress to byte array
557                     ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
558                     if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
559 
560                         // If we need to output to a Uri, write compressed
561                         // bitmap out
562                         if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
563                             if (mOutStream == null) {
564                                 Log.w(LOGTAG,
565                                         "failed to compress bitmap to file: " + mOutUri.toString());
566                                 failure = true;
567                             } else {
568                                 try {
569                                     mOutStream.write(tmpOut.toByteArray());
570                                     mResultIntent.setData(mOutUri);
571                                 } catch (IOException e) {
572                                     Log.w(LOGTAG,
573                                             "failed to compress bitmap to file: "
574                                                     + mOutUri.toString(), e);
575                                     failure = true;
576                                 }
577                             }
578                         }
579 
580                         // If we need to set to the wallpaper, set it
581                         if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
582                             if (mWPManager == null) {
583                                 Log.w(LOGTAG, "no wallpaper manager");
584                                 failure = true;
585                             } else {
586                                 try {
587                                     mWPManager.setStream(new ByteArrayInputStream(tmpOut
588                                             .toByteArray()));
589                                 } catch (IOException e) {
590                                     Log.w(LOGTAG, "cannot write stream to wallpaper", e);
591                                     failure = true;
592                                 }
593                             }
594                         }
595                     } else {
596                         Log.w(LOGTAG, "cannot compress bitmap");
597                         failure = true;
598                     }
599                 }
600             }
601             return !failure; // True if any of the operations failed
602         }
603 
604         @Override
onPostExecute(Boolean result)605         protected void onPostExecute(Boolean result) {
606             Utils.closeSilently(mOutStream);
607             Utils.closeSilently(mInStream);
608             doneBitmapIO(result.booleanValue(), mResultIntent);
609         }
610 
611     }
612 
done()613     private void done() {
614         finish();
615     }
616 
getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds)617     protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
618         RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
619         RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
620         if (crop == null) {
621             return null;
622         }
623         Rect intCrop = new Rect();
624         crop.roundOut(intCrop);
625         return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
626                 intCrop.height());
627     }
628 
getDownsampledBitmap(Bitmap image, int max_size)629     protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
630         if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
631             throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
632         }
633         int shifts = 0;
634         int size = CropMath.getBitmapSize(image);
635         while (size > max_size) {
636             shifts++;
637             size /= 4;
638         }
639         Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
640                 image.getHeight() >> shifts, true);
641         if (ret == null) {
642             return null;
643         }
644         // Handle edge case for rounding.
645         if (CropMath.getBitmapSize(ret) > max_size) {
646             return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
647         }
648         return ret;
649     }
650 
651     /**
652      * Gets the crop extras from the intent, or null if none exist.
653      */
getExtrasFromIntent(Intent intent)654     protected static CropExtras getExtrasFromIntent(Intent intent) {
655         Bundle extras = intent.getExtras();
656         if (extras != null) {
657             return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
658                     extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
659                     extras.getBoolean(CropExtras.KEY_SCALE, true) &&
660                             extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
661                     extras.getInt(CropExtras.KEY_ASPECT_X, 0),
662                     extras.getInt(CropExtras.KEY_ASPECT_Y, 0),
663                     extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
664                     extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
665                     (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
666                     extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
667                     extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
668                     extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
669                     extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
670         }
671         return null;
672     }
673 
convertExtensionToCompressFormat(String extension)674     protected static CompressFormat convertExtensionToCompressFormat(String extension) {
675         return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
676     }
677 
getFileExtension(String requestFormat)678     protected static String getFileExtension(String requestFormat) {
679         String outputFormat = (requestFormat == null)
680                 ? "jpg"
681                 : requestFormat;
682         outputFormat = outputFormat.toLowerCase();
683         return (outputFormat.equals("png") || outputFormat.equals("gif"))
684                 ? "png" // We don't support gif compression.
685                 : "jpg";
686     }
687 
getBitmapCrop(RectF imageBounds)688     private RectF getBitmapCrop(RectF imageBounds) {
689         RectF crop = mCropView.getCrop();
690         RectF photo = mCropView.getPhoto();
691         if (crop == null || photo == null) {
692             Log.w(LOGTAG, "could not get crop");
693             return null;
694         }
695         RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
696         return scaledCrop;
697     }
698 }
699