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