| /* |
| * 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.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. |
| */ |
| 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, Display display) { |
| if (sDefaultWallpaperSize == null) { |
| Point minDims = new Point(); |
| Point maxDims = new Point(); |
| display.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(); |
| display.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(), getDisplay()); |
| 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; |
| |
| Rect windowBounds = getWindowManager().getCurrentWindowMetrics().getBounds(); |
| boolean isPortrait = windowBounds.width() < windowBounds.height(); |
| |
| Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), |
| getDisplay()); |
| // 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"; |
| } |
| } |