Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2007 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;
     18 
     19 import com.android.gallery.R;
     20 
     21 import com.android.camera.gallery.IImage;
     22 import com.android.camera.gallery.IImageList;
     23 
     24 import android.app.WallpaperManager;
     25 import android.content.ContentResolver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.graphics.Bitmap;
     29 import android.graphics.Canvas;
     30 import android.graphics.Matrix;
     31 import android.graphics.Path;
     32 import android.graphics.PointF;
     33 import android.graphics.PorterDuff;
     34 import android.graphics.Rect;
     35 import android.graphics.RectF;
     36 import android.graphics.Region;
     37 import android.media.FaceDetector;
     38 import android.net.Uri;
     39 import android.os.Bundle;
     40 import android.os.Handler;
     41 import android.provider.MediaStore;
     42 import android.util.AttributeSet;
     43 import android.util.Log;
     44 import android.view.MotionEvent;
     45 import android.view.View;
     46 import android.view.Window;
     47 import android.view.WindowManager;
     48 import android.widget.Toast;
     49 
     50 import java.io.File;
     51 import java.io.IOException;
     52 import java.io.OutputStream;
     53 import java.util.ArrayList;
     54 import java.util.concurrent.CountDownLatch;
     55 
     56 /**
     57  * The activity can crop specific region of interest from an image.
     58  */
     59 public class CropImage extends MonitoredActivity {
     60     private static final String TAG = "CropImage";
     61 
     62     // These are various options can be specified in the intent.
     63     private Bitmap.CompressFormat mOutputFormat =
     64             Bitmap.CompressFormat.JPEG; // only used with mSaveUri
     65     private Uri mSaveUri = null;
     66     private boolean mSetWallpaper = false;
     67     private int mAspectX, mAspectY;
     68     private boolean mDoFaceDetection = true;
     69     private boolean mCircleCrop = false;
     70     private final Handler mHandler = new Handler();
     71 
     72     // These options specifiy the output image size and whether we should
     73     // scale the output to fit it (or just crop it).
     74     private int mOutputX, mOutputY;
     75     private boolean mScale;
     76     private boolean mScaleUp = true;
     77 
     78     boolean mWaitingToPick; // Whether we are wait the user to pick a face.
     79     boolean mSaving;  // Whether the "save" button is already clicked.
     80 
     81     private CropImageView mImageView;
     82     private ContentResolver mContentResolver;
     83 
     84     private Bitmap mBitmap;
     85     HighlightView mCrop;
     86 
     87     private IImageList mAllImages;
     88     private IImage mImage;
     89 
     90     @Override
     91     public void onCreate(Bundle icicle) {
     92         super.onCreate(icicle);
     93         mContentResolver = getContentResolver();
     94 
     95         requestWindowFeature(Window.FEATURE_NO_TITLE);
     96         setContentView(R.layout.cropimage);
     97 
     98         mImageView = (CropImageView) findViewById(R.id.image);
     99 
    100         MenuHelper.showStorageToast(this);
    101 
    102         Intent intent = getIntent();
    103         Bundle extras = intent.getExtras();
    104 
    105         if (extras != null) {
    106             if (extras.getString("circleCrop") != null) {
    107                 mCircleCrop = true;
    108                 mAspectX = 1;
    109                 mAspectY = 1;
    110             }
    111             mSaveUri = (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT);
    112             if (mSaveUri != null) {
    113                 String outputFormatString = extras.getString("outputFormat");
    114                 if (outputFormatString != null) {
    115                     mOutputFormat = Bitmap.CompressFormat.valueOf(
    116                             outputFormatString);
    117                 }
    118             } else {
    119                 mSetWallpaper = extras.getBoolean("setWallpaper");
    120             }
    121             mBitmap = (Bitmap) extras.getParcelable("data");
    122             mAspectX = extras.getInt("aspectX");
    123             mAspectY = extras.getInt("aspectY");
    124             mOutputX = extras.getInt("outputX");
    125             mOutputY = extras.getInt("outputY");
    126             mScale = extras.getBoolean("scale", true);
    127             mScaleUp = extras.getBoolean("scaleUpIfNeeded", true);
    128             mDoFaceDetection = extras.containsKey("noFaceDetection")
    129                     ? !extras.getBoolean("noFaceDetection")
    130                     : true;
    131         }
    132 
    133         if (mBitmap == null) {
    134             Uri target = intent.getData();
    135             mAllImages = ImageManager.makeImageList(mContentResolver, target,
    136                     ImageManager.SORT_ASCENDING);
    137             mImage = mAllImages.getImageForUri(target);
    138             if (mImage != null) {
    139                 // Don't read in really large bitmaps. Use the (big) thumbnail
    140                 // instead.
    141                 // TODO when saving the resulting bitmap use the
    142                 // decode/crop/encode api so we don't lose any resolution.
    143                 mBitmap = mImage.thumbBitmap(IImage.ROTATE_AS_NEEDED);
    144             }
    145         }
    146 
    147         if (mBitmap == null) {
    148             finish();
    149             return;
    150         }
    151 
    152         // Make UI fullscreen.
    153         getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    154 
    155         findViewById(R.id.discard).setOnClickListener(
    156                 new View.OnClickListener() {
    157                     public void onClick(View v) {
    158                         setResult(RESULT_CANCELED);
    159                         finish();
    160                     }
    161                 });
    162 
    163         findViewById(R.id.save).setOnClickListener(
    164                 new View.OnClickListener() {
    165                     public void onClick(View v) {
    166                         onSaveClicked();
    167                     }
    168                 });
    169 
    170         startFaceDetection();
    171     }
    172 
    173     private void startFaceDetection() {
    174         if (isFinishing()) {
    175             return;
    176         }
    177 
    178         mImageView.setImageBitmapResetBase(mBitmap, true);
    179 
    180         Util.startBackgroundJob(this, null,
    181                 getResources().getString(R.string.runningFaceDetection),
    182                 new Runnable() {
    183             public void run() {
    184                 final CountDownLatch latch = new CountDownLatch(1);
    185                 final Bitmap b = (mImage != null)
    186                         ? mImage.fullSizeBitmap(IImage.UNCONSTRAINED,
    187                         1024 * 1024)
    188                         : mBitmap;
    189                 mHandler.post(new Runnable() {
    190                     public void run() {
    191                         if (b != mBitmap && b != null) {
    192                             mImageView.setImageBitmapResetBase(b, true);
    193                             mBitmap.recycle();
    194                             mBitmap = b;
    195                         }
    196                         if (mImageView.getScale() == 1F) {
    197                             mImageView.center(true, true);
    198                         }
    199                         latch.countDown();
    200                     }
    201                 });
    202                 try {
    203                     latch.await();
    204                 } catch (InterruptedException e) {
    205                     throw new RuntimeException(e);
    206                 }
    207                 mRunFaceDetection.run();
    208             }
    209         }, mHandler);
    210     }
    211 
    212     private void onSaveClicked() {
    213         // TODO this code needs to change to use the decode/crop/encode single
    214         // step api so that we don't require that the whole (possibly large)
    215         // bitmap doesn't have to be read into memory
    216         if (mCrop == null) {
    217             return;
    218         }
    219 
    220         if (mSaving) return;
    221         mSaving = true;
    222 
    223         Bitmap croppedImage;
    224 
    225         // If the output is required to a specific size, create an new image
    226         // with the cropped image in the center and the extra space filled.
    227         if (mOutputX != 0 && mOutputY != 0 && !mScale) {
    228             // Don't scale the image but instead fill it so it's the
    229             // required dimension
    230             croppedImage = Bitmap.createBitmap(mOutputX, mOutputY,
    231                     Bitmap.Config.RGB_565);
    232             Canvas canvas = new Canvas(croppedImage);
    233 
    234             Rect srcRect = mCrop.getCropRect();
    235             Rect dstRect = new Rect(0, 0, mOutputX, mOutputY);
    236 
    237             int dx = (srcRect.width() - dstRect.width()) / 2;
    238             int dy = (srcRect.height() - dstRect.height()) / 2;
    239 
    240             // If the srcRect is too big, use the center part of it.
    241             srcRect.inset(Math.max(0, dx), Math.max(0, dy));
    242 
    243             // If the dstRect is too big, use the center part of it.
    244             dstRect.inset(Math.max(0, -dx), Math.max(0, -dy));
    245 
    246             // Draw the cropped bitmap in the center
    247             canvas.drawBitmap(mBitmap, srcRect, dstRect, null);
    248 
    249             // Release bitmap memory as soon as possible
    250             mImageView.clear();
    251             mBitmap.recycle();
    252         } else {
    253             Rect r = mCrop.getCropRect();
    254 
    255             int width = r.width();
    256             int height = r.height();
    257 
    258             // If we are circle cropping, we want alpha channel, which is the
    259             // third param here.
    260             croppedImage = Bitmap.createBitmap(width, height,
    261                     mCircleCrop
    262                     ? Bitmap.Config.ARGB_8888
    263                     : Bitmap.Config.RGB_565);
    264 
    265             Canvas canvas = new Canvas(croppedImage);
    266             Rect dstRect = new Rect(0, 0, width, height);
    267             canvas.drawBitmap(mBitmap, r, dstRect, null);
    268 
    269             // Release bitmap memory as soon as possible
    270             mImageView.clear();
    271             mBitmap.recycle();
    272 
    273             if (mCircleCrop) {
    274                 // OK, so what's all this about?
    275                 // Bitmaps are inherently rectangular but we want to return
    276                 // something that's basically a circle.  So we fill in the
    277                 // area around the circle with alpha.  Note the all important
    278                 // PortDuff.Mode.CLEAR.
    279                 Canvas c = new Canvas(croppedImage);
    280                 Path p = new Path();
    281                 p.addCircle(width / 2F, height / 2F, width / 2F,
    282                         Path.Direction.CW);
    283                 c.clipPath(p, Region.Op.DIFFERENCE);
    284                 c.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
    285             }
    286 
    287             // If the required dimension is specified, scale the image.
    288             if (mOutputX != 0 && mOutputY != 0 && mScale) {
    289                 croppedImage = Util.transform(new Matrix(), croppedImage,
    290                         mOutputX, mOutputY, mScaleUp, Util.RECYCLE_INPUT);
    291             }
    292         }
    293 
    294         mImageView.setImageBitmapResetBase(croppedImage, true);
    295         mImageView.center(true, true);
    296         mImageView.mHighlightViews.clear();
    297 
    298         // Return the cropped image directly or save it to the specified URI.
    299         Bundle myExtras = getIntent().getExtras();
    300         if (myExtras != null && (myExtras.getParcelable("data") != null
    301                 || myExtras.getBoolean("return-data"))) {
    302             Bundle extras = new Bundle();
    303             extras.putParcelable("data", croppedImage);
    304             setResult(RESULT_OK,
    305                     (new Intent()).setAction("inline-data").putExtras(extras));
    306             finish();
    307         } else {
    308             final Bitmap b = croppedImage;
    309             final int msdId = mSetWallpaper
    310                     ? R.string.wallpaper
    311                     : R.string.savingImage;
    312             Util.startBackgroundJob(this, null,
    313                     getResources().getString(msdId),
    314                     new Runnable() {
    315                 public void run() {
    316                     saveOutput(b);
    317                 }
    318             }, mHandler);
    319         }
    320     }
    321 
    322     private void saveOutput(Bitmap croppedImage) {
    323         if (mSaveUri != null) {
    324             OutputStream outputStream = null;
    325             try {
    326                 outputStream = mContentResolver.openOutputStream(mSaveUri);
    327                 if (outputStream != null) {
    328                     croppedImage.compress(mOutputFormat, 75, outputStream);
    329                 }
    330             } catch (IOException ex) {
    331                 // TODO: report error to caller
    332                 Log.e(TAG, "Cannot open file: " + mSaveUri, ex);
    333             } finally {
    334                 Util.closeSilently(outputStream);
    335             }
    336             Bundle extras = new Bundle();
    337             setResult(RESULT_OK, new Intent(mSaveUri.toString())
    338                     .putExtras(extras));
    339         } else if (mSetWallpaper) {
    340             try {
    341                 WallpaperManager.getInstance(this).setBitmap(croppedImage);
    342                 setResult(RESULT_OK);
    343             } catch (IOException e) {
    344                 Log.e(TAG, "Failed to set wallpaper.", e);
    345                 setResult(RESULT_CANCELED);
    346             }
    347         } else {
    348             Bundle extras = new Bundle();
    349             extras.putString("rect", mCrop.getCropRect().toString());
    350 
    351             File oldPath = new File(mImage.getDataPath());
    352             File directory = new File(oldPath.getParent());
    353 
    354             int x = 0;
    355             String fileName = oldPath.getName();
    356             fileName = fileName.substring(0, fileName.lastIndexOf("."));
    357 
    358             // Try file-1.jpg, file-2.jpg, ... until we find a filename which
    359             // does not exist yet.
    360             while (true) {
    361                 x += 1;
    362                 String candidate = directory.toString()
    363                         + "/" + fileName + "-" + x + ".jpg";
    364                 boolean exists = (new File(candidate)).exists();
    365                 if (!exists) {
    366                     break;
    367                 }
    368             }
    369 
    370             try {
    371                 int[] degree = new int[1];
    372                 Uri newUri = ImageManager.addImage(
    373                         mContentResolver,
    374                         mImage.getTitle(),
    375                         mImage.getDateTaken(),
    376                         null,    // TODO this null is going to cause us to lose
    377                                  // the location (gps).
    378                         directory.toString(), fileName + "-" + x + ".jpg",
    379                         croppedImage, null,
    380                         degree);
    381 
    382                 setResult(RESULT_OK, new Intent()
    383                         .setAction(newUri.toString())
    384                         .putExtras(extras));
    385             } catch (Exception ex) {
    386                 // basically ignore this or put up
    387                 // some ui saying we failed
    388                 Log.e(TAG, "store image fail, continue anyway", ex);
    389             }
    390         }
    391 
    392         final Bitmap b = croppedImage;
    393         mHandler.post(new Runnable() {
    394             public void run() {
    395                 mImageView.clear();
    396                 b.recycle();
    397             }
    398         });
    399 
    400         finish();
    401     }
    402 
    403     @Override
    404     protected void onPause() {
    405         super.onPause();
    406     }
    407 
    408     @Override
    409     protected void onDestroy() {
    410         if (mAllImages != null) {
    411             mAllImages.close();
    412         }
    413         super.onDestroy();
    414     }
    415 
    416     Runnable mRunFaceDetection = new Runnable() {
    417         @SuppressWarnings("hiding")
    418         float mScale = 1F;
    419         Matrix mImageMatrix;
    420         FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
    421         int mNumFaces;
    422 
    423         // For each face, we create a HightlightView for it.
    424         private void handleFace(FaceDetector.Face f) {
    425             PointF midPoint = new PointF();
    426 
    427             int r = ((int) (f.eyesDistance() * mScale)) * 2;
    428             f.getMidPoint(midPoint);
    429             midPoint.x *= mScale;
    430             midPoint.y *= mScale;
    431 
    432             int midX = (int) midPoint.x;
    433             int midY = (int) midPoint.y;
    434 
    435             HighlightView hv = new HighlightView(mImageView);
    436 
    437             int width = mBitmap.getWidth();
    438             int height = mBitmap.getHeight();
    439 
    440             Rect imageRect = new Rect(0, 0, width, height);
    441 
    442             RectF faceRect = new RectF(midX, midY, midX, midY);
    443             faceRect.inset(-r, -r);
    444             if (faceRect.left < 0) {
    445                 faceRect.inset(-faceRect.left, -faceRect.left);
    446             }
    447 
    448             if (faceRect.top < 0) {
    449                 faceRect.inset(-faceRect.top, -faceRect.top);
    450             }
    451 
    452             if (faceRect.right > imageRect.right) {
    453                 faceRect.inset(faceRect.right - imageRect.right,
    454                                faceRect.right - imageRect.right);
    455             }
    456 
    457             if (faceRect.bottom > imageRect.bottom) {
    458                 faceRect.inset(faceRect.bottom - imageRect.bottom,
    459                                faceRect.bottom - imageRect.bottom);
    460             }
    461 
    462             hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop,
    463                      mAspectX != 0 && mAspectY != 0);
    464 
    465             mImageView.add(hv);
    466         }
    467 
    468         // Create a default HightlightView if we found no face in the picture.
    469         private void makeDefault() {
    470             HighlightView hv = new HighlightView(mImageView);
    471 
    472             int width = mBitmap.getWidth();
    473             int height = mBitmap.getHeight();
    474 
    475             Rect imageRect = new Rect(0, 0, width, height);
    476 
    477             // make the default size about 4/5 of the width or height
    478             int cropWidth = Math.min(width, height) * 4 / 5;
    479             int cropHeight = cropWidth;
    480 
    481             if (mAspectX != 0 && mAspectY != 0) {
    482                 if (mAspectX > mAspectY) {
    483                     cropHeight = cropWidth * mAspectY / mAspectX;
    484                 } else {
    485                     cropWidth = cropHeight * mAspectX / mAspectY;
    486                 }
    487             }
    488 
    489             int x = (width - cropWidth) / 2;
    490             int y = (height - cropHeight) / 2;
    491 
    492             RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
    493             hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop,
    494                      mAspectX != 0 && mAspectY != 0);
    495             mImageView.add(hv);
    496         }
    497 
    498         // Scale the image down for faster face detection.
    499         private Bitmap prepareBitmap() {
    500             if (mBitmap == null) {
    501                 return null;
    502             }
    503 
    504             // 256 pixels wide is enough.
    505             if (mBitmap.getWidth() > 256) {
    506                 mScale = 256.0F / mBitmap.getWidth();
    507             }
    508             Matrix matrix = new Matrix();
    509             matrix.setScale(mScale, mScale);
    510             Bitmap faceBitmap = Bitmap.createBitmap(mBitmap, 0, 0, mBitmap
    511                     .getWidth(), mBitmap.getHeight(), matrix, true);
    512             return faceBitmap;
    513         }
    514 
    515         public void run() {
    516             mImageMatrix = mImageView.getImageMatrix();
    517             Bitmap faceBitmap = prepareBitmap();
    518 
    519             mScale = 1.0F / mScale;
    520             if (faceBitmap != null && mDoFaceDetection) {
    521                 FaceDetector detector = new FaceDetector(faceBitmap.getWidth(),
    522                         faceBitmap.getHeight(), mFaces.length);
    523                 mNumFaces = detector.findFaces(faceBitmap, mFaces);
    524             }
    525 
    526             if (faceBitmap != null && faceBitmap != mBitmap) {
    527                 faceBitmap.recycle();
    528             }
    529 
    530             mHandler.post(new Runnable() {
    531                 public void run() {
    532                     mWaitingToPick = mNumFaces > 1;
    533                     if (mNumFaces > 0) {
    534                         for (int i = 0; i < mNumFaces; i++) {
    535                             handleFace(mFaces[i]);
    536                         }
    537                     } else {
    538                         makeDefault();
    539                     }
    540                     mImageView.invalidate();
    541                     if (mImageView.mHighlightViews.size() == 1) {
    542                         mCrop = mImageView.mHighlightViews.get(0);
    543                         mCrop.setFocus(true);
    544                     }
    545 
    546                     if (mNumFaces > 1) {
    547                         Toast t = Toast.makeText(CropImage.this,
    548                                 R.string.multiface_crop_help,
    549                                 Toast.LENGTH_SHORT);
    550                         t.show();
    551                     }
    552                 }
    553             });
    554         }
    555     };
    556 }
    557 
    558 class CropImageView extends ImageViewTouchBase {
    559     ArrayList<HighlightView> mHighlightViews = new ArrayList<HighlightView>();
    560     HighlightView mMotionHighlightView = null;
    561     float mLastX, mLastY;
    562     int mMotionEdge;
    563 
    564     @Override
    565     protected void onLayout(boolean changed, int left, int top,
    566                             int right, int bottom) {
    567         super.onLayout(changed, left, top, right, bottom);
    568         if (mBitmapDisplayed.getBitmap() != null) {
    569             for (HighlightView hv : mHighlightViews) {
    570                 hv.mMatrix.set(getImageMatrix());
    571                 hv.invalidate();
    572                 if (hv.mIsFocused) {
    573                     centerBasedOnHighlightView(hv);
    574                 }
    575             }
    576         }
    577     }
    578 
    579     public CropImageView(Context context, AttributeSet attrs) {
    580         super(context, attrs);
    581     }
    582 
    583     @Override
    584     protected void zoomTo(float scale, float centerX, float centerY) {
    585         super.zoomTo(scale, centerX, centerY);
    586         for (HighlightView hv : mHighlightViews) {
    587             hv.mMatrix.set(getImageMatrix());
    588             hv.invalidate();
    589         }
    590     }
    591 
    592     @Override
    593     protected void zoomIn() {
    594         super.zoomIn();
    595         for (HighlightView hv : mHighlightViews) {
    596             hv.mMatrix.set(getImageMatrix());
    597             hv.invalidate();
    598         }
    599     }
    600 
    601     @Override
    602     protected void zoomOut() {
    603         super.zoomOut();
    604         for (HighlightView hv : mHighlightViews) {
    605             hv.mMatrix.set(getImageMatrix());
    606             hv.invalidate();
    607         }
    608     }
    609 
    610     @Override
    611     protected void postTranslate(float deltaX, float deltaY) {
    612         super.postTranslate(deltaX, deltaY);
    613         for (int i = 0; i < mHighlightViews.size(); i++) {
    614             HighlightView hv = mHighlightViews.get(i);
    615             hv.mMatrix.postTranslate(deltaX, deltaY);
    616             hv.invalidate();
    617         }
    618     }
    619 
    620     // According to the event's position, change the focus to the first
    621     // hitting cropping rectangle.
    622     private void recomputeFocus(MotionEvent event) {
    623         for (int i = 0; i < mHighlightViews.size(); i++) {
    624             HighlightView hv = mHighlightViews.get(i);
    625             hv.setFocus(false);
    626             hv.invalidate();
    627         }
    628 
    629         for (int i = 0; i < mHighlightViews.size(); i++) {
    630             HighlightView hv = mHighlightViews.get(i);
    631             int edge = hv.getHit(event.getX(), event.getY());
    632             if (edge != HighlightView.GROW_NONE) {
    633                 if (!hv.hasFocus()) {
    634                     hv.setFocus(true);
    635                     hv.invalidate();
    636                 }
    637                 break;
    638             }
    639         }
    640         invalidate();
    641     }
    642 
    643     @Override
    644     public boolean onTouchEvent(MotionEvent event) {
    645         CropImage cropImage = (CropImage) mContext;
    646         if (cropImage.mSaving) {
    647             return false;
    648         }
    649 
    650         switch (event.getAction()) {
    651             case MotionEvent.ACTION_DOWN:
    652                 if (cropImage.mWaitingToPick) {
    653                     recomputeFocus(event);
    654                 } else {
    655                     for (int i = 0; i < mHighlightViews.size(); i++) {
    656                         HighlightView hv = mHighlightViews.get(i);
    657                         int edge = hv.getHit(event.getX(), event.getY());
    658                         if (edge != HighlightView.GROW_NONE) {
    659                             mMotionEdge = edge;
    660                             mMotionHighlightView = hv;
    661                             mLastX = event.getX();
    662                             mLastY = event.getY();
    663                             mMotionHighlightView.setMode(
    664                                     (edge == HighlightView.MOVE)
    665                                     ? HighlightView.ModifyMode.Move
    666                                     : HighlightView.ModifyMode.Grow);
    667                             break;
    668                         }
    669                     }
    670                 }
    671                 break;
    672             case MotionEvent.ACTION_UP:
    673                 if (cropImage.mWaitingToPick) {
    674                     for (int i = 0; i < mHighlightViews.size(); i++) {
    675                         HighlightView hv = mHighlightViews.get(i);
    676                         if (hv.hasFocus()) {
    677                             cropImage.mCrop = hv;
    678                             for (int j = 0; j < mHighlightViews.size(); j++) {
    679                                 if (j == i) {
    680                                     continue;
    681                                 }
    682                                 mHighlightViews.get(j).setHidden(true);
    683                             }
    684                             centerBasedOnHighlightView(hv);
    685                             ((CropImage) mContext).mWaitingToPick = false;
    686                             return true;
    687                         }
    688                     }
    689                 } else if (mMotionHighlightView != null) {
    690                     centerBasedOnHighlightView(mMotionHighlightView);
    691                     mMotionHighlightView.setMode(
    692                             HighlightView.ModifyMode.None);
    693                 }
    694                 mMotionHighlightView = null;
    695                 break;
    696             case MotionEvent.ACTION_MOVE:
    697                 if (cropImage.mWaitingToPick) {
    698                     recomputeFocus(event);
    699                 } else if (mMotionHighlightView != null) {
    700                     mMotionHighlightView.handleMotion(mMotionEdge,
    701                             event.getX() - mLastX,
    702                             event.getY() - mLastY);
    703                     mLastX = event.getX();
    704                     mLastY = event.getY();
    705 
    706                     if (true) {
    707                         // This section of code is optional. It has some user
    708                         // benefit in that moving the crop rectangle against
    709                         // the edge of the screen causes scrolling but it means
    710                         // that the crop rectangle is no longer fixed under
    711                         // the user's finger.
    712                         ensureVisible(mMotionHighlightView);
    713                     }
    714                 }
    715                 break;
    716         }
    717 
    718         switch (event.getAction()) {
    719             case MotionEvent.ACTION_UP:
    720                 center(true, true);
    721                 break;
    722             case MotionEvent.ACTION_MOVE:
    723                 // if we're not zoomed then there's no point in even allowing
    724                 // the user to move the image around.  This call to center puts
    725                 // it back to the normalized location (with false meaning don't
    726                 // animate).
    727                 if (getScale() == 1F) {
    728                     center(true, true);
    729                 }
    730                 break;
    731         }
    732 
    733         return true;
    734     }
    735 
    736     // Pan the displayed image to make sure the cropping rectangle is visible.
    737     private void ensureVisible(HighlightView hv) {
    738         Rect r = hv.mDrawRect;
    739 
    740         int panDeltaX1 = Math.max(0, mLeft - r.left);
    741         int panDeltaX2 = Math.min(0, mRight - r.right);
    742 
    743         int panDeltaY1 = Math.max(0, mTop - r.top);
    744         int panDeltaY2 = Math.min(0, mBottom - r.bottom);
    745 
    746         int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
    747         int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;
    748 
    749         if (panDeltaX != 0 || panDeltaY != 0) {
    750             panBy(panDeltaX, panDeltaY);
    751         }
    752     }
    753 
    754     // If the cropping rectangle's size changed significantly, change the
    755     // view's center and scale according to the cropping rectangle.
    756     private void centerBasedOnHighlightView(HighlightView hv) {
    757         Rect drawRect = hv.mDrawRect;
    758 
    759         float width = drawRect.width();
    760         float height = drawRect.height();
    761 
    762         float thisWidth = getWidth();
    763         float thisHeight = getHeight();
    764 
    765         float z1 = thisWidth / width * .6F;
    766         float z2 = thisHeight / height * .6F;
    767 
    768         float zoom = Math.min(z1, z2);
    769         zoom = zoom * this.getScale();
    770         zoom = Math.max(1F, zoom);
    771 
    772         if ((Math.abs(zoom - getScale()) / zoom) > .1) {
    773             float [] coordinates = new float[] {hv.mCropRect.centerX(),
    774                                                 hv.mCropRect.centerY()};
    775             getImageMatrix().mapPoints(coordinates);
    776             zoomTo(zoom, coordinates[0], coordinates[1], 300F);
    777         }
    778 
    779         ensureVisible(hv);
    780     }
    781 
    782     @Override
    783     protected void onDraw(Canvas canvas) {
    784         super.onDraw(canvas);
    785         for (int i = 0; i < mHighlightViews.size(); i++) {
    786             mHighlightViews.get(i).draw(canvas);
    787         }
    788     }
    789 
    790     public void add(HighlightView hv) {
    791         mHighlightViews.add(hv);
    792         invalidate();
    793     }
    794 }
    795