WallpaperCropActivity.java revision 5ab7d37d903f336c5a96f8caca9b36439e76bdb4
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/* Copied from Launcher3 */ 17package com.android.wallpapercropper; 18 19import android.app.ActionBar; 20import android.app.Activity; 21import android.app.WallpaperManager; 22import android.content.Context; 23import android.content.Intent; 24import android.content.SharedPreferences; 25import android.content.res.Configuration; 26import android.content.res.Resources; 27import android.graphics.Bitmap; 28import android.graphics.Bitmap.CompressFormat; 29import android.graphics.BitmapFactory; 30import android.graphics.BitmapRegionDecoder; 31import android.graphics.Canvas; 32import android.graphics.Matrix; 33import android.graphics.Paint; 34import android.graphics.Point; 35import android.graphics.Rect; 36import android.graphics.RectF; 37import android.net.Uri; 38import android.os.AsyncTask; 39import android.os.Bundle; 40import android.util.Log; 41import android.view.Display; 42import android.view.View; 43import android.view.WindowManager; 44import android.widget.Toast; 45 46import com.android.gallery3d.common.Utils; 47import com.android.gallery3d.exif.ExifInterface; 48import com.android.photos.BitmapRegionTileSource; 49import com.android.photos.BitmapRegionTileSource.BitmapSource; 50 51import java.io.BufferedInputStream; 52import java.io.ByteArrayInputStream; 53import java.io.ByteArrayOutputStream; 54import java.io.FileNotFoundException; 55import java.io.IOException; 56import java.io.InputStream; 57 58public class WallpaperCropActivity extends Activity { 59 private static final String LOGTAG = "Launcher3.CropActivity"; 60 61 protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width"; 62 protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height"; 63 private static final int DEFAULT_COMPRESS_QUALITY = 90; 64 /** 65 * The maximum bitmap size we allow to be returned through the intent. 66 * Intents have a maximum of 1MB in total size. However, the Bitmap seems to 67 * have some overhead to hit so that we go way below the limit here to make 68 * sure the intent stays below 1MB.We should consider just returning a byte 69 * array instead of a Bitmap instance to avoid overhead. 70 */ 71 public static final int MAX_BMAP_IN_INTENT = 750000; 72 private static final float WALLPAPER_SCREENS_SPAN = 2f; 73 74 protected static Point sDefaultWallpaperSize; 75 76 protected CropView mCropView; 77 protected Uri mUri; 78 79 @Override 80 protected void onCreate(Bundle savedInstanceState) { 81 super.onCreate(savedInstanceState); 82 init(); 83 if (!enableRotation()) { 84 setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT); 85 } 86 } 87 88 protected void init() { 89 setContentView(R.layout.wallpaper_cropper); 90 91 mCropView = (CropView) findViewById(R.id.cropView); 92 93 Intent cropIntent = getIntent(); 94 final Uri imageUri = cropIntent.getData(); 95 96 if (imageUri == null) { 97 Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity"); 98 finish(); 99 return; 100 } 101 102 // Action bar 103 // Show the custom action bar view 104 final ActionBar actionBar = getActionBar(); 105 actionBar.setCustomView(R.layout.actionbar_set_wallpaper); 106 actionBar.getCustomView().setOnClickListener( 107 new View.OnClickListener() { 108 @Override 109 public void onClick(View v) { 110 boolean finishActivityWhenDone = true; 111 cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone); 112 } 113 }); 114 115 // Load image in background 116 final BitmapRegionTileSource.UriBitmapSource bitmapSource = 117 new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024); 118 Runnable onLoad = new Runnable() { 119 public void run() { 120 if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) { 121 Toast.makeText(WallpaperCropActivity.this, 122 getString(R.string.wallpaper_load_fail), 123 Toast.LENGTH_LONG).show(); 124 finish(); 125 } 126 } 127 }; 128 setCropViewTileSource(bitmapSource, true, false, onLoad); 129 } 130 131 public void setCropViewTileSource( 132 final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled, 133 final boolean moveToLeft, final Runnable postExecute) { 134 final Context context = WallpaperCropActivity.this; 135 final View progressView = findViewById(R.id.loading); 136 final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() { 137 protected Void doInBackground(Void...args) { 138 if (!isCancelled()) { 139 bitmapSource.loadInBackground(); 140 } 141 return null; 142 } 143 protected void onPostExecute(Void arg) { 144 if (!isCancelled()) { 145 progressView.setVisibility(View.INVISIBLE); 146 if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { 147 mCropView.setTileSource( 148 new BitmapRegionTileSource(context, bitmapSource), null); 149 mCropView.setTouchEnabled(touchEnabled); 150 if (moveToLeft) { 151 mCropView.moveToLeft(); 152 } 153 } 154 } 155 if (postExecute != null) { 156 postExecute.run(); 157 } 158 } 159 }; 160 // We don't want to show the spinner every time we load an image, because that would be 161 // annoying; instead, only start showing the spinner if loading the image has taken 162 // longer than 1 sec (ie 1000 ms) 163 progressView.postDelayed(new Runnable() { 164 public void run() { 165 if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) { 166 progressView.setVisibility(View.VISIBLE); 167 } 168 } 169 }, 1000); 170 loadBitmapTask.execute(); 171 } 172 173 public boolean enableRotation() { 174 return getResources().getBoolean(R.bool.allow_rotation); 175 } 176 177 public static String getSharedPreferencesKey() { 178 return WallpaperCropActivity.class.getName(); 179 } 180 181 // As a ratio of screen height, the total distance we want the parallax effect to span 182 // horizontally 183 private static float wallpaperTravelToScreenWidthRatio(int width, int height) { 184 float aspectRatio = width / (float) height; 185 186 // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width 187 // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width 188 // We will use these two data points to extrapolate how much the wallpaper parallax effect 189 // to span (ie travel) at any aspect ratio: 190 191 final float ASPECT_RATIO_LANDSCAPE = 16/10f; 192 final float ASPECT_RATIO_PORTRAIT = 10/16f; 193 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; 194 final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; 195 196 // To find out the desired width at different aspect ratios, we use the following two 197 // formulas, where the coefficient on x is the aspect ratio (width/height): 198 // (16/10)x + y = 1.5 199 // (10/16)x + y = 1.2 200 // We solve for x and y and end up with a final formula: 201 final float x = 202 (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / 203 (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); 204 final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; 205 return x * aspectRatio + y; 206 } 207 208 static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) { 209 if (sDefaultWallpaperSize == null) { 210 Point minDims = new Point(); 211 Point maxDims = new Point(); 212 windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims); 213 214 int maxDim = Math.max(maxDims.x, maxDims.y); 215 int minDim = Math.max(minDims.x, minDims.y); 216 217 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { 218 Point realSize = new Point(); 219 windowManager.getDefaultDisplay().getRealSize(realSize); 220 maxDim = Math.max(realSize.x, realSize.y); 221 minDim = Math.min(realSize.x, realSize.y); 222 } 223 224 // We need to ensure that there is enough extra space in the wallpaper 225 // for the intended parallax effects 226 final int defaultWidth, defaultHeight; 227 if (isScreenLarge(res)) { 228 defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); 229 defaultHeight = maxDim; 230 } else { 231 defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); 232 defaultHeight = maxDim; 233 } 234 sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight); 235 } 236 return sDefaultWallpaperSize; 237 } 238 239 public static int getRotationFromExif(String path) { 240 return getRotationFromExifHelper(path, null, 0, null, null); 241 } 242 243 public static int getRotationFromExif(Context context, Uri uri) { 244 return getRotationFromExifHelper(null, null, 0, context, uri); 245 } 246 247 public static int getRotationFromExif(Resources res, int resId) { 248 return getRotationFromExifHelper(null, res, resId, null, null); 249 } 250 251 private static int getRotationFromExifHelper( 252 String path, Resources res, int resId, Context context, Uri uri) { 253 ExifInterface ei = new ExifInterface(); 254 InputStream is = null; 255 BufferedInputStream bis = null; 256 try { 257 if (path != null) { 258 ei.readExif(path); 259 } else if (uri != null) { 260 is = context.getContentResolver().openInputStream(uri); 261 bis = new BufferedInputStream(is); 262 ei.readExif(bis); 263 } else { 264 is = res.openRawResource(resId); 265 bis = new BufferedInputStream(is); 266 ei.readExif(bis); 267 } 268 Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); 269 if (ori != null) { 270 return ExifInterface.getRotationForOrientationValue(ori.shortValue()); 271 } 272 } catch (IOException e) { 273 Log.w(LOGTAG, "Getting exif data failed", e); 274 } catch (NullPointerException e) { 275 // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid 276 Log.w(LOGTAG, "Getting exif data failed", e); 277 } finally { 278 Utils.closeSilently(bis); 279 Utils.closeSilently(is); 280 } 281 return 0; 282 } 283 284 protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) { 285 int rotation = getRotationFromExif(filePath); 286 BitmapCropTask cropTask = new BitmapCropTask( 287 this, filePath, null, rotation, 0, 0, true, false, null); 288 final Point bounds = cropTask.getImageBounds(); 289 Runnable onEndCrop = new Runnable() { 290 public void run() { 291 if (finishActivityWhenDone) { 292 setResult(Activity.RESULT_OK); 293 finish(); 294 } 295 } 296 }; 297 cropTask.setOnEndRunnable(onEndCrop); 298 cropTask.setNoCrop(true); 299 cropTask.execute(); 300 } 301 302 protected void cropImageAndSetWallpaper( 303 Resources res, int resId, final boolean finishActivityWhenDone) { 304 // crop this image and scale it down to the default wallpaper size for 305 // this device 306 int rotation = getRotationFromExif(res, resId); 307 Point inSize = mCropView.getSourceDimensions(); 308 Point outSize = getDefaultWallpaperSize(getResources(), 309 getWindowManager()); 310 RectF crop = getMaxCropRect( 311 inSize.x, inSize.y, outSize.x, outSize.y, false); 312 Runnable onEndCrop = new Runnable() { 313 public void run() { 314 if (finishActivityWhenDone) { 315 setResult(Activity.RESULT_OK); 316 finish(); 317 } 318 } 319 }; 320 BitmapCropTask cropTask = new BitmapCropTask(this, res, resId, 321 crop, rotation, outSize.x, outSize.y, true, false, onEndCrop); 322 cropTask.execute(); 323 } 324 325 private static boolean isScreenLarge(Resources res) { 326 Configuration config = res.getConfiguration(); 327 return config.smallestScreenWidthDp >= 720; 328 } 329 330 protected void cropImageAndSetWallpaper(Uri uri, 331 OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) { 332 boolean centerCrop = getResources().getBoolean(R.bool.center_crop); 333 // Get the crop 334 boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; 335 336 Display d = getWindowManager().getDefaultDisplay(); 337 338 Point displaySize = new Point(); 339 d.getSize(displaySize); 340 boolean isPortrait = displaySize.x < displaySize.y; 341 342 Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), 343 getWindowManager()); 344 // Get the crop 345 RectF cropRect = mCropView.getCrop(); 346 int cropRotation = mCropView.getImageRotation(); 347 float cropScale = mCropView.getWidth() / (float) cropRect.width(); 348 349 Point inSize = mCropView.getSourceDimensions(); 350 Matrix rotateMatrix = new Matrix(); 351 rotateMatrix.setRotate(cropRotation); 352 float[] rotatedInSize = new float[] { inSize.x, inSize.y }; 353 rotateMatrix.mapPoints(rotatedInSize); 354 rotatedInSize[0] = Math.abs(rotatedInSize[0]); 355 rotatedInSize[1] = Math.abs(rotatedInSize[1]); 356 357 // ADJUST CROP WIDTH 358 // Extend the crop all the way to the right, for parallax 359 // (or all the way to the left, in RTL) 360 float extraSpace; 361 if (centerCrop) { 362 extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left); 363 } else { 364 extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; 365 } 366 // Cap the amount of extra width 367 float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width(); 368 extraSpace = Math.min(extraSpace, maxExtraSpace); 369 370 if (centerCrop) { 371 cropRect.left -= extraSpace / 2f; 372 cropRect.right += extraSpace / 2f; 373 } else { 374 if (ltr) { 375 cropRect.right += extraSpace; 376 } else { 377 cropRect.left -= extraSpace; 378 } 379 } 380 381 // ADJUST CROP HEIGHT 382 if (isPortrait) { 383 cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale; 384 } else { // LANDSCAPE 385 float extraPortraitHeight = 386 defaultWallpaperSize.y / cropScale - cropRect.height(); 387 float expandHeight = 388 Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top), 389 extraPortraitHeight / 2); 390 cropRect.top -= expandHeight; 391 cropRect.bottom += expandHeight; 392 } 393 final int outWidth = (int) Math.round(cropRect.width() * cropScale); 394 final int outHeight = (int) Math.round(cropRect.height() * cropScale); 395 396 Runnable onEndCrop = new Runnable() { 397 public void run() { 398 if (finishActivityWhenDone) { 399 setResult(Activity.RESULT_OK); 400 finish(); 401 } 402 } 403 }; 404 BitmapCropTask cropTask = new BitmapCropTask(this, uri, 405 cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop); 406 if (onBitmapCroppedHandler != null) { 407 cropTask.setOnBitmapCropped(onBitmapCroppedHandler); 408 } 409 cropTask.execute(); 410 } 411 412 public interface OnBitmapCroppedHandler { 413 public void onBitmapCropped(byte[] imageBytes); 414 } 415 416 protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> { 417 Uri mInUri = null; 418 Context mContext; 419 String mInFilePath; 420 byte[] mInImageBytes; 421 int mInResId = 0; 422 RectF mCropBounds = null; 423 int mOutWidth, mOutHeight; 424 int mRotation; 425 String mOutputFormat = "jpg"; // for now 426 boolean mSetWallpaper; 427 boolean mSaveCroppedBitmap; 428 Bitmap mCroppedBitmap; 429 Runnable mOnEndRunnable; 430 Resources mResources; 431 OnBitmapCroppedHandler mOnBitmapCroppedHandler; 432 boolean mNoCrop; 433 434 public BitmapCropTask(Context c, String filePath, 435 RectF cropBounds, int rotation, int outWidth, int outHeight, 436 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 437 mContext = c; 438 mInFilePath = filePath; 439 init(cropBounds, rotation, 440 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 441 } 442 443 public BitmapCropTask(byte[] imageBytes, 444 RectF cropBounds, int rotation, int outWidth, int outHeight, 445 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 446 mInImageBytes = imageBytes; 447 init(cropBounds, rotation, 448 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 449 } 450 451 public BitmapCropTask(Context c, Uri inUri, 452 RectF cropBounds, int rotation, int outWidth, int outHeight, 453 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 454 mContext = c; 455 mInUri = inUri; 456 init(cropBounds, rotation, 457 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 458 } 459 460 public BitmapCropTask(Context c, Resources res, int inResId, 461 RectF cropBounds, int rotation, int outWidth, int outHeight, 462 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 463 mContext = c; 464 mInResId = inResId; 465 mResources = res; 466 init(cropBounds, rotation, 467 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); 468 } 469 470 private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, 471 boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { 472 mCropBounds = cropBounds; 473 mRotation = rotation; 474 mOutWidth = outWidth; 475 mOutHeight = outHeight; 476 mSetWallpaper = setWallpaper; 477 mSaveCroppedBitmap = saveCroppedBitmap; 478 mOnEndRunnable = onEndRunnable; 479 } 480 481 public void setOnBitmapCropped(OnBitmapCroppedHandler handler) { 482 mOnBitmapCroppedHandler = handler; 483 } 484 485 public void setNoCrop(boolean value) { 486 mNoCrop = value; 487 } 488 489 public void setOnEndRunnable(Runnable onEndRunnable) { 490 mOnEndRunnable = onEndRunnable; 491 } 492 493 // Helper to setup input stream 494 private InputStream regenerateInputStream() { 495 if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) { 496 Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " + 497 "image byte array given"); 498 } else { 499 try { 500 if (mInUri != null) { 501 return new BufferedInputStream( 502 mContext.getContentResolver().openInputStream(mInUri)); 503 } else if (mInFilePath != null) { 504 return mContext.openFileInput(mInFilePath); 505 } else if (mInImageBytes != null) { 506 return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes)); 507 } else { 508 return new BufferedInputStream(mResources.openRawResource(mInResId)); 509 } 510 } catch (FileNotFoundException e) { 511 Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e); 512 } 513 } 514 return null; 515 } 516 517 public Point getImageBounds() { 518 InputStream is = regenerateInputStream(); 519 if (is != null) { 520 BitmapFactory.Options options = new BitmapFactory.Options(); 521 options.inJustDecodeBounds = true; 522 BitmapFactory.decodeStream(is, null, options); 523 Utils.closeSilently(is); 524 if (options.outWidth != 0 && options.outHeight != 0) { 525 return new Point(options.outWidth, options.outHeight); 526 } 527 } 528 return null; 529 } 530 531 public void setCropBounds(RectF cropBounds) { 532 mCropBounds = cropBounds; 533 } 534 535 public Bitmap getCroppedBitmap() { 536 return mCroppedBitmap; 537 } 538 public boolean cropBitmap() { 539 boolean failure = false; 540 541 542 WallpaperManager wallpaperManager = null; 543 if (mSetWallpaper) { 544 wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); 545 } 546 547 548 if (mSetWallpaper && mNoCrop) { 549 try { 550 InputStream is = regenerateInputStream(); 551 if (is != null) { 552 wallpaperManager.setStream(is); 553 Utils.closeSilently(is); 554 } 555 } catch (IOException e) { 556 Log.w(LOGTAG, "cannot write stream to wallpaper", e); 557 failure = true; 558 } 559 return !failure; 560 } else { 561 // Find crop bounds (scaled to original image size) 562 Rect roundedTrueCrop = new Rect(); 563 Matrix rotateMatrix = new Matrix(); 564 Matrix inverseRotateMatrix = new Matrix(); 565 566 Point bounds = getImageBounds(); 567 if (mRotation > 0) { 568 rotateMatrix.setRotate(mRotation); 569 inverseRotateMatrix.setRotate(-mRotation); 570 571 mCropBounds.roundOut(roundedTrueCrop); 572 mCropBounds = new RectF(roundedTrueCrop); 573 574 if (bounds == null) { 575 Log.w(LOGTAG, "cannot get bounds for image"); 576 failure = true; 577 return false; 578 } 579 580 float[] rotatedBounds = new float[] { bounds.x, bounds.y }; 581 rotateMatrix.mapPoints(rotatedBounds); 582 rotatedBounds[0] = Math.abs(rotatedBounds[0]); 583 rotatedBounds[1] = Math.abs(rotatedBounds[1]); 584 585 mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); 586 inverseRotateMatrix.mapRect(mCropBounds); 587 mCropBounds.offset(bounds.x/2, bounds.y/2); 588 589 } 590 591 mCropBounds.roundOut(roundedTrueCrop); 592 593 if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { 594 Log.w(LOGTAG, "crop has bad values for full size image"); 595 failure = true; 596 return false; 597 } 598 599 // See how much we're reducing the size of the image 600 int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth, 601 roundedTrueCrop.height() / mOutHeight)); 602 // Attempt to open a region decoder 603 BitmapRegionDecoder decoder = null; 604 InputStream is = null; 605 try { 606 is = regenerateInputStream(); 607 if (is == null) { 608 Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); 609 failure = true; 610 return false; 611 } 612 decoder = BitmapRegionDecoder.newInstance(is, false); 613 Utils.closeSilently(is); 614 } catch (IOException e) { 615 Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); 616 } finally { 617 Utils.closeSilently(is); 618 is = null; 619 } 620 621 Bitmap crop = null; 622 if (decoder != null) { 623 // Do region decoding to get crop bitmap 624 BitmapFactory.Options options = new BitmapFactory.Options(); 625 if (scaleDownSampleSize > 1) { 626 options.inSampleSize = scaleDownSampleSize; 627 } 628 crop = decoder.decodeRegion(roundedTrueCrop, options); 629 decoder.recycle(); 630 } 631 632 if (crop == null) { 633 // BitmapRegionDecoder has failed, try to crop in-memory 634 is = regenerateInputStream(); 635 Bitmap fullSize = null; 636 if (is != null) { 637 BitmapFactory.Options options = new BitmapFactory.Options(); 638 if (scaleDownSampleSize > 1) { 639 options.inSampleSize = scaleDownSampleSize; 640 } 641 fullSize = BitmapFactory.decodeStream(is, null, options); 642 Utils.closeSilently(is); 643 } 644 if (fullSize != null) { 645 // Find out the true sample size that was used by the decoder 646 scaleDownSampleSize = bounds.x / fullSize.getWidth(); 647 mCropBounds.left /= scaleDownSampleSize; 648 mCropBounds.top /= scaleDownSampleSize; 649 mCropBounds.bottom /= scaleDownSampleSize; 650 mCropBounds.right /= scaleDownSampleSize; 651 mCropBounds.roundOut(roundedTrueCrop); 652 653 // Adjust values to account for issues related to rounding 654 if (roundedTrueCrop.width() > fullSize.getWidth()) { 655 // Adjust the width 656 roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth(); 657 } 658 if (roundedTrueCrop.right > fullSize.getWidth()) { 659 // Adjust the left value 660 int adjustment = roundedTrueCrop.left - 661 Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width()); 662 roundedTrueCrop.left -= adjustment; 663 roundedTrueCrop.right -= adjustment; 664 } 665 if (roundedTrueCrop.height() > fullSize.getHeight()) { 666 // Adjust the height 667 roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight(); 668 } 669 if (roundedTrueCrop.bottom > fullSize.getHeight()) { 670 // Adjust the top value 671 int adjustment = roundedTrueCrop.top - 672 Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height()); 673 roundedTrueCrop.top -= adjustment; 674 roundedTrueCrop.bottom -= adjustment; 675 } 676 677 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, 678 roundedTrueCrop.top, roundedTrueCrop.width(), 679 roundedTrueCrop.height()); 680 } 681 } 682 683 if (crop == null) { 684 Log.w(LOGTAG, "cannot decode file: " + mInUri.toString()); 685 failure = true; 686 return false; 687 } 688 if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { 689 float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; 690 rotateMatrix.mapPoints(dimsAfter); 691 dimsAfter[0] = Math.abs(dimsAfter[0]); 692 dimsAfter[1] = Math.abs(dimsAfter[1]); 693 694 if (!(mOutWidth > 0 && mOutHeight > 0)) { 695 mOutWidth = Math.round(dimsAfter[0]); 696 mOutHeight = Math.round(dimsAfter[1]); 697 } 698 699 RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); 700 RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); 701 702 Matrix m = new Matrix(); 703 if (mRotation == 0) { 704 m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 705 } else { 706 Matrix m1 = new Matrix(); 707 m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); 708 Matrix m2 = new Matrix(); 709 m2.setRotate(mRotation); 710 Matrix m3 = new Matrix(); 711 m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); 712 Matrix m4 = new Matrix(); 713 m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); 714 715 Matrix c1 = new Matrix(); 716 c1.setConcat(m2, m1); 717 Matrix c2 = new Matrix(); 718 c2.setConcat(m4, m3); 719 m.setConcat(c2, c1); 720 } 721 722 Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), 723 (int) returnRect.height(), Bitmap.Config.ARGB_8888); 724 if (tmp != null) { 725 Canvas c = new Canvas(tmp); 726 Paint p = new Paint(); 727 p.setFilterBitmap(true); 728 c.drawBitmap(crop, m, p); 729 crop = tmp; 730 } 731 } 732 733 if (mSaveCroppedBitmap) { 734 mCroppedBitmap = crop; 735 } 736 737 // Get output compression format 738 CompressFormat cf = 739 convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); 740 741 // Compress to byte array 742 ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); 743 if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { 744 // If we need to set to the wallpaper, set it 745 if (mSetWallpaper && wallpaperManager != null) { 746 try { 747 byte[] outByteArray = tmpOut.toByteArray(); 748 wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); 749 if (mOnBitmapCroppedHandler != null) { 750 mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); 751 } 752 } catch (IOException e) { 753 Log.w(LOGTAG, "cannot write stream to wallpaper", e); 754 failure = true; 755 } 756 } 757 } else { 758 Log.w(LOGTAG, "cannot compress bitmap"); 759 failure = true; 760 } 761 } 762 return !failure; // True if any of the operations failed 763 } 764 765 @Override 766 protected Boolean doInBackground(Void... params) { 767 return cropBitmap(); 768 } 769 770 @Override 771 protected void onPostExecute(Boolean result) { 772 if (mOnEndRunnable != null) { 773 mOnEndRunnable.run(); 774 } 775 } 776 } 777 778 protected static RectF getMaxCropRect( 779 int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { 780 RectF cropRect = new RectF(); 781 // Get a crop rect that will fit this 782 if (inWidth / (float) inHeight > outWidth / (float) outHeight) { 783 cropRect.top = 0; 784 cropRect.bottom = inHeight; 785 cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; 786 cropRect.right = inWidth - cropRect.left; 787 if (leftAligned) { 788 cropRect.right -= cropRect.left; 789 cropRect.left = 0; 790 } 791 } else { 792 cropRect.left = 0; 793 cropRect.right = inWidth; 794 cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; 795 cropRect.bottom = inHeight - cropRect.top; 796 } 797 return cropRect; 798 } 799 800 protected static CompressFormat convertExtensionToCompressFormat(String extension) { 801 return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; 802 } 803 804 protected static String getFileExtension(String requestFormat) { 805 String outputFormat = (requestFormat == null) 806 ? "jpg" 807 : requestFormat; 808 outputFormat = outputFormat.toLowerCase(); 809 return (outputFormat.equals("png") || outputFormat.equals("gif")) 810 ? "png" // We don't support gif compression. 811 : "jpg"; 812 } 813} 814