diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd62b21..ef515041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,21 +4,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - `Added` for new features. - `Changed` for changes in existing functionality. -- `Deprecated for soon-to-be removed features. +- `Deprecated` for soon-to-be removed features. - `Removed` for now removed features. - `Fixed` for any bug fixes. - `Security` in case of vulnerabilities. ## unreleased -## [1.1.1] - 03/01/20 +## [2.0.0] - 06/01/21 +### Changed +- AsyncTask to Kotlin Coroutines + +## [1.1.1] - 03/01/21 ### Added - Ktlint - Release using JitPack ### Changed - Java to kotlin -- Cange icons from PNG to vectors +- Change icons from PNG to vectors ## [1.1.0] - 13/12/20 ### Changed diff --git a/README.md b/README.md index aff6915d..14f390bf 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Android Image Cropper #### Step 3. Add permissions to manifest - ``` + ```xml ``` @@ -48,6 +48,19 @@ Android Image Cropper -keep class androidx.appcompat.widget.** { *; } ``` +#### Step 5. Set source compatibility version to Java 8 + +- Go to app level `build.gradle` file + +- Add this line inside ```android``` in build.gradle + ```groovy + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + ``` + +- This will set the java version to 8 ## Using Activity diff --git a/build.gradle b/build.gradle index 9d02cf75..5de5694d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.4.20' + ext.kotlin_version = '1.4.21' repositories { maven { url "https://plugins.gradle.org/m2/" } jcenter() @@ -30,7 +30,7 @@ allprojects { ext { compileSdkVersion = 30 targetSdkVersion = 30 - buildToolsVersion = '29.0.3' + buildToolsVersion = '30.0.2' // AndroidX androidXAppCompatVersion = '1.2.0' diff --git a/cropper/build.gradle b/cropper/build.gradle index 378724c8..f0567052 100644 --- a/cropper/build.gradle +++ b/cropper/build.gradle @@ -30,6 +30,10 @@ dependencies { implementation "androidx.exifinterface:exifinterface:$androidXExifVersion" implementation "androidx.core:core-ktx:1.3.2" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0" + def coroutines_version = "1.4.2" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" } repositories { mavenCentral() diff --git a/cropper/src/main/java/com/canhub/cropper/BitmapCroppingWorkerJob.kt b/cropper/src/main/java/com/canhub/cropper/BitmapCroppingWorkerJob.kt new file mode 100644 index 00000000..48cfeea2 --- /dev/null +++ b/cropper/src/main/java/com/canhub/cropper/BitmapCroppingWorkerJob.kt @@ -0,0 +1,247 @@ +package com.canhub.cropper + +import android.graphics.Bitmap +import android.net.Uri +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.lang.ref.WeakReference + +class BitmapCroppingWorkerJob internal constructor( + private val activity: FragmentActivity, + private val cropImageViewReference: WeakReference, + val uri: Uri?, + private val bitmap: Bitmap?, + private val cropPoints: FloatArray, + private val degreesRotated: Int, + private val orgWidth: Int, + private val orgHeight: Int, + private val fixAspectRatio: Boolean, + private val aspectRatioX: Int, + private val aspectRatioY: Int, + private val reqWidth: Int, + private val reqHeight: Int, + private val flipHorizontally: Boolean, + private val flipVertically: Boolean, + private val options: CropImageView.RequestSizeOptions, + private val saveUri: Uri?, + private val saveCompressFormat: Bitmap.CompressFormat, + private val saveCompressQuality: Int +) { + private var currentJob: Job? = null + + constructor( + activity: FragmentActivity, + cropImageView: CropImageView, + bitmap: Bitmap, + cropPoints: FloatArray, + degreesRotated: Int, + fixAspectRatio: Boolean, + aspectRatioX: Int, + aspectRatioY: Int, + reqWidth: Int, + reqHeight: Int, + flipHorizontally: Boolean, + flipVertically: Boolean, + options: CropImageView.RequestSizeOptions, + saveUri: Uri, + saveCompressFormat: Bitmap.CompressFormat, + saveCompressQuality: Int + ) : this( + activity, + WeakReference(cropImageView), + null, + bitmap, + cropPoints, + degreesRotated, + 0, + 0, + fixAspectRatio, + aspectRatioX, + aspectRatioY, + reqWidth, + reqHeight, + flipHorizontally, + flipVertically, + options, + saveUri, + saveCompressFormat, + saveCompressQuality + ) + + constructor( + activity: FragmentActivity, + cropImageView: CropImageView, + uri: Uri, + cropPoints: FloatArray, + degreesRotated: Int, + orgWidth: Int, + orgHeight: Int, + fixAspectRatio: Boolean, + aspectRatioX: Int, + aspectRatioY: Int, + reqWidth: Int, + reqHeight: Int, + flipHorizontally: Boolean, + flipVertically: Boolean, + options: CropImageView.RequestSizeOptions, + saveUri: Uri, + saveCompressFormat: Bitmap.CompressFormat, + saveCompressQuality: Int + ) : this( + activity, + WeakReference(cropImageView), + uri, + null, + cropPoints, + degreesRotated, + orgWidth, + orgHeight, + fixAspectRatio, + aspectRatioX, + aspectRatioY, + reqWidth, + reqHeight, + flipHorizontally, + flipVertically, + options, + saveUri, + saveCompressFormat, + saveCompressQuality + ) + + fun start() { + currentJob = activity.lifecycleScope.launch(Dispatchers.Default) { + try { + if (isActive) { + val bitmapSampled: BitmapUtils.BitmapSampled + if (uri != null) { + bitmapSampled = BitmapUtils.cropBitmap( + activity, + uri, + cropPoints, + degreesRotated, + orgWidth, + orgHeight, + fixAspectRatio, + aspectRatioX, + aspectRatioY, + reqWidth, + reqHeight, + flipHorizontally, + flipVertically + ) + } else if (bitmap != null) { + bitmapSampled = BitmapUtils.cropBitmapObjectHandleOOM( + bitmap, + cropPoints, + degreesRotated, + fixAspectRatio, + aspectRatioX, + aspectRatioY, + flipHorizontally, + flipVertically + ) + } else { + onPostExecute(Result(bitmap = null, 1)) + return@launch + } + val resizedBitmap = + BitmapUtils.resizeBitmap(bitmapSampled.bitmap, reqWidth, reqHeight, options) + if (saveUri == null) { + onPostExecute( + Result( + resizedBitmap, + bitmapSampled.sampleSize + ) + ) + } else { + BitmapUtils.writeBitmapToUri( + activity, + resizedBitmap, + saveUri, + saveCompressFormat, + saveCompressQuality + ) + resizedBitmap?.recycle() + onPostExecute( + Result( + saveUri, + bitmapSampled.sampleSize + ) + ) + } + } + } catch (e: Exception) { + onPostExecute(Result(e, saveUri != null)) + } + } + } + + private suspend fun onPostExecute(result: Result) { + withContext(Dispatchers.Main) { + var completeCalled = false + if (isActive) { + cropImageViewReference.get()?.let { + completeCalled = true + it.onImageCroppingAsyncComplete(result) + } + } + if (!completeCalled && result.bitmap != null) { + // fast release of unused bitmap + result.bitmap.recycle() + } + } + } + + fun cancel() { + currentJob?.cancel() + } + + // region: Inner class: Result + companion object class Result { + + /** The cropped bitmap */ + val bitmap: Bitmap? + + /** The saved cropped bitmap uri */ + val uri: Uri? + + /** The error that occurred during async bitmap cropping. */ + val error: java.lang.Exception? + + /** is the cropping request was to get a bitmap or to save it to uri */ + val isSave: Boolean + + /** sample size used creating the crop bitmap to lower its size */ + val sampleSize: Int + + constructor(bitmap: Bitmap?, sampleSize: Int) { + this.bitmap = bitmap + uri = null + error = null + isSave = false + this.sampleSize = sampleSize + } + + constructor(uri: Uri?, sampleSize: Int) { + bitmap = null + this.uri = uri + error = null + isSave = true + this.sampleSize = sampleSize + } + + constructor(error: java.lang.Exception?, isSave: Boolean) { + bitmap = null + uri = null + this.error = error + this.isSave = isSave + sampleSize = 1 + } + } +} diff --git a/cropper/src/main/java/com/canhub/cropper/BitmapCroppingWorkerTask.java b/cropper/src/main/java/com/canhub/cropper/BitmapCroppingWorkerTask.java deleted file mode 100644 index 1395ff8e..00000000 --- a/cropper/src/main/java/com/canhub/cropper/BitmapCroppingWorkerTask.java +++ /dev/null @@ -1,300 +0,0 @@ -// "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.canhub.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 -} diff --git a/cropper/src/main/java/com/canhub/cropper/BitmapLoadingWorkerJob.kt b/cropper/src/main/java/com/canhub/cropper/BitmapLoadingWorkerJob.kt new file mode 100644 index 00000000..8dca338d --- /dev/null +++ b/cropper/src/main/java/com/canhub/cropper/BitmapLoadingWorkerJob.kt @@ -0,0 +1,110 @@ +package com.canhub.cropper + +import android.graphics.Bitmap +import android.net.Uri +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.lifecycleScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.lang.ref.WeakReference + +internal class BitmapLoadingWorkerJob internal constructor( + private val activity: FragmentActivity, + cropImageView: CropImageView, + val uri: Uri +) { + private val width: Int + private val height: Int + private val cropImageViewReference = WeakReference(cropImageView) + private var currentJob: Job? = null + + init { + val metrics = cropImageView.resources.displayMetrics + val densityAdj: Double = if (metrics.density > 1) (1.0 / metrics.density) else 1.0 + width = (metrics.widthPixels * densityAdj).toInt() + height = (metrics.heightPixels * densityAdj).toInt() + } + + fun start() { + currentJob = activity.lifecycleScope.launch(Dispatchers.Default) { + try { + if (isActive) { + val decodeResult = + BitmapUtils.decodeSampledBitmap(activity, uri, width, height) + if (isActive) { + val rotateResult = + BitmapUtils.rotateBitmapByExif(decodeResult.bitmap, activity, uri) + onPostExecute(Result(uri, rotateResult.bitmap, decodeResult.sampleSize, rotateResult.degrees)) + } + } + } catch (e: Exception) { + onPostExecute(Result(uri, e)) + } + } + } + + /** + * Once complete, see if ImageView is still around and set bitmap. + * + * @param result the result of bitmap loading + */ + private suspend fun onPostExecute(result: Result) { + withContext(Dispatchers.Main) { + var completeCalled = false + if (isActive) { + cropImageViewReference.get()?.let { + completeCalled = true + it.onSetImageUriAsyncComplete(result) + } + } + if (!completeCalled && result.bitmap != null) { + // fast release of unused bitmap + result.bitmap.recycle() + } + } + } + + fun cancel() { + currentJob?.cancel() + } + + // region: Inner class: Result + /** The result of BitmapLoadingWorkerJob async loading. */ + companion object class Result { + + /** The Android URI of the image to load */ + val uri: Uri + + /** The loaded bitmap */ + val bitmap: Bitmap? + + /** The sample size used to load the given bitmap */ + val loadSampleSize: Int + + /** The degrees the image was rotated */ + val degreesRotated: Int + + /** The error that occurred during async bitmap loading. */ + val error: Exception? + + internal constructor(uri: Uri, bitmap: Bitmap?, loadSampleSize: Int, degreesRotated: Int) { + this.uri = uri + this.bitmap = bitmap + this.loadSampleSize = loadSampleSize + this.degreesRotated = degreesRotated + error = null + } + + internal constructor(uri: Uri, error: Exception?) { + this.uri = uri + bitmap = null + loadSampleSize = 0 + degreesRotated = 0 + this.error = error + } + } + // endregion +} diff --git a/cropper/src/main/java/com/canhub/cropper/BitmapLoadingWorkerTask.java b/cropper/src/main/java/com/canhub/cropper/BitmapLoadingWorkerTask.java deleted file mode 100644 index e06ea0d8..00000000 --- a/cropper/src/main/java/com/canhub/cropper/BitmapLoadingWorkerTask.java +++ /dev/null @@ -1,150 +0,0 @@ -// "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.canhub.cropper; - -import android.content.Context; -import android.graphics.Bitmap; -import android.net.Uri; -import android.os.AsyncTask; -import android.util.DisplayMetrics; - -import java.lang.ref.WeakReference; - -/** Task to load bitmap asynchronously from the UI thread. */ -final class BitmapLoadingWorkerTask extends AsyncTask { - - // region: Fields and Consts - - /** Use a WeakReference to ensure the ImageView can be garbage collected */ - private final WeakReference mCropImageViewReference; - - /** 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 width of the cropping image after density adjustment */ - private final int mWidth; - - /** required height of the cropping image after density adjustment */ - private final int mHeight; - // endregion - - public BitmapLoadingWorkerTask(CropImageView cropImageView, Uri uri) { - mUri = uri; - mCropImageViewReference = new WeakReference<>(cropImageView); - - mContext = cropImageView.getContext(); - - DisplayMetrics metrics = cropImageView.getResources().getDisplayMetrics(); - double densityAdj = metrics.density > 1 ? 1 / metrics.density : 1; - mWidth = (int) (metrics.widthPixels * densityAdj); - mHeight = (int) (metrics.heightPixels * densityAdj); - } - - /** The Android URI that this task is currently loading. */ - public Uri getUri() { - return mUri; - } - - /** - * Decode image in background. - * - * @param params ignored - * @return the decoded bitmap data - */ - @Override - protected Result doInBackground(Void... params) { - try { - if (!isCancelled()) { - - BitmapUtils.BitmapSampled decodeResult = - BitmapUtils.decodeSampledBitmap(mContext, mUri, mWidth, mHeight); - - if (!isCancelled()) { - - BitmapUtils.RotateBitmapResult rotateResult = - BitmapUtils.rotateBitmapByExif(decodeResult.bitmap, mContext, mUri); - - return new Result( - mUri, rotateResult.bitmap, decodeResult.sampleSize, rotateResult.degrees); - } - } - return null; - } catch (Exception e) { - return new Result(mUri, e); - } - } - - /** - * Once complete, see if ImageView is still around and set bitmap. - * - * @param result the result of bitmap loading - */ - @Override - protected void onPostExecute(Result result) { - if (result != null) { - boolean completeCalled = false; - if (!isCancelled()) { - CropImageView cropImageView = mCropImageViewReference.get(); - if (cropImageView != null) { - completeCalled = true; - cropImageView.onSetImageUriAsyncComplete(result); - } - } - if (!completeCalled && result.bitmap != null) { - // fast release of unused bitmap - result.bitmap.recycle(); - } - } - } - - // region: Inner class: Result - - /** The result of BitmapLoadingWorkerTask async loading. */ - public static final class Result { - - /** The Android URI of the image to load */ - public final Uri uri; - - /** The loaded bitmap */ - public final Bitmap bitmap; - - /** The sample size used to load the given bitmap */ - public final int loadSampleSize; - - /** The degrees the image was rotated */ - public final int degreesRotated; - - /** The error that occurred during async bitmap loading. */ - public final Exception error; - - Result(Uri uri, Bitmap bitmap, int loadSampleSize, int degreesRotated) { - this.uri = uri; - this.bitmap = bitmap; - this.loadSampleSize = loadSampleSize; - this.degreesRotated = degreesRotated; - this.error = null; - } - - Result(Uri uri, Exception error) { - this.uri = uri; - this.bitmap = null; - this.loadSampleSize = 0; - this.degreesRotated = 0; - this.error = error; - } - } - // endregion -} diff --git a/cropper/src/main/java/com/canhub/cropper/BitmapUtils.java b/cropper/src/main/java/com/canhub/cropper/BitmapUtils.java index 40835fe7..dacbc1bc 100644 --- a/cropper/src/main/java/com/canhub/cropper/BitmapUtils.java +++ b/cropper/src/main/java/com/canhub/cropper/BitmapUtils.java @@ -227,7 +227,7 @@ private static Bitmap cropBitmapObjectWithScale( // crop and rotate the cropped image in one operation Matrix matrix = new Matrix(); - matrix.setRotate(degreesRotated, bitmap.getWidth() / 2, bitmap.getHeight() / 2); + matrix.setRotate(degreesRotated, bitmap.getWidth() / 2.0f, bitmap.getHeight() / 2.0f); matrix.postScale(flipHorizontally ? -scale : scale, flipVertically ? -scale : scale); Bitmap result = Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height(), matrix, true); @@ -412,7 +412,7 @@ static Uri writeTempStateStoreBitmap(Context context, Bitmap bitmap, Uri uri) { boolean needSave = true; if (uri == null) { // We have this because of a HUAWEI path bug when we use getUriForFile - if (new CommonVersionCheck().isAtLeastQ29()) { + if (CommonVersionCheck.INSTANCE.isAtLeastQ29()) { uri = FileProvider.getUriForFile( context, context.getPackageName() + CommonValues.authority, diff --git a/cropper/src/main/java/com/canhub/cropper/CropImage.java b/cropper/src/main/java/com/canhub/cropper/CropImage.java index 28178434..5d44893f 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropImage.java +++ b/cropper/src/main/java/com/canhub/cropper/CropImage.java @@ -338,7 +338,7 @@ public static Uri getCaptureImageOutputUri(@NonNull Context context) { File getImage; // We have this because of a HUAWEI path bug when we use getUriForFile - if (new CommonVersionCheck().isAtLeastQ29()) { + if (CommonVersionCheck.INSTANCE.isAtLeastQ29()) { getImage = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); try { outputFileUri = FileProvider.getUriForFile( diff --git a/cropper/src/main/java/com/canhub/cropper/CropImageActivity.kt b/cropper/src/main/java/com/canhub/cropper/CropImageActivity.kt index 695cf78a..c0d063ad 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropImageActivity.kt +++ b/cropper/src/main/java/com/canhub/cropper/CropImageActivity.kt @@ -5,8 +5,6 @@ import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap -import android.graphics.BlendMode -import android.graphics.BlendModeColorFilter import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle @@ -18,6 +16,8 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.core.graphics.BlendModeColorFilterCompat +import androidx.core.graphics.BlendModeCompat import com.canhub.cropper.CropImageView.CropResult import com.canhub.cropper.CropImageView.OnCropImageCompleteListener import com.canhub.cropper.CropImageView.OnSetImageUriCompleteListener @@ -70,7 +70,7 @@ class CropImageActivity : } else if (cropImageUri?.let { CropImage.isReadExternalStoragePermissionsRequired(this, it) } == true && - CommonVersionCheck().isAtLeastM23() + CommonVersionCheck.isAtLeastM23() ) { // request permissions and handle the result in onRequestPermissionsResult() requestPermissions( @@ -174,7 +174,7 @@ class CropImageActivity : if (cropImageUri?.let { CropImage.isReadExternalStoragePermissionsRequired(this, it) } == true && - CommonVersionCheck().isAtLeastM23() + CommonVersionCheck.isAtLeastM23() ) { // request permissions and handle the result in onRequestPermissionsResult() requestPermissions( @@ -271,7 +271,7 @@ class CropImageActivity : else -> ".webp" } // We have this because of a HUAWEI path bug when we use getUriForFile - if (CommonVersionCheck().isAtLeastQ29()) { + if (CommonVersionCheck.isAtLeastQ29()) { try { FileProvider.getUriForFile( applicationContext, @@ -347,7 +347,7 @@ class CropImageActivity : try { menuItemIcon.apply { mutate() - colorFilter = BlendModeColorFilter(color, BlendMode.SRC_ATOP) + colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(color, BlendModeCompat.SRC_ATOP) } menuItem.icon = menuItemIcon } catch (e: Exception) { diff --git a/cropper/src/main/java/com/canhub/cropper/CropImageView.java b/cropper/src/main/java/com/canhub/cropper/CropImageView.java index d9828a26..5e262b4a 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropImageView.java +++ b/cropper/src/main/java/com/canhub/cropper/CropImageView.java @@ -22,10 +22,11 @@ import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Parcelable; import androidx.exifinterface.media.ExifInterface; +import androidx.fragment.app.FragmentActivity; + import android.util.AttributeSet; import android.util.Pair; import android.view.LayoutInflater; @@ -41,7 +42,7 @@ /** Custom view that provides cropping capabilities to an image. */ public class CropImageView extends FrameLayout { - // region: Fields and Consts + // region: Fields and Constants /** Image view widget used to show the image for cropping. */ private final ImageView mImageView; @@ -169,10 +170,10 @@ public class CropImageView extends FrameLayout { private Uri mSaveInstanceStateBitmapUri; /** Task used to load bitmap async from UI thread */ - private WeakReference mBitmapLoadingWorkerTask; + private WeakReference mBitmapLoadingWorkerJob; /** Task used to crop bitmap async from UI thread */ - private WeakReference mBitmapCroppingWorkerTask; + private WeakReference mBitmapCroppingWorkerJob; // endregion public CropImageView(Context context) { @@ -331,9 +332,7 @@ public CropImageView(Context context, AttributeSet attrs) { mCropOverlayView = v.findViewById(R.id.CropOverlayView); mCropOverlayView.setCropWindowChangeListener( - new CropOverlayView.CropWindowChangeListener() { - @Override - public void onCropWindowChanged(boolean inProgress) { + inProgress -> { handleCropWindowChanged(inProgress, true); OnSetCropOverlayReleasedListener listener = mOnCropOverlayReleasedListener; if (listener != null && !inProgress) { @@ -343,8 +342,7 @@ public void onCropWindowChanged(boolean inProgress) { if (movedListener != null && inProgress) { movedListener.onCropOverlayMoved(getCropRect()); } - } - }); + }); mCropOverlayView.setInitialAttributeValues(options); mProgressBar = v.findViewById(R.id.CropProgressBar); @@ -1010,12 +1008,12 @@ public void setImageResource(int resId) { */ public void setImageUriAsync(Uri uri) { if (uri != null) { - BitmapLoadingWorkerTask currentTask = - mBitmapLoadingWorkerTask != null ? mBitmapLoadingWorkerTask.get() : null; + BitmapLoadingWorkerJob currentTask = + mBitmapLoadingWorkerJob != null ? mBitmapLoadingWorkerJob.get() : null; if (currentTask != null) { // cancel previous loading (no check if the same URI because camera URI can be the same for // different images) - currentTask.cancel(true); + currentTask.cancel(); } // either no existing task is working or we canceled it, need to load new URI @@ -1023,8 +1021,8 @@ public void setImageUriAsync(Uri uri) { mRestoreCropWindowRect = null; mRestoreDegreesRotated = 0; mCropOverlayView.setInitialCropWindowRect(null); - mBitmapLoadingWorkerTask = new WeakReference<>(new BitmapLoadingWorkerTask(this, uri)); - mBitmapLoadingWorkerTask.get().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + mBitmapLoadingWorkerJob = new WeakReference<>(new BitmapLoadingWorkerJob((FragmentActivity)getContext(),this, uri)); + mBitmapLoadingWorkerJob.get().start(); setProgressBarVisibility(); } } @@ -1136,19 +1134,19 @@ public void flipImageVertically() { * * @param result the result of bitmap loading */ - void onSetImageUriAsyncComplete(BitmapLoadingWorkerTask.Result result) { + void onSetImageUriAsyncComplete(BitmapLoadingWorkerJob.Result result) { - mBitmapLoadingWorkerTask = null; + mBitmapLoadingWorkerJob = null; setProgressBarVisibility(); - if (result.error == null) { - mInitialDegreesRotated = result.degreesRotated; - setBitmap(result.bitmap, 0, result.uri, result.loadSampleSize, result.degreesRotated); + if (result.getError() == null) { + mInitialDegreesRotated = result.getDegreesRotated(); + setBitmap(result.getBitmap(), 0, result.getUri(), result.getLoadSampleSize(), result.getDegreesRotated()); } OnSetImageUriCompleteListener listener = mOnSetImageUriCompleteListener; if (listener != null) { - listener.onSetImageUriComplete(this, result.uri, result.error); + listener.onSetImageUriComplete(this, result.getUri(), result.getError()); } } @@ -1158,9 +1156,9 @@ void onSetImageUriAsyncComplete(BitmapLoadingWorkerTask.Result result) { * * @param result the result of bitmap cropping */ - void onImageCroppingAsyncComplete(BitmapCroppingWorkerTask.Result result) { + void onImageCroppingAsyncComplete(BitmapCroppingWorkerJob.Result result) { - mBitmapCroppingWorkerTask = null; + mBitmapCroppingWorkerJob = null; setProgressBarVisibility(); OnCropImageCompleteListener listener = mOnCropImageCompleteListener; @@ -1169,14 +1167,14 @@ void onImageCroppingAsyncComplete(BitmapCroppingWorkerTask.Result result) { new CropResult( mBitmap, mLoadedImageUri, - result.bitmap, - result.uri, - result.error, + result.getBitmap(), + result.getUri(), + result.getError(), getCropPoints(), getCropRect(), getWholeImageRect(), getRotatedDegrees(), - result.sampleSize); + result.getSampleSize()); listener.onCropImageComplete(this, cropResult); } } @@ -1268,11 +1266,11 @@ public void startCropWorkerTask( if (bitmap != null) { mImageView.clearAnimation(); - BitmapCroppingWorkerTask currentTask = - mBitmapCroppingWorkerTask != null ? mBitmapCroppingWorkerTask.get() : null; + BitmapCroppingWorkerJob currentTask = + mBitmapCroppingWorkerJob != null ? mBitmapCroppingWorkerJob.get() : null; if (currentTask != null) { // cancel previous cropping - currentTask.cancel(true); + currentTask.cancel(); } reqWidth = options != RequestSizeOptions.NONE ? reqWidth : 0; @@ -1282,9 +1280,10 @@ public void startCropWorkerTask( int orgHeight = bitmap.getHeight() * mLoadedSampleSize; if (mLoadedImageUri != null && (mLoadedSampleSize > 1 || options == RequestSizeOptions.SAMPLING)) { - mBitmapCroppingWorkerTask = + mBitmapCroppingWorkerJob = new WeakReference<>( - new BitmapCroppingWorkerTask( + new BitmapCroppingWorkerJob( + (FragmentActivity)getContext(), this, mLoadedImageUri, getCropPoints(), @@ -1303,9 +1302,10 @@ public void startCropWorkerTask( saveCompressFormat, saveCompressQuality)); } else { - mBitmapCroppingWorkerTask = + mBitmapCroppingWorkerJob = new WeakReference<>( - new BitmapCroppingWorkerTask( + new BitmapCroppingWorkerJob( + (FragmentActivity)getContext(), this, bitmap, getCropPoints(), @@ -1322,7 +1322,7 @@ public void startCropWorkerTask( saveCompressFormat, saveCompressQuality)); } - mBitmapCroppingWorkerTask.get().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + mBitmapCroppingWorkerJob.get().start(); setProgressBarVisibility(); } } @@ -1346,8 +1346,8 @@ public Parcelable onSaveInstanceState() { BitmapUtils.mStateBitmap = new Pair<>(key, new WeakReference<>(mBitmap)); bundle.putString("LOADED_IMAGE_STATE_BITMAP_KEY", key); } - if (mBitmapLoadingWorkerTask != null) { - BitmapLoadingWorkerTask task = mBitmapLoadingWorkerTask.get(); + if (mBitmapLoadingWorkerJob != null) { + BitmapLoadingWorkerJob task = mBitmapLoadingWorkerJob.get(); if (task != null) { bundle.putParcelable("LOADING_IMAGE_URI", task.getUri()); } @@ -1381,7 +1381,7 @@ public void onRestoreInstanceState(Parcelable state) { Bundle bundle = (Bundle) state; // prevent restoring state if already set by outside code - if (mBitmapLoadingWorkerTask == null + if (mBitmapLoadingWorkerJob == null && mLoadedImageUri == null && mBitmap == null && mImageResource == 0) { @@ -1795,8 +1795,8 @@ private void setCropOverlayVisibility() { private void setProgressBarVisibility() { boolean visible = mShowProgressBar - && (mBitmap == null && mBitmapLoadingWorkerTask != null - || mBitmapCroppingWorkerTask != null); + && (mBitmap == null && mBitmapLoadingWorkerJob != null + || mBitmapCroppingWorkerJob != null); mProgressBar.setVisibility(visible ? VISIBLE : INVISIBLE); } diff --git a/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java b/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java index ea9414e0..57321c2d 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java +++ b/cropper/src/main/java/com/canhub/cropper/CropOverlayView.java @@ -32,7 +32,7 @@ /** A custom View representing the crop window and the shaded background outside the crop window. */ public class CropOverlayView extends View { - // region: Fields and Consts + // region: Fields and Constants /** Gesture detector used for multi touch box scaling */ private ScaleGestureDetector mScaleDetector; diff --git a/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.java b/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.java index 99aad87d..9567c9ff 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.java +++ b/cropper/src/main/java/com/canhub/cropper/CropWindowHandler.java @@ -17,7 +17,7 @@ /** Handler from crop window stuff, moving and knowing possition. */ final class CropWindowHandler { - // region: Fields and Consts + // region: Fields and Constants /** The 4 edges of the crop window defining its coordinates and size */ private final RectF mEdges = new RectF(); diff --git a/cropper/src/main/java/com/canhub/cropper/CropWindowMoveHandler.java b/cropper/src/main/java/com/canhub/cropper/CropWindowMoveHandler.java index f5610fd5..46883c4a 100644 --- a/cropper/src/main/java/com/canhub/cropper/CropWindowMoveHandler.java +++ b/cropper/src/main/java/com/canhub/cropper/CropWindowMoveHandler.java @@ -22,7 +22,7 @@ */ final class CropWindowMoveHandler { - // region: Fields and Consts + // region: Fields and Constants /** Matrix used for rectangle rotation handling */ private static final Matrix MATRIX = new Matrix(); @@ -52,9 +52,7 @@ final class CropWindowMoveHandler { // endregion /** - * @param edgeMoveType the type of move this handler is executing - * @param horizontalEdge the primary edge associated with this handle; may be null - * @param verticalEdge the secondary edge associated with this handle; may be null + * @param type the type of move this handler is executing * @param cropWindowHandler main crop window handle to get and update the crop window edges * @param touchX the location of the initial toch possition to measure move distance * @param touchY the location of the initial toch possition to measure move distance @@ -86,7 +84,6 @@ public CropWindowMoveHandler( * @param viewWidth The bounding image view width used to know the crop overlay is at view edges. * @param viewHeight The bounding image view height used to know the crop overlay is at view * edges. - * @param parentView the parent View containing the image * @param snapMargin the maximum distance (in pixels) at which the crop window should snap to the * image * @param fixedAspectRatio is the aspect ration fixed and 'targetAspectRatio' should be used diff --git a/cropper/src/main/java/com/canhub/cropper/common/CommonVersionCheck.kt b/cropper/src/main/java/com/canhub/cropper/common/CommonVersionCheck.kt index 504d3e8a..a30082e1 100644 --- a/cropper/src/main/java/com/canhub/cropper/common/CommonVersionCheck.kt +++ b/cropper/src/main/java/com/canhub/cropper/common/CommonVersionCheck.kt @@ -3,7 +3,7 @@ package com.canhub.cropper.common import android.os.Build import android.os.Build.VERSION.SDK_INT -class CommonVersionCheck { +object CommonVersionCheck { fun isAtLeastM23() = SDK_INT >= Build.VERSION_CODES.M fun isAtLeastQ29() = SDK_INT >= Build.VERSION_CODES.Q diff --git a/quick-start/build.gradle b/quick-start/build.gradle index 486d7757..493c20db 100644 --- a/quick-start/build.gradle +++ b/quick-start/build.gradle @@ -17,6 +17,10 @@ android { buildFeatures { viewBinding = true } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/sample/build.gradle b/sample/build.gradle index 172c6efb..20062870 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -14,6 +14,10 @@ android { lintOptions { abortOnError false } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies { diff --git a/test/build.gradle b/test/build.gradle index e06703bc..7d857ef7 100644 --- a/test/build.gradle +++ b/test/build.gradle @@ -17,6 +17,10 @@ android { buildFeatures { viewBinding = true } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } } dependencies {