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 onCreate(Bundle savedInstanceState)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 enableSave(boolean enable)126 private void enableSave(boolean enable) { 127 if (mSaveButton != null) { 128 mSaveButton.setEnabled(enable); 129 } 130 } 131 132 @Override onDestroy()133 protected void onDestroy() { 134 if (mLoadBitmapTask != null) { 135 mLoadBitmapTask.cancel(false); 136 } 137 super.onDestroy(); 138 } 139 140 @Override onConfigurationChanged(Configuration newConfig)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 */ pickImage()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 onActivityResult(int requestCode, int resultCode, Intent data)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 */ getScreenImageSize()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 */ startLoadBitmap(Uri uri)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 */ doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation)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 */ cannotLoadImage()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 LoadBitmapTask()254 public LoadBitmapTask() { 255 mBitmapSize = getScreenImageSize(); 256 mContext = getApplicationContext(); 257 mOriginalBounds = new Rect(); 258 mOrientation = 0; 259 } 260 261 @Override doInBackground(Uri... params)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 onPostExecute(Bitmap result)271 protected void onPostExecute(Bitmap result) { 272 doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation); 273 } 274 } 275 startFinishOutput()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 startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri, RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format, int rotation)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 doneBitmapIO(boolean success, Intent intent)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 regenerateInputStream()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 BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags, RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation, int outputX, int outputY)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 doInBackground(Bitmap... params)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 onPostExecute(Boolean result)602 protected void onPostExecute(Boolean result) { 603 Utils.closeSilently(mOutStream); 604 Utils.closeSilently(mInStream); 605 doneBitmapIO(result.booleanValue(), mResultIntent); 606 } 607 608 } 609 done()610 private void done() { 611 finish(); 612 } 613 getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds)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 getDownsampledBitmap(Bitmap image, int max_size)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 */ getExtrasFromIntent(Intent intent)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 convertExtensionToCompressFormat(String extension)671 protected static CompressFormat convertExtensionToCompressFormat(String extension) { 672 return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; 673 } 674 getFileExtension(String requestFormat)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 getBitmapCrop(RectF imageBounds)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