// "Therefore those skilled at the unorthodox // are infinite as heaven and earth, // inexhaustible as the great rivers. // When they come to an end, // they begin again, // like the days and months; // they die and are reborn, // like the four seasons." // // - Sun Tsu, // "The Art of War" package com.theartofdev.edmodo.cropper; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import java.lang.ref.WeakReference; /** * Task to crop bitmap asynchronously from the UI thread. */ final class BitmapCroppingWorkerTask extends AsyncTask { // region: Fields and Consts /** * Use a WeakReference to ensure the ImageView can be garbage collected */ private final WeakReference mCropImageViewReference; /** * the bitmap to crop */ private final Bitmap mBitmap; /** * The Android URI of the image to load */ private final Uri mUri; /** * The context of the crop image view widget used for loading of bitmap by Android URI */ private final Context mContext; /** * Required cropping 4 points (x0,y0,x1,y1,x2,y2,x3,y3) */ private final float[] mCropPoints; /** * Degrees the image was rotated after loading */ private final int mDegreesRotated; /** * the original width of the image to be cropped (for image loaded from URI) */ private final int mOrgWidth; /** * the original height of the image to be cropped (for image loaded from URI) */ private final int mOrgHeight; /** * is there is fixed aspect ratio for the crop rectangle */ private final boolean mFixAspectRatio; /** * the X aspect ration of the crop rectangle */ private final int mAspectRatioX; /** * the Y aspect ration of the crop rectangle */ private final int mAspectRatioY; /** * required width of the cropping image */ private final int mReqWidth; /** * required height of the cropping image */ private final int mReqHeight; /** * is the image flipped horizontally */ private final boolean mFlipHorizontally; /** * is the image flipped vertically */ private final boolean mFlipVertically; /** * The option to handle requested width/height */ private final CropImageView.RequestSizeOptions mReqSizeOptions; /** * the Android Uri to save the cropped image to */ private final Uri mSaveUri; /** * the compression format to use when writing the image */ private final Bitmap.CompressFormat mSaveCompressFormat; /** * the quality (if applicable) to use when writing the image (0 - 100) */ private final int mSaveCompressQuality; // endregion BitmapCroppingWorkerTask( CropImageView cropImageView, Bitmap bitmap, float[] cropPoints, int degreesRotated, boolean fixAspectRatio, int aspectRatioX, int aspectRatioY, int reqWidth, int reqHeight, boolean flipHorizontally, boolean flipVertically, CropImageView.RequestSizeOptions options, Uri saveUri, Bitmap.CompressFormat saveCompressFormat, int saveCompressQuality) { mCropImageViewReference = new WeakReference<>(cropImageView); mContext = cropImageView.getContext(); mBitmap = bitmap; mCropPoints = cropPoints; mUri = null; mDegreesRotated = degreesRotated; mFixAspectRatio = fixAspectRatio; mAspectRatioX = aspectRatioX; mAspectRatioY = aspectRatioY; mReqWidth = reqWidth; mReqHeight = reqHeight; mFlipHorizontally = flipHorizontally; mFlipVertically = flipVertically; mReqSizeOptions = options; mSaveUri = saveUri; mSaveCompressFormat = saveCompressFormat; mSaveCompressQuality = saveCompressQuality; mOrgWidth = 0; mOrgHeight = 0; } BitmapCroppingWorkerTask( CropImageView cropImageView, Uri uri, float[] cropPoints, int degreesRotated, int orgWidth, int orgHeight, boolean fixAspectRatio, int aspectRatioX, int aspectRatioY, int reqWidth, int reqHeight, boolean flipHorizontally, boolean flipVertically, CropImageView.RequestSizeOptions options, Uri saveUri, Bitmap.CompressFormat saveCompressFormat, int saveCompressQuality) { mCropImageViewReference = new WeakReference<>(cropImageView); mContext = cropImageView.getContext(); mUri = uri; mCropPoints = cropPoints; mDegreesRotated = degreesRotated; mFixAspectRatio = fixAspectRatio; mAspectRatioX = aspectRatioX; mAspectRatioY = aspectRatioY; mOrgWidth = orgWidth; mOrgHeight = orgHeight; mReqWidth = reqWidth; mReqHeight = reqHeight; mFlipHorizontally = flipHorizontally; mFlipVertically = flipVertically; mReqSizeOptions = options; mSaveUri = saveUri; mSaveCompressFormat = saveCompressFormat; mSaveCompressQuality = saveCompressQuality; mBitmap = null; } /** * The Android URI that this task is currently loading. */ public Uri getUri() { return mUri; } /** * Crop image in background. * * @param params ignored * @return the decoded bitmap data */ @Override protected BitmapCroppingWorkerTask.Result doInBackground(Void... params) { try { if (!isCancelled()) { BitmapUtils.BitmapSampled bitmapSampled; if (mUri != null) { bitmapSampled = BitmapUtils.cropBitmap( mContext, mUri, mCropPoints, mDegreesRotated, mOrgWidth, mOrgHeight, mFixAspectRatio, mAspectRatioX, mAspectRatioY, mReqWidth, mReqHeight, mFlipHorizontally, mFlipVertically); } else if (mBitmap != null) { bitmapSampled = BitmapUtils.cropBitmapObjectHandleOOM( mBitmap, mCropPoints, mDegreesRotated, mFixAspectRatio, mAspectRatioX, mAspectRatioY, mFlipHorizontally, mFlipVertically); } else { return new Result((Bitmap) null, 1); } Bitmap bitmap = BitmapUtils.resizeBitmap(bitmapSampled.bitmap, mReqWidth, mReqHeight, mReqSizeOptions); if (mSaveUri == null) { return new Result(bitmap, bitmapSampled.sampleSize); } else { BitmapUtils.writeBitmapToUri( mContext, bitmap, mSaveUri, mSaveCompressFormat, mSaveCompressQuality); if (bitmap != null) { bitmap.recycle(); } return new Result(mSaveUri, bitmapSampled.sampleSize); } } return null; } catch (Exception e) { return new Result(e, mSaveUri != null); } } /** * Once complete, see if ImageView is still around and set bitmap. * * @param result the result of bitmap cropping */ @Override protected void onPostExecute(Result result) { if (result != null) { boolean completeCalled = false; if (!isCancelled()) { CropImageView cropImageView = mCropImageViewReference.get(); if (cropImageView != null) { completeCalled = true; cropImageView.onImageCroppingAsyncComplete(result); } } if (!completeCalled && result.bitmap != null) { // fast release of unused bitmap result.bitmap.recycle(); } } } // region: Inner class: Result /** * The result of BitmapCroppingWorkerTask async loading. */ static final class Result { /** * The cropped bitmap */ public final Bitmap bitmap; /** * The saved cropped bitmap uri */ public final Uri uri; /** * The error that occurred during async bitmap cropping. */ final Exception error; /** * is the cropping request was to get a bitmap or to save it to uri */ final boolean isSave; /** * sample size used creating the crop bitmap to lower its size */ final int sampleSize; Result(Bitmap bitmap, int sampleSize) { this.bitmap = bitmap; this.uri = null; this.error = null; this.isSave = false; this.sampleSize = sampleSize; } Result(Uri uri, int sampleSize) { this.bitmap = null; this.uri = uri; this.error = null; this.isSave = true; this.sampleSize = sampleSize; } Result(Exception error, boolean isSave) { this.bitmap = null; this.uri = null; this.error = error; this.isSave = isSave; this.sampleSize = 1; } } // endregion }