/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* Copied from Launcher3 */
package com.android.wallpapercropper;

import android.app.ActionBar;
import android.app.Activity;
import android.app.WallpaperManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

public class WallpaperCropActivity extends Activity {
    private static final String LOGTAG = "Launcher3.CropActivity";

    protected static final String WALLPAPER_WIDTH_KEY = "wallpaper.width";
    protected static final String WALLPAPER_HEIGHT_KEY = "wallpaper.height";
    private static final int DEFAULT_COMPRESS_QUALITY = 90;
    
The maximum bitmap size we allow to be returned through the intent. Intents have a maximum of 1MB in total size. However, the Bitmap seems to have some overhead to hit so that we go way below the limit here to make sure the intent stays below 1MB.We should consider just returning a byte array instead of a Bitmap instance to avoid overhead.
/** * The maximum bitmap size we allow to be returned through the intent. * Intents have a maximum of 1MB in total size. However, the Bitmap seems to * have some overhead to hit so that we go way below the limit here to make * sure the intent stays below 1MB.We should consider just returning a byte * array instead of a Bitmap instance to avoid overhead. */
public static final int MAX_BMAP_IN_INTENT = 750000; private static final float WALLPAPER_SCREENS_SPAN = 2f; protected static Point sDefaultWallpaperSize; protected CropView mCropView; protected Uri mUri; private View mSetWallpaperButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); init(); if (!enableRotation()) { setRequestedOrientation(Configuration.ORIENTATION_PORTRAIT); } } protected void init() { setContentView(R.layout.wallpaper_cropper); mCropView = findViewById(R.id.cropView); Intent cropIntent = getIntent(); final Uri imageUri = cropIntent.getData(); if (imageUri == null) { Log.e(LOGTAG, "No URI passed in intent, exiting WallpaperCropActivity"); finish(); return; } // Action bar // Show the custom action bar view final ActionBar actionBar = getActionBar(); actionBar.setCustomView(R.layout.actionbar_set_wallpaper); actionBar.getCustomView().setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { boolean finishActivityWhenDone = true; cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone); } }); mSetWallpaperButton = findViewById(R.id.set_wallpaper_button); // Load image in background final BitmapRegionTileSource.UriBitmapSource bitmapSource = new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024); mSetWallpaperButton.setVisibility(View.INVISIBLE); Runnable onLoad = new Runnable() { public void run() { if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) { Toast.makeText(WallpaperCropActivity.this, getString(R.string.wallpaper_load_fail), Toast.LENGTH_LONG).show(); finish(); } else { mSetWallpaperButton.setVisibility(View.VISIBLE); } } }; setCropViewTileSource(bitmapSource, true, false, onLoad); } @Override protected void onDestroy() { if (mCropView != null) { mCropView.destroy(); } super.onDestroy(); } public void setCropViewTileSource( final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled, final boolean moveToLeft, final Runnable postExecute) { final Context context = WallpaperCropActivity.this; final View progressView = findViewById(R.id.loading); final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() { protected Void doInBackground(Void...args) { if (!isCancelled()) { try { bitmapSource.loadInBackground(); } catch (SecurityException securityException) { if (isDestroyed()) { // Temporarily granted permissions are revoked when the activity // finishes, potentially resulting in a SecurityException here. // Even though {@link #isDestroyed} might also return true in different // situations where the configuration changes, we are fine with // catching these cases here as well. cancel(false); } else { // otherwise it had a different cause and we throw it further throw securityException; } } } return null; } protected void onPostExecute(Void arg) { if (!isCancelled()) { progressView.setVisibility(View.INVISIBLE); if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { mCropView.setTileSource( new BitmapRegionTileSource(context, bitmapSource), null); mCropView.setTouchEnabled(touchEnabled); if (moveToLeft) { mCropView.moveToLeft(); } } } if (postExecute != null) { postExecute.run(); } } }; // We don't want to show the spinner every time we load an image, because that would be // annoying; instead, only start showing the spinner if loading the image has taken // longer than 1 sec (ie 1000 ms) progressView.postDelayed(new Runnable() { public void run() { if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) { progressView.setVisibility(View.VISIBLE); } } }, 1000); loadBitmapTask.execute(); } public boolean enableRotation() { return getResources().getBoolean(R.bool.allow_rotation); } public static String getSharedPreferencesKey() { return WallpaperCropActivity.class.getName(); } // As a ratio of screen height, the total distance we want the parallax effect to span // horizontally private static float wallpaperTravelToScreenWidthRatio(int width, int height) { float aspectRatio = width / (float) height; // At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width // At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width // We will use these two data points to extrapolate how much the wallpaper parallax effect // to span (ie travel) at any aspect ratio: final float ASPECT_RATIO_LANDSCAPE = 16/10f; final float ASPECT_RATIO_PORTRAIT = 10/16f; final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f; final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f; // To find out the desired width at different aspect ratios, we use the following two // formulas, where the coefficient on x is the aspect ratio (width/height): // (16/10)x + y = 1.5 // (10/16)x + y = 1.2 // We solve for x and y and end up with a final formula: final float x = (WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) / (ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT); final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT; return x * aspectRatio + y; } static protected Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) { if (sDefaultWallpaperSize == null) { Point minDims = new Point(); Point maxDims = new Point(); windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims); int maxDim = Math.max(maxDims.x, maxDims.y); int minDim = Math.max(minDims.x, minDims.y); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { Point realSize = new Point(); windowManager.getDefaultDisplay().getRealSize(realSize); maxDim = Math.max(realSize.x, realSize.y); minDim = Math.min(realSize.x, realSize.y); } // We need to ensure that there is enough extra space in the wallpaper // for the intended parallax effects final int defaultWidth, defaultHeight; if (isScreenLarge(res)) { defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim)); defaultHeight = maxDim; } else { defaultWidth = Math.max((int) (minDim * WALLPAPER_SCREENS_SPAN), maxDim); defaultHeight = maxDim; } sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight); } return sDefaultWallpaperSize; } public static int getRotationFromExif(String path) { return getRotationFromExifHelper(path, null, 0, null, null); } public static int getRotationFromExif(Context context, Uri uri) { return getRotationFromExifHelper(null, null, 0, context, uri); } public static int getRotationFromExif(Resources res, int resId) { return getRotationFromExifHelper(null, res, resId, null, null); } private static int getRotationFromExifHelper( String path, Resources res, int resId, Context context, Uri uri) { ExifInterface ei = new ExifInterface(); InputStream is = null; BufferedInputStream bis = null; try { if (path != null) { ei.readExif(path); } else if (uri != null) { is = context.getContentResolver().openInputStream(uri); bis = new BufferedInputStream(is); ei.readExif(bis); } else { is = res.openRawResource(resId); bis = new BufferedInputStream(is); ei.readExif(bis); } Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); if (ori != null) { return ExifInterface.getRotationForOrientationValue(ori.shortValue()); } } catch (IOException e) { Log.w(LOGTAG, "Getting exif data failed", e); } catch (NullPointerException e) { // Sometimes the ExifInterface has an internal NPE if Exif data isn't valid Log.w(LOGTAG, "Getting exif data failed", e); } finally { Utils.closeSilently(bis); Utils.closeSilently(is); } return 0; } protected void setWallpaper(String filePath, final boolean finishActivityWhenDone) { int rotation = getRotationFromExif(filePath); BitmapCropTask cropTask = new BitmapCropTask( this, filePath, null, rotation, 0, 0, true, false, null); final Point bounds = cropTask.getImageBounds(); Runnable onEndCrop = new Runnable() { public void run() { if (finishActivityWhenDone) { setResult(Activity.RESULT_OK); finish(); } } }; cropTask.setOnEndRunnable(onEndCrop); cropTask.setNoCrop(true); cropTask.execute(); } protected void cropImageAndSetWallpaper( Resources res, int resId, final boolean finishActivityWhenDone) { // crop this image and scale it down to the default wallpaper size for // this device int rotation = getRotationFromExif(res, resId); Point inSize = mCropView.getSourceDimensions(); Point outSize = getDefaultWallpaperSize(getResources(), getWindowManager()); RectF crop = getMaxCropRect( inSize.x, inSize.y, outSize.x, outSize.y, false); Runnable onEndCrop = new Runnable() { public void run() { if (finishActivityWhenDone) { setResult(Activity.RESULT_OK); finish(); } } }; BitmapCropTask cropTask = new BitmapCropTask(this, res, resId, crop, rotation, outSize.x, outSize.y, true, false, onEndCrop); cropTask.execute(); } private static boolean isScreenLarge(Resources res) { Configuration config = res.getConfiguration(); return config.smallestScreenWidthDp >= 720; } protected void cropImageAndSetWallpaper(Uri uri, OnBitmapCroppedHandler onBitmapCroppedHandler, final boolean finishActivityWhenDone) { boolean centerCrop = getResources().getBoolean(R.bool.center_crop); // Get the crop boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; Display d = getWindowManager().getDefaultDisplay(); Point displaySize = new Point(); d.getSize(displaySize); boolean isPortrait = displaySize.x < displaySize.y; Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), getWindowManager()); // Get the crop RectF cropRect = mCropView.getCrop(); Point inSize = mCropView.getSourceDimensions(); int cropRotation = mCropView.getImageRotation(); float cropScale = mCropView.getWidth() / (float) cropRect.width(); Matrix rotateMatrix = new Matrix(); rotateMatrix.setRotate(cropRotation); float[] rotatedInSize = new float[] { inSize.x, inSize.y }; rotateMatrix.mapPoints(rotatedInSize); rotatedInSize[0] = Math.abs(rotatedInSize[0]); rotatedInSize[1] = Math.abs(rotatedInSize[1]); // Due to rounding errors in the cropview renderer the edges can be slightly offset // therefore we ensure that the boundaries are sanely defined cropRect.left = Math.max(0, cropRect.left); cropRect.right = Math.min(rotatedInSize[0], cropRect.right); cropRect.top = Math.max(0, cropRect.top); cropRect.bottom = Math.min(rotatedInSize[1], cropRect.bottom); // ADJUST CROP WIDTH // Extend the crop all the way to the right, for parallax // (or all the way to the left, in RTL) float extraSpace; if (centerCrop) { extraSpace = 2f * Math.min(rotatedInSize[0] - cropRect.right, cropRect.left); } else { extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; } // Cap the amount of extra width float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width(); extraSpace = Math.min(extraSpace, maxExtraSpace); if (centerCrop) { cropRect.left -= extraSpace / 2f; cropRect.right += extraSpace / 2f; } else { if (ltr) { cropRect.right += extraSpace; } else { cropRect.left -= extraSpace; } } // ADJUST CROP HEIGHT if (isPortrait) { cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale; } else { // LANDSCAPE float extraPortraitHeight = defaultWallpaperSize.y / cropScale - cropRect.height(); float expandHeight = Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top), extraPortraitHeight / 2); cropRect.top -= expandHeight; cropRect.bottom += expandHeight; } final int outWidth = (int) Math.round(cropRect.width() * cropScale); final int outHeight = (int) Math.round(cropRect.height() * cropScale); Runnable onEndCrop = new Runnable() { public void run() { if (finishActivityWhenDone) { setResult(Activity.RESULT_OK); finish(); } } }; BitmapCropTask cropTask = new BitmapCropTask(this, uri, cropRect, cropRotation, outWidth, outHeight, true, false, onEndCrop); if (onBitmapCroppedHandler != null) { cropTask.setOnBitmapCropped(onBitmapCroppedHandler); } cropTask.execute(); } public interface OnBitmapCroppedHandler { public void onBitmapCropped(byte[] imageBytes); } protected static class BitmapCropTask extends AsyncTask<Void, Void, Boolean> { Uri mInUri = null; Context mContext; String mInFilePath; byte[] mInImageBytes; int mInResId = 0; RectF mCropBounds = null; int mOutWidth, mOutHeight; int mRotation; String mOutputFormat = "jpg"; // for now boolean mSetWallpaper; boolean mSaveCroppedBitmap; Bitmap mCroppedBitmap; Runnable mOnEndRunnable; Resources mResources; OnBitmapCroppedHandler mOnBitmapCroppedHandler; boolean mNoCrop; public BitmapCropTask(Context c, String filePath, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { mContext = c; mInFilePath = filePath; init(cropBounds, rotation, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); } public BitmapCropTask(byte[] imageBytes, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { mInImageBytes = imageBytes; init(cropBounds, rotation, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); } public BitmapCropTask(Context c, Uri inUri, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { mContext = c; mInUri = inUri; init(cropBounds, rotation, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); } public BitmapCropTask(Context c, Resources res, int inResId, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { mContext = c; mInResId = inResId; mResources = res; init(cropBounds, rotation, outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); } private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { mCropBounds = cropBounds; mRotation = rotation; mOutWidth = outWidth; mOutHeight = outHeight; mSetWallpaper = setWallpaper; mSaveCroppedBitmap = saveCroppedBitmap; mOnEndRunnable = onEndRunnable; } public void setOnBitmapCropped(OnBitmapCroppedHandler handler) { mOnBitmapCroppedHandler = handler; } public void setNoCrop(boolean value) { mNoCrop = value; } public void setOnEndRunnable(Runnable onEndRunnable) { mOnEndRunnable = onEndRunnable; } // Helper to setup input stream private InputStream regenerateInputStream() { if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) { Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " + "image byte array given"); } else { try { if (mInUri != null) { return new BufferedInputStream( mContext.getContentResolver().openInputStream(mInUri)); } else if (mInFilePath != null) { return mContext.openFileInput(mInFilePath); } else if (mInImageBytes != null) { return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes)); } else { return new BufferedInputStream(mResources.openRawResource(mInResId)); } } catch (FileNotFoundException e) { Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e); } } return null; } public Point getImageBounds() { InputStream is = regenerateInputStream(); if (is != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(is, null, options); Utils.closeSilently(is); if (options.outWidth != 0 && options.outHeight != 0) { return new Point(options.outWidth, options.outHeight); } } return null; } public void setCropBounds(RectF cropBounds) { mCropBounds = cropBounds; } public Bitmap getCroppedBitmap() { return mCroppedBitmap; } public boolean cropBitmap() { boolean failure = false; WallpaperManager wallpaperManager = null; if (mSetWallpaper) { wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); } if (mSetWallpaper && mNoCrop) { try { InputStream is = regenerateInputStream(); if (is != null) { wallpaperManager.setStream(is); Utils.closeSilently(is); } } catch (IOException e) { Log.w(LOGTAG, "cannot write stream to wallpaper", e); failure = true; } return !failure; } else { // Find crop bounds (scaled to original image size) Rect roundedTrueCrop = new Rect(); Matrix rotateMatrix = new Matrix(); Matrix inverseRotateMatrix = new Matrix(); Point bounds = getImageBounds(); if (mRotation > 0) { rotateMatrix.setRotate(mRotation); inverseRotateMatrix.setRotate(-mRotation); mCropBounds.roundOut(roundedTrueCrop); mCropBounds = new RectF(roundedTrueCrop); if (bounds == null) { Log.w(LOGTAG, "cannot get bounds for image"); failure = true; return false; } float[] rotatedBounds = new float[] { bounds.x, bounds.y }; rotateMatrix.mapPoints(rotatedBounds); rotatedBounds[0] = Math.abs(rotatedBounds[0]); rotatedBounds[1] = Math.abs(rotatedBounds[1]); mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); inverseRotateMatrix.mapRect(mCropBounds); mCropBounds.offset(bounds.x/2, bounds.y/2); } mCropBounds.roundOut(roundedTrueCrop); if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { Log.w(LOGTAG, "crop has bad values for full size image"); failure = true; return false; } // See how much we're reducing the size of the image int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth, roundedTrueCrop.height() / mOutHeight)); // Attempt to open a region decoder BitmapRegionDecoder decoder = null; InputStream is = null; try { is = regenerateInputStream(); if (is == null) { Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); failure = true; return false; } decoder = BitmapRegionDecoder.newInstance(is, false); Utils.closeSilently(is); } catch (IOException e) { Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); } finally { Utils.closeSilently(is); is = null; } Bitmap crop = null; if (decoder != null) { // Do region decoding to get crop bitmap BitmapFactory.Options options = new BitmapFactory.Options(); if (scaleDownSampleSize > 1) { options.inSampleSize = scaleDownSampleSize; } crop = decoder.decodeRegion(roundedTrueCrop, options); decoder.recycle(); } if (crop == null) { // BitmapRegionDecoder has failed, try to crop in-memory is = regenerateInputStream(); Bitmap fullSize = null; if (is != null) { BitmapFactory.Options options = new BitmapFactory.Options(); if (scaleDownSampleSize > 1) { options.inSampleSize = scaleDownSampleSize; } fullSize = BitmapFactory.decodeStream(is, null, options); Utils.closeSilently(is); } if (fullSize != null) { // Find out the true sample size that was used by the decoder scaleDownSampleSize = bounds.x / fullSize.getWidth(); mCropBounds.left /= scaleDownSampleSize; mCropBounds.top /= scaleDownSampleSize; mCropBounds.bottom /= scaleDownSampleSize; mCropBounds.right /= scaleDownSampleSize; mCropBounds.roundOut(roundedTrueCrop); // Adjust values to account for issues related to rounding if (roundedTrueCrop.width() > fullSize.getWidth()) { // Adjust the width roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth(); } if (roundedTrueCrop.right > fullSize.getWidth()) { // Adjust the left value int adjustment = roundedTrueCrop.left - Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width()); roundedTrueCrop.left -= adjustment; roundedTrueCrop.right -= adjustment; } if (roundedTrueCrop.height() > fullSize.getHeight()) { // Adjust the height roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight(); } if (roundedTrueCrop.bottom > fullSize.getHeight()) { // Adjust the top value int adjustment = roundedTrueCrop.top - Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height()); roundedTrueCrop.top -= adjustment; roundedTrueCrop.bottom -= adjustment; } crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, roundedTrueCrop.top, roundedTrueCrop.width(), roundedTrueCrop.height()); } } if (crop == null) { Log.w(LOGTAG, "cannot decode file: " + mInUri.toString()); failure = true; return false; } if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; rotateMatrix.mapPoints(dimsAfter); dimsAfter[0] = Math.abs(dimsAfter[0]); dimsAfter[1] = Math.abs(dimsAfter[1]); if (!(mOutWidth > 0 && mOutHeight > 0)) { mOutWidth = Math.round(dimsAfter[0]); mOutHeight = Math.round(dimsAfter[1]); } RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); Matrix m = new Matrix(); if (mRotation == 0) { m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); } else { Matrix m1 = new Matrix(); m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); Matrix m2 = new Matrix(); m2.setRotate(mRotation); Matrix m3 = new Matrix(); m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); Matrix m4 = new Matrix(); m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); Matrix c1 = new Matrix(); c1.setConcat(m2, m1); Matrix c2 = new Matrix(); c2.setConcat(m4, m3); m.setConcat(c2, c1); } Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), (int) returnRect.height(), Bitmap.Config.ARGB_8888); if (tmp != null) { Canvas c = new Canvas(tmp); Paint p = new Paint(); p.setFilterBitmap(true); c.drawBitmap(crop, m, p); crop = tmp; } } if (mSaveCroppedBitmap) { mCroppedBitmap = crop; } // Get output compression format CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); // Compress to byte array ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { // If we need to set to the wallpaper, set it if (mSetWallpaper && wallpaperManager != null) { try { byte[] outByteArray = tmpOut.toByteArray(); wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); if (mOnBitmapCroppedHandler != null) { mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); } } catch (IOException e) { Log.w(LOGTAG, "cannot write stream to wallpaper", e); failure = true; } } } else { Log.w(LOGTAG, "cannot compress bitmap"); failure = true; } } return !failure; // True if any of the operations failed } @Override protected Boolean doInBackground(Void... params) { return cropBitmap(); } @Override protected void onPostExecute(Boolean result) { if (mOnEndRunnable != null) { mOnEndRunnable.run(); } } } protected static RectF getMaxCropRect( int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { RectF cropRect = new RectF(); // Get a crop rect that will fit this if (inWidth / (float) inHeight > outWidth / (float) outHeight) { cropRect.top = 0; cropRect.bottom = inHeight; cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; cropRect.right = inWidth - cropRect.left; if (leftAligned) { cropRect.right -= cropRect.left; cropRect.left = 0; } } else { cropRect.left = 0; cropRect.right = inWidth; cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; cropRect.bottom = inHeight - cropRect.top; } return cropRect; } protected static CompressFormat convertExtensionToCompressFormat(String extension) { return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; } protected static String getFileExtension(String requestFormat) { String outputFormat = (requestFormat == null) ? "jpg" : requestFormat; outputFormat = outputFormat.toLowerCase(); return (outputFormat.equals("png") || outputFormat.equals("gif")) ? "png" // We don't support gif compression. : "jpg"; } }