Home | History | Annotate | Download | only in crop
      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
     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 
    129     private void enableSave(boolean enable) {
    130         if (mSaveButton != null) {
    131             mSaveButton.setEnabled(enable);
    132         }
    133     }
    134 
    135     @Override
    136     protected void onDestroy() {
    137         if (mLoadBitmapTask != null) {
    138             mLoadBitmapTask.cancel(false);
    139         }
    140         super.onDestroy();
    141     }
    142 
    143     @Override
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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 
    257         public LoadBitmapTask() {
    258             mBitmapSize = getScreenImageSize();
    259             mContext = getApplicationContext();
    260             mOriginalBounds = new Rect();
    261             mOrientation = 0;
    262         }
    263 
    264         @Override
    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
    274         protected void onPostExecute(Bitmap result) {
    275             doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
    276         }
    277     }
    278 
    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 
    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 
    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
    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 
    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
    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
    605         protected void onPostExecute(Boolean result) {
    606             Utils.closeSilently(mOutStream);
    607             Utils.closeSilently(mInStream);
    608             doneBitmapIO(result.booleanValue(), mResultIntent);
    609         }
    610 
    611     }
    612 
    613     private void done() {
    614         finish();
    615     }
    616 
    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 
    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      */
    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 
    674     protected static CompressFormat convertExtensionToCompressFormat(String extension) {
    675         return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
    676     }
    677 
    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 
    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