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