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 {