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