From e5f36c3e43555445e0699c835b2f681e194e9053 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 23 Dec 2022 11:36:03 +0100 Subject: [PATCH] switch lib --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 2 +- .../android/activities/ComposeActivity.java | 1 + .../endpoints/MastodonStatusesService.java | 2 +- .../app/fedilab/android/helper/Helper.java | 52 +- .../fedilab/android/helper/MediaHelper.java | 154 ++ .../imageeditor/EditImageActivity.java | 91 +- .../android/interfaces/ProgressListener.java | 5 + .../fedilab/android/jobs/ComposeWorker.java | 6 +- .../viewmodel/mastodon/StatusesVM.java | 6 +- cropper/build.gradle | 22 - cropper/src/main/AndroidManifest.xml | 3 - .../cropper/BitmapCroppingWorkerTask.java | 354 --- .../cropper/BitmapLoadingWorkerTask.java | 176 -- .../edmodo/cropper/BitmapUtils.java | 923 ------- .../theartofdev/edmodo/cropper/CropImage.java | 1048 -------- .../edmodo/cropper/CropImageActivity.java | 367 --- .../edmodo/cropper/CropImageAnimation.java | 123 - .../edmodo/cropper/CropImageOptions.java | 541 ---- .../edmodo/cropper/CropImageView.java | 2296 ----------------- .../edmodo/cropper/CropOverlayView.java | 1118 -------- .../edmodo/cropper/CropWindowHandler.java | 405 --- .../edmodo/cropper/CropWindowMoveHandler.java | 786 ------ .../drawable-hdpi/crop_image_menu_flip.png | Bin 262 -> 0 bytes .../crop_image_menu_rotate_left.png | Bin 634 -> 0 bytes .../crop_image_menu_rotate_right.png | Bin 617 -> 0 bytes .../drawable-xhdpi/crop_image_menu_flip.png | Bin 259 -> 0 bytes .../crop_image_menu_rotate_left.png | Bin 798 -> 0 bytes .../crop_image_menu_rotate_right.png | Bin 787 -> 0 bytes .../drawable-xxhdpi/crop_image_menu_flip.png | Bin 417 -> 0 bytes .../crop_image_menu_rotate_left.png | Bin 1181 -> 0 bytes .../crop_image_menu_rotate_right.png | Bin 1165 -> 0 bytes .../drawable-xxxhdpi/crop_image_menu_flip.png | Bin 508 -> 0 bytes .../crop_image_menu_rotate_left.png | Bin 1577 -> 0 bytes .../crop_image_menu_rotate_right.png | Bin 1570 -> 0 bytes .../main/res/layout/crop_image_activity.xml | 5 - .../src/main/res/layout/crop_image_view.xml | 25 - cropper/src/main/res/menu/crop_image_menu.xml | 35 - cropper/src/main/res/values-ar/strings.xml | 16 - cropper/src/main/res/values-cs/strings.xml | 16 - cropper/src/main/res/values-de/strings.xml | 16 - .../src/main/res/values-es-rGT/strings.xml | 12 - cropper/src/main/res/values-es/strings.xml | 12 - cropper/src/main/res/values-fa/strings.xml | 11 - cropper/src/main/res/values-fr/strings.xml | 12 - cropper/src/main/res/values-he/strings.xml | 16 - cropper/src/main/res/values-hi/strings.xml | 11 - cropper/src/main/res/values-id/strings.xml | 12 - cropper/src/main/res/values-in/strings.xml | 12 - cropper/src/main/res/values-it/strings.xml | 16 - cropper/src/main/res/values-ja/strings.xml | 11 - cropper/src/main/res/values-ko/strings.xml | 16 - cropper/src/main/res/values-ms/strings.xml | 12 - cropper/src/main/res/values-nb/strings.xml | 16 - cropper/src/main/res/values-nl/strings.xml | 16 - cropper/src/main/res/values-pl/strings.xml | 16 - .../src/main/res/values-pt-rBR/strings.xml | 14 - .../src/main/res/values-ru-rRU/strings.xml | 10 - cropper/src/main/res/values-sv/strings.xml | 16 - cropper/src/main/res/values-tr/strings.xml | 12 - cropper/src/main/res/values-ur/strings.xml | 12 - cropper/src/main/res/values-vi/strings.xml | 16 - .../src/main/res/values-zh-rCN/strings.xml | 16 - .../src/main/res/values-zh-rTW/strings.xml | 16 - cropper/src/main/res/values-zh/strings.xml | 16 - cropper/src/main/res/values/attrs.xml | 50 - cropper/src/main/res/values/strings.xml | 16 - settings.gradle | 1 - 68 files changed, 281 insertions(+), 8715 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/interfaces/ProgressListener.java delete mode 100644 cropper/build.gradle delete mode 100644 cropper/src/main/AndroidManifest.xml delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropOverlayView.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowHandler.java delete mode 100644 cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowMoveHandler.java delete mode 100644 cropper/src/main/res/drawable-hdpi/crop_image_menu_flip.png delete mode 100644 cropper/src/main/res/drawable-hdpi/crop_image_menu_rotate_left.png delete mode 100644 cropper/src/main/res/drawable-hdpi/crop_image_menu_rotate_right.png delete mode 100644 cropper/src/main/res/drawable-xhdpi/crop_image_menu_flip.png delete mode 100644 cropper/src/main/res/drawable-xhdpi/crop_image_menu_rotate_left.png delete mode 100644 cropper/src/main/res/drawable-xhdpi/crop_image_menu_rotate_right.png delete mode 100644 cropper/src/main/res/drawable-xxhdpi/crop_image_menu_flip.png delete mode 100644 cropper/src/main/res/drawable-xxhdpi/crop_image_menu_rotate_left.png delete mode 100644 cropper/src/main/res/drawable-xxhdpi/crop_image_menu_rotate_right.png delete mode 100644 cropper/src/main/res/drawable-xxxhdpi/crop_image_menu_flip.png delete mode 100644 cropper/src/main/res/drawable-xxxhdpi/crop_image_menu_rotate_left.png delete mode 100644 cropper/src/main/res/drawable-xxxhdpi/crop_image_menu_rotate_right.png delete mode 100644 cropper/src/main/res/layout/crop_image_activity.xml delete mode 100644 cropper/src/main/res/layout/crop_image_view.xml delete mode 100644 cropper/src/main/res/menu/crop_image_menu.xml delete mode 100644 cropper/src/main/res/values-ar/strings.xml delete mode 100644 cropper/src/main/res/values-cs/strings.xml delete mode 100644 cropper/src/main/res/values-de/strings.xml delete mode 100644 cropper/src/main/res/values-es-rGT/strings.xml delete mode 100644 cropper/src/main/res/values-es/strings.xml delete mode 100644 cropper/src/main/res/values-fa/strings.xml delete mode 100644 cropper/src/main/res/values-fr/strings.xml delete mode 100644 cropper/src/main/res/values-he/strings.xml delete mode 100644 cropper/src/main/res/values-hi/strings.xml delete mode 100644 cropper/src/main/res/values-id/strings.xml delete mode 100644 cropper/src/main/res/values-in/strings.xml delete mode 100644 cropper/src/main/res/values-it/strings.xml delete mode 100644 cropper/src/main/res/values-ja/strings.xml delete mode 100644 cropper/src/main/res/values-ko/strings.xml delete mode 100644 cropper/src/main/res/values-ms/strings.xml delete mode 100644 cropper/src/main/res/values-nb/strings.xml delete mode 100644 cropper/src/main/res/values-nl/strings.xml delete mode 100644 cropper/src/main/res/values-pl/strings.xml delete mode 100644 cropper/src/main/res/values-pt-rBR/strings.xml delete mode 100644 cropper/src/main/res/values-ru-rRU/strings.xml delete mode 100644 cropper/src/main/res/values-sv/strings.xml delete mode 100644 cropper/src/main/res/values-tr/strings.xml delete mode 100644 cropper/src/main/res/values-ur/strings.xml delete mode 100644 cropper/src/main/res/values-vi/strings.xml delete mode 100644 cropper/src/main/res/values-zh-rCN/strings.xml delete mode 100644 cropper/src/main/res/values-zh-rTW/strings.xml delete mode 100644 cropper/src/main/res/values-zh/strings.xml delete mode 100644 cropper/src/main/res/values/attrs.xml delete mode 100644 cropper/src/main/res/values/strings.xml diff --git a/app/build.gradle b/app/build.gradle index 445efddd..aefc0662 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,11 +8,11 @@ plugins { } def flavor android { - compileSdk 32 + compileSdk 33 defaultConfig { minSdk 21 - targetSdk 32 + targetSdk 33 versionCode 451 versionName "3.12.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -106,7 +106,7 @@ dependencies { implementation 'com.burhanrashid52:photoeditor:1.5.1' - implementation project(path: ':cropper') + implementation("com.vanniktech:android-image-cropper:4.3.3") annotationProcessor "com.github.bumptech.glide:compiler:4.12.0" implementation 'jp.wasabeef:glide-transformations:4.3.0' implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.24.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0a10c058..7c0b89c5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -213,7 +213,7 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/scheduled" /> diff --git a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java index 1688d292..3e344a94 100644 --- a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java @@ -126,6 +126,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana if (focusX != -2) { attachment.focus = focusX + "," + focusY; } + composeAdapter.notifyItemChanged(position); break; } diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonStatusesService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonStatusesService.java index 08ae1d50..cf8613b4 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonStatusesService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonStatusesService.java @@ -251,7 +251,7 @@ public interface MastodonStatusesService { @Part MultipartBody.Part file, @Part MultipartBody.Part thumbnail, @Part("description") RequestBody description, - @Part("focus") String focus + @Part("focus") RequestBody focus ); //Edit a Media diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index fe70d4fe..da475df5 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -1243,32 +1243,48 @@ public class Helper { counter++; Date now = new Date(); attachment.filename = formatter.format(now) + "." + extension; - InputStream selectedFileInputStream; - try { - selectedFileInputStream = context.getContentResolver().openInputStream(uri); - if (selectedFileInputStream != null) { + if (attachment.mimeType.startsWith("image")) { + try { final File certCacheDir = new File(context.getCacheDir(), TEMP_MEDIA_DIRECTORY); boolean isCertCacheDirExists = certCacheDir.exists(); if (!isCertCacheDirExists) { - isCertCacheDirExists = certCacheDir.mkdirs(); + certCacheDir.mkdirs(); } - if (isCertCacheDirExists) { - String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename; - attachment.local_path = filePath; - OutputStream selectedFileOutPutStream = new FileOutputStream(filePath); - byte[] buffer = new byte[1024]; - int length; - while ((length = selectedFileInputStream.read(buffer)) > 0) { - selectedFileOutPutStream.write(buffer, 0, length); + String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename; + MediaHelper.ResizedImageRequestBody(context, uri, filePath); + attachment.local_path = filePath; + } catch (IOException e) { + e.printStackTrace(); + } + } else { + InputStream selectedFileInputStream; + try { + selectedFileInputStream = context.getContentResolver().openInputStream(uri); + if (selectedFileInputStream != null) { + final File certCacheDir = new File(context.getCacheDir(), TEMP_MEDIA_DIRECTORY); + boolean isCertCacheDirExists = certCacheDir.exists(); + if (!isCertCacheDirExists) { + isCertCacheDirExists = certCacheDir.mkdirs(); } - selectedFileOutPutStream.flush(); - selectedFileOutPutStream.close(); + if (isCertCacheDirExists) { + String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename; + attachment.local_path = filePath; + OutputStream selectedFileOutPutStream = new FileOutputStream(filePath); + byte[] buffer = new byte[1024]; + int length; + while ((length = selectedFileInputStream.read(buffer)) > 0) { + selectedFileOutPutStream.write(buffer, 0, length); + } + selectedFileOutPutStream.flush(); + selectedFileOutPutStream.close(); + } + selectedFileInputStream.close(); } - selectedFileInputStream.close(); + } catch (Exception e) { + e.printStackTrace(); } - } catch (Exception e) { - e.printStackTrace(); } + Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> callBack.onAttachmentCopied(attachment); mainHandler.post(myRunnable); diff --git a/app/src/main/java/app/fedilab/android/helper/MediaHelper.java b/app/src/main/java/app/fedilab/android/helper/MediaHelper.java index 93c1b8d3..586df508 100644 --- a/app/src/main/java/app/fedilab/android/helper/MediaHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/MediaHelper.java @@ -24,14 +24,23 @@ import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.database.Cursor; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ImageDecoder; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.media.ExifInterface; import android.media.MediaRecorder; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.provider.MediaStore; +import android.text.TextUtils; import android.text.format.DateFormat; import android.view.View; import android.webkit.MimeTypeMap; @@ -55,6 +64,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.channels.FileChannel; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -69,10 +79,12 @@ import java.util.concurrent.atomic.AtomicInteger; import app.fedilab.android.BuildConfig; import app.fedilab.android.R; import app.fedilab.android.activities.ComposeActivity; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.databinding.DatetimePickerBinding; import app.fedilab.android.databinding.PopupRecordBinding; import es.dmoral.toasty.Toasty; +import okhttp3.MediaType; public class MediaHelper { @@ -420,4 +432,146 @@ public class MediaHelper { public interface OnSchedule { void scheduledAt(String scheduledDate); } + + + public static void ResizedImageRequestBody(Context context, Uri uri, String fullpatch) throws IOException { + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + String contentType; + if ("file".equals(uri.getScheme())) { + BitmapFactory.decodeFile(uri.getPath(), opts); + contentType = MediaHelper.getFileMediaType(new File(uri.getPath())).type(); + } else { + try (InputStream in = context.getContentResolver().openInputStream(uri)) { + BitmapFactory.decodeStream(in, null, opts); + } + contentType = context.getContentResolver().getType(uri); + } + if (TextUtils.isEmpty(contentType)) + contentType = "image/jpeg"; + Bitmap bitmap; + if (Build.VERSION.SDK_INT >= 28) { + ImageDecoder.Source source; + if ("file".equals(uri.getScheme())) { + source = ImageDecoder.createSource(new File(uri.getPath())); + } else { + source = ImageDecoder.createSource(context.getContentResolver(), uri); + } + BitmapFactory.Options finalOpts = opts; + bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, _source) -> { + int[] size = getTargetSize(info.getSize().getWidth(), info.getSize().getHeight()); + decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); + if (needResize(finalOpts.outWidth, finalOpts.outHeight)) { + decoder.setTargetSize(size[0], size[1]); + } + }); + } else { + int[] size = getTargetSize(opts.outWidth, opts.outHeight); + int targetWidth; + int targetHeight; + if (needResize(opts.outWidth, opts.outHeight)) { + targetWidth = size[0]; + targetHeight = size[1]; + } else { + targetWidth = opts.outWidth; + targetHeight = opts.outHeight; + } + float factor = opts.outWidth / (float) targetWidth; + opts = new BitmapFactory.Options(); + opts.inSampleSize = (int) factor; + int orientation = 0; + String[] projection = {MediaStore.Images.ImageColumns.ORIENTATION}; + try { + Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); + if (cursor.moveToFirst()) { + int photoRotation = cursor.getInt(0); + } + cursor.close(); + } catch (Exception e) { + } + + if ("file".equals(uri.getScheme())) { + ExifInterface exif = new ExifInterface(uri.getPath()); + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + try (InputStream in = context.getContentResolver().openInputStream(uri)) { + ExifInterface exif = new ExifInterface(in); + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + } + } + if ("file".equals(uri.getScheme())) { + bitmap = BitmapFactory.decodeFile(uri.getPath(), opts); + } else { + try (InputStream in = context.getContentResolver().openInputStream(uri)) { + bitmap = BitmapFactory.decodeStream(in, null, opts); + } + } + if (factor % 1f != 0f) { + Rect srcBounds = null; + Rect dstBounds; + dstBounds = new Rect(0, 0, targetWidth, targetHeight); + Bitmap scaled = Bitmap.createBitmap(dstBounds.width(), dstBounds.height(), Bitmap.Config.ARGB_8888); + new Canvas(scaled).drawBitmap(bitmap, srcBounds, dstBounds, new Paint(Paint.FILTER_BITMAP_FLAG)); + bitmap = scaled; + } + + + int rotation = 0; + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + rotation = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + rotation = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + rotation = 270; + break; + } + if (rotation != 0) { + Matrix matrix = new Matrix(); + matrix.setRotate(rotation); + bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); + } + } + + boolean isPNG = "image/png".equals(contentType); + File tempFile = new File(fullpatch); + try (FileOutputStream out = new FileOutputStream(tempFile)) { + if (isPNG) { + bitmap.compress(Bitmap.CompressFormat.PNG, 0, out); + } else { + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + } + } + + } + + + private static int[] getTargetSize(int srcWidth, int srcHeight) { + int maxSize = 1; + if (MainActivity.instanceInfo != null && MainActivity.instanceInfo.configuration != null && MainActivity.instanceInfo.configuration.media_attachments != null) { + maxSize = MainActivity.instanceInfo.configuration.media_attachments.image_size_limit; + } + int targetWidth = Math.round((float) Math.sqrt((float) maxSize * ((float) srcWidth / srcHeight))); + int targetHeight = Math.round((float) Math.sqrt((float) maxSize * ((float) srcHeight / srcWidth))); + return new int[]{targetWidth, targetHeight}; + } + + private static boolean needResize(int srcWidth, int srcHeight) { + int maxSize; + if (MainActivity.instanceInfo != null && MainActivity.instanceInfo.configuration != null && MainActivity.instanceInfo.configuration.media_attachments != null) { + maxSize = MainActivity.instanceInfo.configuration.media_attachments.image_size_limit; + } else { + return false; + } + return srcWidth * srcHeight > maxSize; + } + + + public static MediaType getFileMediaType(File file) { + String name = file.getName(); + return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.') + 1))); + } } diff --git a/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java b/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java index 81e65086..ecdee197 100644 --- a/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java +++ b/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java @@ -10,10 +10,12 @@ import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.animation.AnticipateOvershootInterpolator; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.constraintlayout.widget.ConstraintSet; @@ -25,8 +27,12 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.transition.ChangeBounds; import androidx.transition.TransitionManager; +import com.canhub.cropper.CropImage; +import com.canhub.cropper.CropImageContract; +import com.canhub.cropper.CropImageContractOptions; +import com.canhub.cropper.CropImageOptions; +import com.canhub.cropper.CropImageView; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import com.theartofdev.edmodo.cropper.CropImage; import java.io.File; import java.io.IOException; @@ -73,6 +79,8 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList private Uri uri; private boolean exit; private ActivityEditImageBinding binding; + CropImageContractOptions cropImageContractOptions; + ActivityResultLauncher cropImageContractOptionsActivityResultLauncher; private static int exifToDegrees(int exifOrientation) { if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { @@ -146,6 +154,35 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList } } + cropImageContractOptions = new CropImageContractOptions(uri, new CropImageOptions()) + .setGuidelines(CropImageView.Guidelines.ON) + .setCropShape(CropImageView.CropShape.RECTANGLE) + .setAllowRotation(true) + .setAllowFlipping(true) + .setOutputCompressFormat(Bitmap.CompressFormat.PNG) + .setAllowCounterRotation(true) + .setImageSource(true, false) + .setScaleType(CropImageView.ScaleType.CENTER); + cropImageContractOptionsActivityResultLauncher = registerForActivityResult( + new CropImageContract(), + result -> { + if (result.isSuccessful()) { + Uri resultUri = result.getUriContent(); + if (resultUri != null) { + binding.photoEditorView.getSource().setImageURI(resultUri); + if (uri != null && uri.getPath() != null) { + File fdelete = new File(uri.getPath()); + if (fdelete.exists()) { + //noinspection ResultOfMethodCallIgnored + fdelete.delete(); + } + } + uri = resultUri; + } + } else { + Log.e(Helper.TAG, "onActivityResult...Error CropImage: " + result.getError()); + } + }); mPhotoEditor.setFilterEffect(PhotoFilter.NONE); binding.send.setOnClickListener(v -> { exit = true; @@ -286,6 +323,7 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList } intentImage.putExtra("focusX", focusX); intentImage.putExtra("focusY", focusY); + } LocalBroadcastManager.getInstance(EditImageActivity.this).sendBroadcast(intentImage); @@ -351,22 +389,37 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList } break; case CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE: - - CropImage.ActivityResult result = CropImage.getActivityResult(data); - if (result != null) { - Uri resultUri = result.getUri(); - if (resultUri != null) { - binding.photoEditorView.getSource().setImageURI(resultUri); - binding.photoEditorView.getSource().setRotation(rotationInDegrees); - if (uri != null && uri.getPath() != null) { - File fdelete = new File(uri.getPath()); - if (fdelete.exists()) { - //noinspection ResultOfMethodCallIgnored - fdelete.delete(); - } - } - uri = resultUri; - } + if (data != null && data.getData() != null) { + CropImageContractOptions cropImageContractOptions = new CropImageContractOptions(data.getData(), new CropImageOptions()) + .setGuidelines(CropImageView.Guidelines.ON) + .setCropShape(CropImageView.CropShape.RECTANGLE) + .setAllowRotation(true) + .setAllowFlipping(true) + .setOutputCompressFormat(Bitmap.CompressFormat.PNG) + .setAllowCounterRotation(true) + .setImageSource(true, false) + .setScaleType(CropImageView.ScaleType.CENTER); + ActivityResultLauncher cropImageContractOptionsActivityResultLauncher = registerForActivityResult( + new CropImageContract(), + result -> { + if (result.isSuccessful()) { + Uri resultUri = result.getUriContent(); + if (resultUri != null) { + binding.photoEditorView.getSource().setImageURI(resultUri); + if (uri != null && uri.getPath() != null) { + File fdelete = new File(uri.getPath()); + if (fdelete.exists()) { + //noinspection ResultOfMethodCallIgnored + fdelete.delete(); + } + } + uri = resultUri; + } + } else { + Log.e(Helper.TAG, "onActivityResult...Error CropImage: " + result.getError()); + } + }); + cropImageContractOptionsActivityResultLauncher.launch(cropImageContractOptions); } break; } @@ -454,8 +507,8 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList mPropertiesBSFragment.show(getSupportFragmentManager(), mPropertiesBSFragment.getTag()); break; case CROP: - CropImage.activity(uri) - .start(this); + + cropImageContractOptionsActivityResultLauncher.launch(cropImageContractOptions); break; case FOCUS: binding.focusCircle.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/app/fedilab/android/interfaces/ProgressListener.java b/app/src/main/java/app/fedilab/android/interfaces/ProgressListener.java new file mode 100644 index 00000000..4d9bcd96 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/interfaces/ProgressListener.java @@ -0,0 +1,5 @@ +package app.fedilab.android.interfaces; + +public interface ProgressListener { + void onProgress(long transferred, long total); +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java b/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java index f241d7e8..de565b87 100644 --- a/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java +++ b/app/src/main/java/app/fedilab/android/jobs/ComposeWorker.java @@ -341,10 +341,14 @@ public class ComposeWorker extends Worker { private static String postAttachment(MastodonStatusesService mastodonStatusesService, DataPost dataPost, MultipartBody.Part fileMultipartBody, Attachment attachment) { RequestBody descriptionBody = null; + RequestBody focusBody = null; if (attachment.description != null && attachment.description.trim().length() > 0) { descriptionBody = RequestBody.create(MediaType.parse("text/plain"), attachment.description); } - Call attachmentCall = mastodonStatusesService.postMedia(dataPost.token, fileMultipartBody, null, descriptionBody, attachment.focus); + if (attachment.focus != null && attachment.focus.trim().length() > 0) { + focusBody = RequestBody.create(MediaType.parse("text/plain"), attachment.focus); + } + Call attachmentCall = mastodonStatusesService.postMedia(dataPost.token, fileMultipartBody, null, descriptionBody, focusBody); if (attachmentCall != null) { try { diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java index be870a9f..82628377 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/StatusesVM.java @@ -127,7 +127,11 @@ public class StatusesVM extends AndroidViewModel { if (description != null && description.trim().length() > 0) { descriptionBody = RequestBody.create(MediaType.parse("text/plain"), description); } - Call attachmentCall = mastodonStatusesService.postMedia(token, fileMultipartBody, thumbnailMultipartBody, descriptionBody, focus); + RequestBody focusBody = null; + if (focus != null && focus.trim().length() > 0) { + focusBody = RequestBody.create(MediaType.parse("text/plain"), focus); + } + Call attachmentCall = mastodonStatusesService.postMedia(token, fileMultipartBody, thumbnailMultipartBody, descriptionBody, focusBody); Attachment attachment = null; if (attachmentCall != null) { try { diff --git a/cropper/build.gradle b/cropper/build.gradle deleted file mode 100644 index 3486b192..00000000 --- a/cropper/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -apply plugin: 'com.android.library' - -android { - - compileSdk 31 - defaultConfig { - minSdkVersion 14 - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - lintOptions { - abortOnError false - } -} - -dependencies { - api 'androidx.appcompat:appcompat:1.4.2' - implementation "androidx.exifinterface:exifinterface:1.3.3" -} - diff --git a/cropper/src/main/AndroidManifest.xml b/cropper/src/main/AndroidManifest.xml deleted file mode 100644 index fe8baa84..00000000 --- a/cropper/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java deleted file mode 100644 index 54d33df4..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java +++ /dev/null @@ -1,354 +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.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 -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java deleted file mode 100644 index a6ecf939..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java +++ /dev/null @@ -1,176 +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.theartofdev.edmodo.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/theartofdev/edmodo/cropper/BitmapUtils.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java deleted file mode 100644 index 7190bd42..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java +++ /dev/null @@ -1,923 +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.theartofdev.edmodo.cropper; - -import android.content.ContentResolver; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.graphics.RectF; -import android.net.Uri; -import android.util.Log; -import android.util.Pair; - -import androidx.exifinterface.media.ExifInterface; - -import java.io.Closeable; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.ref.WeakReference; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; - -/** - * Utility class that deals with operations with an ImageView. - */ -final class BitmapUtils { - - static final Rect EMPTY_RECT = new Rect(); - - static final RectF EMPTY_RECT_F = new RectF(); - - /** - * Reusable rectangle for general internal usage - */ - static final RectF RECT = new RectF(); - - /** - * Reusable point for general internal usage - */ - static final float[] POINTS = new float[6]; - - /** - * Reusable point for general internal usage - */ - static final float[] POINTS2 = new float[6]; - /** - * used to save bitmaps during state save and restore so not to reload them. - */ - static Pair> mStateBitmap; - /** - * Used to know the max texture size allowed to be rendered - */ - private static int mMaxTextureSize; - - /** - * Rotate the given image by reading the Exif value of the image (uri).
- * If no rotation is required the image will not be rotated.
- * New bitmap is created and the old one is recycled. - */ - static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, Context context, Uri uri) { - ExifInterface ei = null; - try { - InputStream is = context.getContentResolver().openInputStream(uri); - if (is != null) { - ei = new ExifInterface(is); - is.close(); - } - } catch (Exception ignored) { - } - return ei != null ? rotateBitmapByExif(bitmap, ei) : new RotateBitmapResult(bitmap, 0); - } - - /** - * Rotate the given image by given Exif value.
- * If no rotation is required the image will not be rotated.
- * New bitmap is created and the old one is recycled. - */ - static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, ExifInterface exif) { - int degrees; - int orientation = - exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - switch (orientation) { - case ExifInterface.ORIENTATION_ROTATE_90: - degrees = 90; - break; - case ExifInterface.ORIENTATION_ROTATE_180: - degrees = 180; - break; - case ExifInterface.ORIENTATION_ROTATE_270: - degrees = 270; - break; - default: - degrees = 0; - break; - } - return new RotateBitmapResult(bitmap, degrees); - } - - /** - * Decode bitmap from stream using sampling to get bitmap with the requested limit. - */ - static BitmapSampled decodeSampledBitmap(Context context, Uri uri, int reqWidth, int reqHeight) { - - try { - ContentResolver resolver = context.getContentResolver(); - - // First decode with inJustDecodeBounds=true to check dimensions - BitmapFactory.Options options = decodeImageForOption(resolver, uri); - - if (options.outWidth == -1 && options.outHeight == -1) - throw new RuntimeException("File is not a picture"); - - // Calculate inSampleSize - options.inSampleSize = - Math.max( - calculateInSampleSizeByReqestedSize( - options.outWidth, options.outHeight, reqWidth, reqHeight), - calculateInSampleSizeByMaxTextureSize(options.outWidth, options.outHeight)); - - // Decode bitmap with inSampleSize set - Bitmap bitmap = decodeImage(resolver, uri, options); - - return new BitmapSampled(bitmap, options.inSampleSize); - - } catch (Exception e) { - throw new RuntimeException( - "Failed to load sampled bitmap: " + uri + "\r\n" + e.getMessage(), e); - } - } - - /** - * Crop image bitmap from given bitmap using the given points in the original bitmap and the given - * rotation.
- * if the rotation is not 0,90,180 or 270 degrees then we must first crop a larger area of the - * image that contains the requires rectangle, rotate and then crop again a sub rectangle.
- * If crop fails due to OOM we scale the cropping image by 0.5 every time it fails until it is - * small enough. - */ - static BitmapSampled cropBitmapObjectHandleOOM( - Bitmap bitmap, - float[] points, - int degreesRotated, - boolean fixAspectRatio, - int aspectRatioX, - int aspectRatioY, - boolean flipHorizontally, - boolean flipVertically) { - int scale = 1; - while (true) { - try { - Bitmap cropBitmap = - cropBitmapObjectWithScale( - bitmap, - points, - degreesRotated, - fixAspectRatio, - aspectRatioX, - aspectRatioY, - 1 / (float) scale, - flipHorizontally, - flipVertically); - return new BitmapSampled(cropBitmap, scale); - } catch (OutOfMemoryError e) { - scale *= 2; - if (scale > 8) { - throw e; - } - } - } - } - - /** - * Crop image bitmap from given bitmap using the given points in the original bitmap and the given - * rotation.
- * if the rotation is not 0,90,180 or 270 degrees then we must first crop a larger area of the - * image that contains the requires rectangle, rotate and then crop again a sub rectangle. - * - * @param scale how much to scale the cropped image part, use 0.5 to lower the image by half (OOM - * handling) - */ - private static Bitmap cropBitmapObjectWithScale( - Bitmap bitmap, - float[] points, - int degreesRotated, - boolean fixAspectRatio, - int aspectRatioX, - int aspectRatioY, - float scale, - boolean flipHorizontally, - boolean flipVertically) { - - // get the rectangle in original image that contains the required cropped area (larger for non - // rectangular crop) - Rect rect = - getRectFromPoints( - points, - bitmap.getWidth(), - bitmap.getHeight(), - fixAspectRatio, - aspectRatioX, - aspectRatioY); - - // crop and rotate the cropped image in one operation - Matrix matrix = new Matrix(); - matrix.setRotate(degreesRotated, bitmap.getWidth() / 2, bitmap.getHeight() / 2); - matrix.postScale(flipHorizontally ? -scale : scale, flipVertically ? -scale : scale); - Bitmap result = - Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height(), matrix, true); - - if (result == bitmap) { - // corner case when all bitmap is selected, no worth optimizing for it - result = bitmap.copy(bitmap.getConfig(), false); - } - - // rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping - if (degreesRotated % 90 != 0) { - - // extra crop because non rectangular crop cannot be done directly on the image without - // rotating first - result = - cropForRotatedImage( - result, points, rect, degreesRotated, fixAspectRatio, aspectRatioX, aspectRatioY); - } - - return result; - } - - /** - * Crop image bitmap from URI by decoding it with specific width and height to down-sample if - * required.
- * Additionally if OOM is thrown try to increase the sampling (2,4,8). - */ - static BitmapSampled cropBitmap( - Context context, - Uri loadedImageUri, - float[] points, - int degreesRotated, - int orgWidth, - int orgHeight, - boolean fixAspectRatio, - int aspectRatioX, - int aspectRatioY, - int reqWidth, - int reqHeight, - boolean flipHorizontally, - boolean flipVertically) { - int sampleMulti = 1; - while (true) { - try { - // if successful, just return the resulting bitmap - return cropBitmap( - context, - loadedImageUri, - points, - degreesRotated, - orgWidth, - orgHeight, - fixAspectRatio, - aspectRatioX, - aspectRatioY, - reqWidth, - reqHeight, - flipHorizontally, - flipVertically, - sampleMulti); - } catch (OutOfMemoryError e) { - // if OOM try to increase the sampling to lower the memory usage - sampleMulti *= 2; - if (sampleMulti > 16) { - throw new RuntimeException( - "Failed to handle OOM by sampling (" - + sampleMulti - + "): " - + loadedImageUri - + "\r\n" - + e.getMessage(), - e); - } - } - } - } - - /** - * Get left value of the bounding rectangle of the given points. - */ - static float getRectLeft(float[] points) { - return Math.min(Math.min(Math.min(points[0], points[2]), points[4]), points[6]); - } - - /** - * Get top value of the bounding rectangle of the given points. - */ - static float getRectTop(float[] points) { - return Math.min(Math.min(Math.min(points[1], points[3]), points[5]), points[7]); - } - - /** - * Get right value of the bounding rectangle of the given points. - */ - static float getRectRight(float[] points) { - return Math.max(Math.max(Math.max(points[0], points[2]), points[4]), points[6]); - } - - /** - * Get bottom value of the bounding rectangle of the given points. - */ - static float getRectBottom(float[] points) { - return Math.max(Math.max(Math.max(points[1], points[3]), points[5]), points[7]); - } - - /** - * Get width of the bounding rectangle of the given points. - */ - static float getRectWidth(float[] points) { - return getRectRight(points) - getRectLeft(points); - } - - /** - * Get height of the bounding rectangle of the given points. - */ - static float getRectHeight(float[] points) { - return getRectBottom(points) - getRectTop(points); - } - - /** - * Get horizontal center value of the bounding rectangle of the given points. - */ - static float getRectCenterX(float[] points) { - return (getRectRight(points) + getRectLeft(points)) / 2f; - } - - /** - * Get vertical center value of the bounding rectangle of the given points. - */ - static float getRectCenterY(float[] points) { - return (getRectBottom(points) + getRectTop(points)) / 2f; - } - - /** - * Get a rectangle for the given 4 points (x0,y0,x1,y1,x2,y2,x3,y3) by finding the min/max 2 - * points that contains the given 4 points and is a straight rectangle. - */ - static Rect getRectFromPoints( - float[] points, - int imageWidth, - int imageHeight, - boolean fixAspectRatio, - int aspectRatioX, - int aspectRatioY) { - int left = Math.round(Math.max(0, getRectLeft(points))); - int top = Math.round(Math.max(0, getRectTop(points))); - int right = Math.round(Math.min(imageWidth, getRectRight(points))); - int bottom = Math.round(Math.min(imageHeight, getRectBottom(points))); - - Rect rect = new Rect(left, top, right, bottom); - if (fixAspectRatio) { - fixRectForAspectRatio(rect, aspectRatioX, aspectRatioY); - } - - return rect; - } - - /** - * Fix the given rectangle if it doesn't confirm to aspect ration rule.
- * Make sure that width and height are equal if 1:1 fixed aspect ratio is requested. - */ - private static void fixRectForAspectRatio(Rect rect, int aspectRatioX, int aspectRatioY) { - if (aspectRatioX == aspectRatioY && rect.width() != rect.height()) { - if (rect.height() > rect.width()) { - rect.bottom -= rect.height() - rect.width(); - } else { - rect.right -= rect.width() - rect.height(); - } - } - } - - /** - * Write given bitmap to a temp file. If file already exists no-op as we already saved the file in - * this session. Uses JPEG 95% compression. - * - * @param uri the uri to write the bitmap to, if null - * @return the uri where the image was saved in, either the given uri or new pointing to temp - * file. - */ - static Uri writeTempStateStoreBitmap(Context context, Bitmap bitmap, Uri uri) { - try { - boolean needSave = true; - if (uri == null) { - uri = - Uri.fromFile( - File.createTempFile("aic_state_store_temp", ".jpg", context.getCacheDir())); - } else if (new File(uri.getPath()).exists()) { - needSave = false; - } - if (needSave) { - writeBitmapToUri(context, bitmap, uri, Bitmap.CompressFormat.JPEG, 95); - } - return uri; - } catch (Exception e) { - Log.w("AIC", "Failed to write bitmap to temp file for image-cropper save instance state", e); - return null; - } - } - - /** - * Write the given bitmap to the given uri using the given compression. - */ - static void writeBitmapToUri( - Context context, - Bitmap bitmap, - Uri uri, - Bitmap.CompressFormat compressFormat, - int compressQuality) - throws FileNotFoundException { - OutputStream outputStream = null; - try { - outputStream = context.getContentResolver().openOutputStream(uri); - bitmap.compress(compressFormat, compressQuality, outputStream); - } finally { - closeSafe(outputStream); - } - } - - /** - * Resize the given bitmap to the given width/height by the given option.
- */ - static Bitmap resizeBitmap( - Bitmap bitmap, int reqWidth, int reqHeight, CropImageView.RequestSizeOptions options) { - try { - if (reqWidth > 0 - && reqHeight > 0 - && (options == CropImageView.RequestSizeOptions.RESIZE_FIT - || options == CropImageView.RequestSizeOptions.RESIZE_INSIDE - || options == CropImageView.RequestSizeOptions.RESIZE_EXACT)) { - - Bitmap resized = null; - if (options == CropImageView.RequestSizeOptions.RESIZE_EXACT) { - resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false); - } else { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - float scale = Math.max(width / (float) reqWidth, height / (float) reqHeight); - if (scale > 1 || options == CropImageView.RequestSizeOptions.RESIZE_FIT) { - resized = - Bitmap.createScaledBitmap( - bitmap, (int) (width / scale), (int) (height / scale), false); - } - } - if (resized != null) { - if (resized != bitmap) { - bitmap.recycle(); - } - return resized; - } - } - } catch (Exception e) { - Log.w("AIC", "Failed to resize cropped image, return bitmap before resize", e); - } - return bitmap; - } - - // region: Private methods - - /** - * Crop image bitmap from URI by decoding it with specific width and height to down-sample if - * required. - * - * @param orgWidth used to get rectangle from points (handle edge cases to limit rectangle) - * @param orgHeight used to get rectangle from points (handle edge cases to limit rectangle) - * @param sampleMulti used to increase the sampling of the image to handle memory issues. - */ - private static BitmapSampled cropBitmap( - Context context, - Uri loadedImageUri, - float[] points, - int degreesRotated, - int orgWidth, - int orgHeight, - boolean fixAspectRatio, - int aspectRatioX, - int aspectRatioY, - int reqWidth, - int reqHeight, - boolean flipHorizontally, - boolean flipVertically, - int sampleMulti) { - - // get the rectangle in original image that contains the required cropped area (larger for non - // rectangular crop) - Rect rect = - getRectFromPoints(points, orgWidth, orgHeight, fixAspectRatio, aspectRatioX, aspectRatioY); - - int width = reqWidth > 0 ? reqWidth : rect.width(); - int height = reqHeight > 0 ? reqHeight : rect.height(); - - Bitmap result = null; - int sampleSize = 1; - try { - // decode only the required image from URI, optionally sub-sampling if reqWidth/reqHeight is - // given. - BitmapSampled bitmapSampled = - decodeSampledBitmapRegion(context, loadedImageUri, rect, width, height, sampleMulti); - result = bitmapSampled.bitmap; - sampleSize = bitmapSampled.sampleSize; - } catch (Exception ignored) { - } - - if (result != null) { - try { - // rotate the decoded region by the required amount - result = rotateAndFlipBitmapInt(result, degreesRotated, flipHorizontally, flipVertically); - - // rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping - if (degreesRotated % 90 != 0) { - - // extra crop because non rectangular crop cannot be done directly on the image without - // rotating first - result = - cropForRotatedImage( - result, points, rect, degreesRotated, fixAspectRatio, aspectRatioX, aspectRatioY); - } - } catch (OutOfMemoryError e) { - if (result != null) { - result.recycle(); - } - throw e; - } - return new BitmapSampled(result, sampleSize); - } else { - // failed to decode region, may be skia issue, try full decode and then crop - return cropBitmap( - context, - loadedImageUri, - points, - degreesRotated, - fixAspectRatio, - aspectRatioX, - aspectRatioY, - sampleMulti, - rect, - width, - height, - flipHorizontally, - flipVertically); - } - } - - /** - * Crop bitmap by fully loading the original and then cropping it, fallback in case cropping - * region failed. - */ - private static BitmapSampled cropBitmap( - Context context, - Uri loadedImageUri, - float[] points, - int degreesRotated, - boolean fixAspectRatio, - int aspectRatioX, - int aspectRatioY, - int sampleMulti, - Rect rect, - int width, - int height, - boolean flipHorizontally, - boolean flipVertically) { - Bitmap result = null; - int sampleSize; - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = - sampleSize = - sampleMulti - * calculateInSampleSizeByReqestedSize(rect.width(), rect.height(), width, height); - - Bitmap fullBitmap = decodeImage(context.getContentResolver(), loadedImageUri, options); - if (fullBitmap != null) { - try { - // adjust crop points by the sampling because the image is smaller - float[] points2 = new float[points.length]; - System.arraycopy(points, 0, points2, 0, points.length); - for (int i = 0; i < points2.length; i++) { - points2[i] = points2[i] / options.inSampleSize; - } - - result = - cropBitmapObjectWithScale( - fullBitmap, - points2, - degreesRotated, - fixAspectRatio, - aspectRatioX, - aspectRatioY, - 1, - flipHorizontally, - flipVertically); - } finally { - if (result != fullBitmap) { - fullBitmap.recycle(); - } - } - } - } catch (OutOfMemoryError e) { - if (result != null) { - result.recycle(); - } - throw e; - } catch (Exception e) { - throw new RuntimeException( - "Failed to load sampled bitmap: " + loadedImageUri + "\r\n" + e.getMessage(), e); - } - return new BitmapSampled(result, sampleSize); - } - - /** - * Decode image from uri using "inJustDecodeBounds" to get the image dimensions. - */ - private static BitmapFactory.Options decodeImageForOption(ContentResolver resolver, Uri uri) - throws FileNotFoundException { - InputStream stream = null; - try { - stream = resolver.openInputStream(uri); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(stream, EMPTY_RECT, options); - options.inJustDecodeBounds = false; - return options; - } finally { - closeSafe(stream); - } - } - - /** - * Decode image from uri using given "inSampleSize", but if failed due to out-of-memory then raise - * the inSampleSize until success. - */ - private static Bitmap decodeImage( - ContentResolver resolver, Uri uri, BitmapFactory.Options options) - throws FileNotFoundException { - do { - InputStream stream = null; - try { - stream = resolver.openInputStream(uri); - return BitmapFactory.decodeStream(stream, EMPTY_RECT, options); - } catch (OutOfMemoryError e) { - options.inSampleSize *= 2; - } finally { - closeSafe(stream); - } - } while (options.inSampleSize <= 512); - throw new RuntimeException("Failed to decode image: " + uri); - } - - /** - * Decode specific rectangle bitmap from stream using sampling to get bitmap with the requested - * limit. - * - * @param sampleMulti used to increase the sampling of the image to handle memory issues. - */ - private static BitmapSampled decodeSampledBitmapRegion( - Context context, Uri uri, Rect rect, int reqWidth, int reqHeight, int sampleMulti) { - InputStream stream = null; - BitmapRegionDecoder decoder = null; - try { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inSampleSize = - sampleMulti - * calculateInSampleSizeByReqestedSize( - rect.width(), rect.height(), reqWidth, reqHeight); - - stream = context.getContentResolver().openInputStream(uri); - decoder = BitmapRegionDecoder.newInstance(stream, false); - do { - try { - return new BitmapSampled(decoder.decodeRegion(rect, options), options.inSampleSize); - } catch (OutOfMemoryError e) { - options.inSampleSize *= 2; - } - } while (options.inSampleSize <= 512); - } catch (Exception e) { - throw new RuntimeException( - "Failed to load sampled bitmap: " + uri + "\r\n" + e.getMessage(), e); - } finally { - closeSafe(stream); - if (decoder != null) { - decoder.recycle(); - } - } - return new BitmapSampled(null, 1); - } - - /** - * Special crop of bitmap rotated by not stright angle, in this case the original crop bitmap - * contains parts beyond the required crop area, this method crops the already cropped and rotated - * bitmap to the final rectangle.
- * Note: rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping. - */ - private static Bitmap cropForRotatedImage( - Bitmap bitmap, - float[] points, - Rect rect, - int degreesRotated, - boolean fixAspectRatio, - int aspectRatioX, - int aspectRatioY) { - if (degreesRotated % 90 != 0) { - - int adjLeft = 0, adjTop = 0, width = 0, height = 0; - double rads = Math.toRadians(degreesRotated); - int compareTo = - degreesRotated < 90 || (degreesRotated > 180 && degreesRotated < 270) - ? rect.left - : rect.right; - for (int i = 0; i < points.length; i += 2) { - if (points[i] >= compareTo - 1 && points[i] <= compareTo + 1) { - adjLeft = (int) Math.abs(Math.sin(rads) * (rect.bottom - points[i + 1])); - adjTop = (int) Math.abs(Math.cos(rads) * (points[i + 1] - rect.top)); - width = (int) Math.abs((points[i + 1] - rect.top) / Math.sin(rads)); - height = (int) Math.abs((rect.bottom - points[i + 1]) / Math.cos(rads)); - break; - } - } - - rect.set(adjLeft, adjTop, adjLeft + width, adjTop + height); - if (fixAspectRatio) { - fixRectForAspectRatio(rect, aspectRatioX, aspectRatioY); - } - - Bitmap bitmapTmp = bitmap; - bitmap = Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height()); - if (bitmapTmp != bitmap) { - bitmapTmp.recycle(); - } - } - return bitmap; - } - - /** - * Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width - * larger than the requested height and width. - */ - private static int calculateInSampleSizeByReqestedSize( - int width, int height, int reqWidth, int reqHeight) { - int inSampleSize = 1; - if (height > reqHeight || width > reqWidth) { - while ((height / 2 / inSampleSize) > reqHeight && (width / 2 / inSampleSize) > reqWidth) { - inSampleSize *= 2; - } - } - return inSampleSize; - } - - /** - * Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width - * smaller than max texture size allowed for the device. - */ - private static int calculateInSampleSizeByMaxTextureSize(int width, int height) { - int inSampleSize = 1; - if (mMaxTextureSize == 0) { - mMaxTextureSize = getMaxTextureSize(); - } - if (mMaxTextureSize > 0) { - while ((height / inSampleSize) > mMaxTextureSize - || (width / inSampleSize) > mMaxTextureSize) { - inSampleSize *= 2; - } - } - return inSampleSize; - } - - /** - * Rotate the given bitmap by the given degrees.
- * New bitmap is created and the old one is recycled. - */ - private static Bitmap rotateAndFlipBitmapInt( - Bitmap bitmap, int degrees, boolean flipHorizontally, boolean flipVertically) { - if (degrees > 0 || flipHorizontally || flipVertically) { - Matrix matrix = new Matrix(); - matrix.setRotate(degrees); - matrix.postScale(flipHorizontally ? -1 : 1, flipVertically ? -1 : 1); - Bitmap newBitmap = - Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); - if (newBitmap != bitmap) { - bitmap.recycle(); - } - return newBitmap; - } else { - return bitmap; - } - } - - /** - * Get the max size of bitmap allowed to be rendered on the device.
- * http://stackoverflow.com/questions/7428996/hw-accelerated-activity-how-to-get-opengl-texture-size-limit. - */ - private static int getMaxTextureSize() { - // Safe minimum default size - final int IMAGE_MAX_BITMAP_DIMENSION = 2048; - - try { - // Get EGL Display - EGL10 egl = (EGL10) EGLContext.getEGL(); - EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - // Initialise - int[] version = new int[2]; - egl.eglInitialize(display, version); - - // Query total number of configurations - int[] totalConfigurations = new int[1]; - egl.eglGetConfigs(display, null, 0, totalConfigurations); - - // Query actual list configurations - EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]]; - egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations); - - int[] textureSize = new int[1]; - int maximumTextureSize = 0; - - // Iterate through all the configurations to located the maximum texture size - for (int i = 0; i < totalConfigurations[0]; i++) { - // Only need to check for width since opengl textures are always squared - egl.eglGetConfigAttrib( - display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize); - - // Keep track of the maximum texture size - if (maximumTextureSize < textureSize[0]) { - maximumTextureSize = textureSize[0]; - } - } - - // Release - egl.eglTerminate(display); - - // Return largest texture size found, or default - return Math.max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION); - } catch (Exception e) { - return IMAGE_MAX_BITMAP_DIMENSION; - } - } - - /** - * Close the given closeable object (Stream) in a safe way: check if it is null and catch-log - * exception thrown. - * - * @param closeable the closable object to close - */ - private static void closeSafe(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException ignored) { - } - } - } - // endregion - - // region: Inner class: BitmapSampled - - /** - * Holds bitmap instance and the sample size that the bitmap was loaded/cropped with. - */ - static final class BitmapSampled { - - /** - * The bitmap instance - */ - public final Bitmap bitmap; - - /** - * The sample size used to lower the size of the bitmap (1,2,4,8,...) - */ - final int sampleSize; - - BitmapSampled(Bitmap bitmap, int sampleSize) { - this.bitmap = bitmap; - this.sampleSize = sampleSize; - } - } - // endregion - - // region: Inner class: RotateBitmapResult - - /** - * The result of {@link #rotateBitmapByExif(android.graphics.Bitmap, ExifInterface)}. - */ - static final class RotateBitmapResult { - - /** - * The loaded bitmap - */ - public final Bitmap bitmap; - - /** - * The degrees the image was rotated - */ - final int degrees; - - RotateBitmapResult(Bitmap bitmap, int degrees) { - this.bitmap = bitmap; - this.degrees = degrees; - } - } - // endregion -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java deleted file mode 100644 index 07d36ea0..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java +++ /dev/null @@ -1,1048 +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.theartofdev.edmodo.cropper; - -import android.Manifest; -import android.app.Activity; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.provider.MediaStore; - -import androidx.annotation.DrawableRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.fragment.app.Fragment; - -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * Helper to simplify crop image work like starting pick-image acitvity and handling camera/gallery - * intents.
- * The goal of the helper is to simplify the starting and most-common usage of image cropping and - * not all porpose all possible scenario one-to-rule-them-all code base. So feel free to use it as - * is and as a wiki to make your own.
- * Added value you get out-of-the-box is some edge case handling that you may miss otherwise, like - * the stupid-ass Android camera result URI that may differ from version to version and from device - * to device. - */ -@SuppressWarnings("WeakerAccess, unused") -public final class CropImage { - - // region: Fields and Consts - - /** - * The key used to pass crop image source URI to {@link CropImageActivity}. - */ - public static final String CROP_IMAGE_EXTRA_SOURCE = "CROP_IMAGE_EXTRA_SOURCE"; - - /** - * The key used to pass crop image options to {@link CropImageActivity}. - */ - public static final String CROP_IMAGE_EXTRA_OPTIONS = "CROP_IMAGE_EXTRA_OPTIONS"; - - /** - * The key used to pass crop image bundle data to {@link CropImageActivity}. - */ - public static final String CROP_IMAGE_EXTRA_BUNDLE = "CROP_IMAGE_EXTRA_BUNDLE"; - - /** - * The key used to pass crop image result data back from {@link CropImageActivity}. - */ - public static final String CROP_IMAGE_EXTRA_RESULT = "CROP_IMAGE_EXTRA_RESULT"; - - /** - * The request code used to start pick image activity to be used on result to identify the this - * specific request. - */ - public static final int PICK_IMAGE_CHOOSER_REQUEST_CODE = 200; - - /** - * The request code used to request permission to pick image from external storage. - */ - public static final int PICK_IMAGE_PERMISSIONS_REQUEST_CODE = 201; - - /** - * The request code used to request permission to capture image from camera. - */ - public static final int CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE = 2011; - - /** - * The request code used to start {@link CropImageActivity} to be used on result to identify the - * this specific request. - */ - public static final int CROP_IMAGE_ACTIVITY_REQUEST_CODE = 203; - - /** - * The result code used to return error from {@link CropImageActivity}. - */ - public static final int CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE = 204; - // endregion - - private CropImage() { - } - - /** - * Create a new bitmap that has all pixels beyond the oval shape transparent. Old bitmap is - * recycled. - */ - public static Bitmap toOvalBitmap(@NonNull Bitmap bitmap) { - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - - Canvas canvas = new Canvas(output); - - int color = 0xff424242; - Paint paint = new Paint(); - - paint.setAntiAlias(true); - canvas.drawARGB(0, 0, 0, 0); - paint.setColor(color); - - RectF rect = new RectF(0, 0, width, height); - canvas.drawOval(rect, paint); - paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); - canvas.drawBitmap(bitmap, 0, 0, paint); - - bitmap.recycle(); - - return output; - } - - /** - * Start an activity to get image for cropping using chooser intent that will have all the - * available applications for the device like camera (MyCamera), galery (Photos), store apps - * (Dropbox), etc.
- * Use "pick_image_intent_chooser_title" string resource to override pick chooser title. - * - * @param activity the activity to be used to start activity from - */ - public static void startPickImageActivity(@NonNull Activity activity) { - activity.startActivityForResult( - getPickImageChooserIntent(activity), PICK_IMAGE_CHOOSER_REQUEST_CODE); - } - - /** - * Same as {@link #startPickImageActivity(Activity) startPickImageActivity} method but instead of - * being called and returning to an Activity, this method can be called and return to a Fragment. - * - * @param context The Fragments context. Use getContext() - * @param fragment The calling Fragment to start and return the image to - */ - public static void startPickImageActivity(@NonNull Context context, @NonNull Fragment fragment) { - fragment.startActivityForResult( - getPickImageChooserIntent(context), PICK_IMAGE_CHOOSER_REQUEST_CODE); - } - - /** - * Create a chooser intent to select the source to get image from.
- * The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).
- * All possible sources are added to the intent chooser.
- * Use "pick_image_intent_chooser_title" string resource to override chooser title. - * - * @param context used to access Android APIs, like content resolve, it is your - * activity/fragment/widget. - */ - public static Intent getPickImageChooserIntent(@NonNull Context context) { - return getPickImageChooserIntent( - context, context.getString(R.string.pick_image_intent_chooser_title), false, true); - } - - /** - * Create a chooser intent to select the source to get image from.
- * The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).
- * All possible sources are added to the intent chooser. - * - * @param context used to access Android APIs, like content resolve, it is your - * activity/fragment/widget. - * @param title the title to use for the chooser UI - * @param includeDocuments if to include KitKat documents activity containing all sources - * @param includeCamera if to include camera intents - */ - public static Intent getPickImageChooserIntent( - @NonNull Context context, - CharSequence title, - boolean includeDocuments, - boolean includeCamera) { - - List allIntents = new ArrayList<>(); - PackageManager packageManager = context.getPackageManager(); - - // collect all camera intents if Camera permission is available - if (!isExplicitCameraPermissionRequired(context) && includeCamera) { - allIntents.addAll(getCameraIntents(context, packageManager)); - } - - List galleryIntents = - getGalleryIntents(packageManager, Intent.ACTION_GET_CONTENT, includeDocuments); - if (galleryIntents.size() == 0) { - // if no intents found for get-content try pick intent action (Huawei P9). - galleryIntents = getGalleryIntents(packageManager, Intent.ACTION_PICK, includeDocuments); - } - allIntents.addAll(galleryIntents); - - Intent target; - if (allIntents.isEmpty()) { - target = new Intent(); - } else { - target = allIntents.get(allIntents.size() - 1); - allIntents.remove(allIntents.size() - 1); - } - - // Create a chooser from the main intent - Intent chooserIntent = Intent.createChooser(target, title); - - // Add all other intents - chooserIntent.putExtra( - Intent.EXTRA_INITIAL_INTENTS, allIntents.toArray(new Parcelable[allIntents.size()])); - - return chooserIntent; - } - - /** - * Get the main Camera intent for capturing image using device camera app. If the outputFileUri is - * null, a default Uri will be created with {@link #getCaptureImageOutputUri(Context)}, so then - * you will be able to get the pictureUri using {@link #getPickImageResultUri(Context, Intent)}. - * Otherwise, it is just you use the Uri passed to this method. - * - * @param context used to access Android APIs, like content resolve, it is your - * activity/fragment/widget. - * @param outputFileUri the Uri where the picture will be placed. - */ - public static Intent getCameraIntent(@NonNull Context context, Uri outputFileUri) { - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - if (outputFileUri == null) { - outputFileUri = getCaptureImageOutputUri(context); - } - intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri); - return intent; - } - - /** - * Get all Camera intents for capturing image using device camera apps. - */ - public static List getCameraIntents( - @NonNull Context context, @NonNull PackageManager packageManager) { - - List allIntents = new ArrayList<>(); - - // Determine Uri of camera image to save. - Uri outputFileUri = getCaptureImageOutputUri(context); - - Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - List listCam = packageManager.queryIntentActivities(captureIntent, 0); - for (ResolveInfo res : listCam) { - Intent intent = new Intent(captureIntent); - intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name)); - intent.setPackage(res.activityInfo.packageName); - if (outputFileUri != null) { - intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri); - } - allIntents.add(intent); - } - - return allIntents; - } - - /** - * Get all Gallery intents for getting image from one of the apps of the device that handle - * images. - */ - public static List getGalleryIntents( - @NonNull PackageManager packageManager, String action, boolean includeDocuments) { - List intents = new ArrayList<>(); - Intent galleryIntent = - action == Intent.ACTION_GET_CONTENT - ? new Intent(action) - : new Intent(action, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); - galleryIntent.setType("image/*"); - List listGallery = packageManager.queryIntentActivities(galleryIntent, 0); - for (ResolveInfo res : listGallery) { - Intent intent = new Intent(galleryIntent); - intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name)); - intent.setPackage(res.activityInfo.packageName); - intents.add(intent); - } - - // remove documents intent - if (!includeDocuments) { - for (Intent intent : intents) { - if (intent - .getComponent() - .getClassName() - .equals("com.android.documentsui.DocumentsActivity")) { - intents.remove(intent); - break; - } - } - } - return intents; - } - - /** - * Check if explicetly requesting camera permission is required.
- * It is required in Android Marshmellow and above if "CAMERA" permission is requested in the - * manifest.
- * See StackOverflow - * question. - */ - public static boolean isExplicitCameraPermissionRequired(@NonNull Context context) { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && hasPermissionInManifest(context, "android.permission.CAMERA") - && context.checkSelfPermission(Manifest.permission.CAMERA) - != PackageManager.PERMISSION_GRANTED; - } - - /** - * Check if the app requests a specific permission in the manifest. - * - * @param permissionName the permission to check - * @return true - the permission in requested in manifest, false - not. - */ - public static boolean hasPermissionInManifest( - @NonNull Context context, @NonNull String permissionName) { - String packageName = context.getPackageName(); - try { - PackageInfo packageInfo = - context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); - final String[] declaredPermisisons = packageInfo.requestedPermissions; - if (declaredPermisisons != null && declaredPermisisons.length > 0) { - for (String p : declaredPermisisons) { - if (p.equalsIgnoreCase(permissionName)) { - return true; - } - } - } - } catch (PackageManager.NameNotFoundException e) { - } - return false; - } - - /** - * Get URI to image received from capture by camera. - * - * @param context used to access Android APIs, like content resolve, it is your - * activity/fragment/widget. - */ - public static Uri getCaptureImageOutputUri(@NonNull Context context) { - Uri outputFileUri = null; - File getImage = context.getExternalCacheDir(); - if (getImage != null) { - outputFileUri = Uri.fromFile(new File(getImage.getPath(), "pickImageResult.jpeg")); - } - return outputFileUri; - } - - /** - * Get the URI of the selected image from {@link #getPickImageChooserIntent(Context)}.
- * Will return the correct URI for camera and gallery image. - * - * @param context used to access Android APIs, like content resolve, it is your - * activity/fragment/widget. - * @param data the returned data of the activity result - */ - public static Uri getPickImageResultUri(@NonNull Context context, @Nullable Intent data) { - boolean isCamera = true; - if (data != null && data.getData() != null) { - String action = data.getAction(); - isCamera = action != null && action.equals(MediaStore.ACTION_IMAGE_CAPTURE); - } - return isCamera || data.getData() == null ? getCaptureImageOutputUri(context) : data.getData(); - } - - /** - * Check if the given picked image URI requires READ_EXTERNAL_STORAGE permissions.
- * Only relevant for API version 23 and above and not required for all URI's depends on the - * implementation of the app that was used for picking the image. So we just test if we can open - * the stream or do we get an exception when we try, Android is awesome. - * - * @param context used to access Android APIs, like content resolve, it is your - * activity/fragment/widget. - * @param uri the result URI of image pick. - * @return true - required permission are not granted, false - either no need for permissions or - * they are granted - */ - public static boolean isReadExternalStoragePermissionsRequired( - @NonNull Context context, @NonNull Uri uri) { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED - && isUriRequiresPermissions(context, uri); - } - - /** - * Test if we can open the given Android URI to test if permission required error is thrown.
- * Only relevant for API version 23 and above. - * - * @param context used to access Android APIs, like content resolve, it is your - * activity/fragment/widget. - * @param uri the result URI of image pick. - */ - public static boolean isUriRequiresPermissions(@NonNull Context context, @NonNull Uri uri) { - try { - ContentResolver resolver = context.getContentResolver(); - InputStream stream = resolver.openInputStream(uri); - if (stream != null) { - stream.close(); - } - return false; - } catch (Exception e) { - return true; - } - } - - /** - * Create {@link ActivityBuilder} instance to open image picker for cropping and then start {@link - * CropImageActivity} to crop the selected image.
- * Result will be received in {@link Activity#onActivityResult(int, int, Intent)} and can be - * retrieved using {@link #getActivityResult(Intent)}. - * - * @return builder for Crop Image Activity - */ - public static ActivityBuilder activity() { - return new ActivityBuilder(null); - } - - /** - * Create {@link ActivityBuilder} instance to start {@link CropImageActivity} to crop the given - * image.
- * Result will be received in {@link Activity#onActivityResult(int, int, Intent)} and can be - * retrieved using {@link #getActivityResult(Intent)}. - * - * @param uri the image Android uri source to crop or null to start a picker - * @return builder for Crop Image Activity - */ - public static ActivityBuilder activity(@Nullable Uri uri) { - return new ActivityBuilder(uri); - } - - /** - * Get {@link CropImageActivity} result data object for crop image activity started using {@link - * #activity(Uri)}. - * - * @param data result data intent as received in {@link Activity#onActivityResult(int, int, - * Intent)}. - * @return Crop Image Activity Result object or null if none exists - */ - public static ActivityResult getActivityResult(@Nullable Intent data) { - return data != null ? (ActivityResult) data.getParcelableExtra(CROP_IMAGE_EXTRA_RESULT) : null; - } - - // region: Inner class: ActivityBuilder - - /** - * Builder used for creating Image Crop Activity by user request. - */ - public static final class ActivityBuilder { - - /** - * The image to crop source Android uri. - */ - @Nullable - private final Uri mSource; - - /** - * Options for image crop UX - */ - private final CropImageOptions mOptions; - - private ActivityBuilder(@Nullable Uri source) { - mSource = source; - mOptions = new CropImageOptions(); - } - - /** - * Get {@link CropImageActivity} intent to start the activity. - */ - public Intent getIntent(@NonNull Context context) { - return getIntent(context, CropImageActivity.class); - } - - /** - * Get {@link CropImageActivity} intent to start the activity. - */ - public Intent getIntent(@NonNull Context context, @Nullable Class cls) { - mOptions.validate(); - - Intent intent = new Intent(); - intent.setClass(context, cls); - Bundle bundle = new Bundle(); - bundle.putParcelable(CROP_IMAGE_EXTRA_SOURCE, mSource); - bundle.putParcelable(CROP_IMAGE_EXTRA_OPTIONS, mOptions); - intent.putExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE, bundle); - return intent; - } - - /** - * Start {@link CropImageActivity}. - * - * @param activity activity to receive result - */ - public void start(@NonNull Activity activity) { - mOptions.validate(); - activity.startActivityForResult(getIntent(activity), CROP_IMAGE_ACTIVITY_REQUEST_CODE); - } - - /** - * Start {@link CropImageActivity}. - * - * @param activity activity to receive result - */ - public void start(@NonNull Activity activity, @Nullable Class cls) { - mOptions.validate(); - activity.startActivityForResult(getIntent(activity, cls), CROP_IMAGE_ACTIVITY_REQUEST_CODE); - } - - /** - * Start {@link CropImageActivity}. - * - * @param fragment fragment to receive result - */ - public void start(@NonNull Context context, @NonNull Fragment fragment) { - fragment.startActivityForResult(getIntent(context), CROP_IMAGE_ACTIVITY_REQUEST_CODE); - } - - /** - * Start {@link CropImageActivity}. - * - * @param fragment fragment to receive result - */ - @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) - public void start(@NonNull Context context, @NonNull android.app.Fragment fragment) { - fragment.startActivityForResult(getIntent(context), CROP_IMAGE_ACTIVITY_REQUEST_CODE); - } - - /** - * Start {@link CropImageActivity}. - * - * @param fragment fragment to receive result - */ - public void start( - @NonNull Context context, @NonNull Fragment fragment, @Nullable Class cls) { - fragment.startActivityForResult(getIntent(context, cls), CROP_IMAGE_ACTIVITY_REQUEST_CODE); - } - - /** - * Start {@link CropImageActivity}. - * - * @param fragment fragment to receive result - */ - @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) - public void start( - @NonNull Context context, @NonNull android.app.Fragment fragment, @Nullable Class cls) { - fragment.startActivityForResult(getIntent(context, cls), CROP_IMAGE_ACTIVITY_REQUEST_CODE); - } - - /** - * The shape of the cropping window.
- * To set square/circle crop shape set aspect ratio to 1:1.
- * Default: RECTANGLE - */ - public ActivityBuilder setCropShape(@NonNull CropImageView.CropShape cropShape) { - mOptions.cropShape = cropShape; - return this; - } - - /** - * An edge of the crop window will snap to the corresponding edge of a specified bounding box - * when the crop window edge is less than or equal to this distance (in pixels) away from the - * bounding box edge (in pixels).
- * Default: 3dp - */ - public ActivityBuilder setSnapRadius(float snapRadius) { - mOptions.snapRadius = snapRadius; - return this; - } - - /** - * The radius of the touchable area around the handle (in pixels).
- * We are basing this value off of the recommended 48dp Rhythm.
- * See: http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm
- * Default: 48dp - */ - public ActivityBuilder setTouchRadius(float touchRadius) { - mOptions.touchRadius = touchRadius; - return this; - } - - /** - * whether the guidelines should be on, off, or only showing when resizing.
- * Default: ON_TOUCH - */ - public ActivityBuilder setGuidelines(@NonNull CropImageView.Guidelines guidelines) { - mOptions.guidelines = guidelines; - return this; - } - - /** - * The initial scale type of the image in the crop image view
- * Default: FIT_CENTER - */ - public ActivityBuilder setScaleType(@NonNull CropImageView.ScaleType scaleType) { - mOptions.scaleType = scaleType; - return this; - } - - /** - * if to show crop overlay UI what contains the crop window UI surrounded by background over the - * cropping image.
- * default: true, may disable for animation or frame transition. - */ - public ActivityBuilder setShowCropOverlay(boolean showCropOverlay) { - mOptions.showCropOverlay = showCropOverlay; - return this; - } - - /** - * if auto-zoom functionality is enabled.
- * default: true. - */ - public ActivityBuilder setAutoZoomEnabled(boolean autoZoomEnabled) { - mOptions.autoZoomEnabled = autoZoomEnabled; - return this; - } - - /** - * if multi touch functionality is enabled.
- * default: true. - */ - public ActivityBuilder setMultiTouchEnabled(boolean multiTouchEnabled) { - mOptions.multiTouchEnabled = multiTouchEnabled; - return this; - } - - /** - * The max zoom allowed during cropping.
- * Default: 4 - */ - public ActivityBuilder setMaxZoom(int maxZoom) { - mOptions.maxZoom = maxZoom; - return this; - } - - /** - * The initial crop window padding from image borders in percentage of the cropping image - * dimensions.
- * Default: 0.1 - */ - public ActivityBuilder setInitialCropWindowPaddingRatio(float initialCropWindowPaddingRatio) { - mOptions.initialCropWindowPaddingRatio = initialCropWindowPaddingRatio; - return this; - } - - /** - * whether the width to height aspect ratio should be maintained or free to change.
- * Default: false - */ - public ActivityBuilder setFixAspectRatio(boolean fixAspectRatio) { - mOptions.fixAspectRatio = fixAspectRatio; - return this; - } - - /** - * the X,Y value of the aspect ratio.
- * Also sets fixes aspect ratio to TRUE.
- * Default: 1/1 - * - * @param aspectRatioX the width - * @param aspectRatioY the height - */ - public ActivityBuilder setAspectRatio(int aspectRatioX, int aspectRatioY) { - mOptions.aspectRatioX = aspectRatioX; - mOptions.aspectRatioY = aspectRatioY; - mOptions.fixAspectRatio = true; - return this; - } - - /** - * the thickness of the guidelines lines (in pixels).
- * Default: 3dp - */ - public ActivityBuilder setBorderLineThickness(float borderLineThickness) { - mOptions.borderLineThickness = borderLineThickness; - return this; - } - - /** - * the color of the guidelines lines.
- * Default: Color.argb(170, 255, 255, 255) - */ - public ActivityBuilder setBorderLineColor(int borderLineColor) { - mOptions.borderLineColor = borderLineColor; - return this; - } - - /** - * thickness of the corner line (in pixels).
- * Default: 2dp - */ - public ActivityBuilder setBorderCornerThickness(float borderCornerThickness) { - mOptions.borderCornerThickness = borderCornerThickness; - return this; - } - - /** - * the offset of corner line from crop window border (in pixels).
- * Default: 5dp - */ - public ActivityBuilder setBorderCornerOffset(float borderCornerOffset) { - mOptions.borderCornerOffset = borderCornerOffset; - return this; - } - - /** - * the length of the corner line away from the corner (in pixels).
- * Default: 14dp - */ - public ActivityBuilder setBorderCornerLength(float borderCornerLength) { - mOptions.borderCornerLength = borderCornerLength; - return this; - } - - /** - * the color of the corner line.
- * Default: WHITE - */ - public ActivityBuilder setBorderCornerColor(int borderCornerColor) { - mOptions.borderCornerColor = borderCornerColor; - return this; - } - - /** - * the thickness of the guidelines lines (in pixels).
- * Default: 1dp - */ - public ActivityBuilder setGuidelinesThickness(float guidelinesThickness) { - mOptions.guidelinesThickness = guidelinesThickness; - return this; - } - - /** - * the color of the guidelines lines.
- * Default: Color.argb(170, 255, 255, 255) - */ - public ActivityBuilder setGuidelinesColor(int guidelinesColor) { - mOptions.guidelinesColor = guidelinesColor; - return this; - } - - /** - * the color of the overlay background around the crop window cover the image parts not in the - * crop window.
- * Default: Color.argb(119, 0, 0, 0) - */ - public ActivityBuilder setBackgroundColor(int backgroundColor) { - mOptions.backgroundColor = backgroundColor; - return this; - } - - /** - * the min size the crop window is allowed to be (in pixels).
- * Default: 42dp, 42dp - */ - public ActivityBuilder setMinCropWindowSize(int minCropWindowWidth, int minCropWindowHeight) { - mOptions.minCropWindowWidth = minCropWindowWidth; - mOptions.minCropWindowHeight = minCropWindowHeight; - return this; - } - - /** - * the min size the resulting cropping image is allowed to be, affects the cropping window - * limits (in pixels).
- * Default: 40px, 40px - */ - public ActivityBuilder setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) { - mOptions.minCropResultWidth = minCropResultWidth; - mOptions.minCropResultHeight = minCropResultHeight; - return this; - } - - /** - * the max size the resulting cropping image is allowed to be, affects the cropping window - * limits (in pixels).
- * Default: 99999, 99999 - */ - public ActivityBuilder setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) { - mOptions.maxCropResultWidth = maxCropResultWidth; - mOptions.maxCropResultHeight = maxCropResultHeight; - return this; - } - - /** - * the title of the {@link CropImageActivity}.
- * Default: "" - */ - public ActivityBuilder setActivityTitle(CharSequence activityTitle) { - mOptions.activityTitle = activityTitle; - return this; - } - - /** - * the color to use for action bar items icons.
- * Default: NONE - */ - public ActivityBuilder setActivityMenuIconColor(int activityMenuIconColor) { - mOptions.activityMenuIconColor = activityMenuIconColor; - return this; - } - - /** - * the Android Uri to save the cropped image to.
- * Default: NONE, will create a temp file - */ - public ActivityBuilder setOutputUri(Uri outputUri) { - mOptions.outputUri = outputUri; - return this; - } - - /** - * the compression format to use when writting the image.
- * Default: JPEG - */ - public ActivityBuilder setOutputCompressFormat(Bitmap.CompressFormat outputCompressFormat) { - mOptions.outputCompressFormat = outputCompressFormat; - return this; - } - - /** - * the quility (if applicable) to use when writting the image (0 - 100).
- * Default: 90 - */ - public ActivityBuilder setOutputCompressQuality(int outputCompressQuality) { - mOptions.outputCompressQuality = outputCompressQuality; - return this; - } - - /** - * the size to resize the cropped image to.
- * Uses {@link CropImageView.RequestSizeOptions#RESIZE_INSIDE} option.
- * Default: 0, 0 - not set, will not resize - */ - public ActivityBuilder setRequestedSize(int reqWidth, int reqHeight) { - return setRequestedSize(reqWidth, reqHeight, CropImageView.RequestSizeOptions.RESIZE_INSIDE); - } - - /** - * the size to resize the cropped image to.
- * Default: 0, 0 - not set, will not resize - */ - public ActivityBuilder setRequestedSize( - int reqWidth, int reqHeight, CropImageView.RequestSizeOptions options) { - mOptions.outputRequestWidth = reqWidth; - mOptions.outputRequestHeight = reqHeight; - mOptions.outputRequestSizeOptions = options; - return this; - } - - /** - * if the result of crop image activity should not save the cropped image bitmap.
- * Used if you want to crop the image manually and need only the crop rectangle and rotation - * data.
- * Default: false - */ - public ActivityBuilder setNoOutputImage(boolean noOutputImage) { - mOptions.noOutputImage = noOutputImage; - return this; - } - - /** - * the initial rectangle to set on the cropping image after loading.
- * Default: NONE - will initialize using initial crop window padding ratio - */ - public ActivityBuilder setInitialCropWindowRectangle(Rect initialCropWindowRectangle) { - mOptions.initialCropWindowRectangle = initialCropWindowRectangle; - return this; - } - - /** - * the initial rotation to set on the cropping image after loading (0-360 degrees clockwise). - *
- * Default: NONE - will read image exif data - */ - public ActivityBuilder setInitialRotation(int initialRotation) { - mOptions.initialRotation = (initialRotation + 360) % 360; - return this; - } - - /** - * if to allow rotation during cropping.
- * Default: true - */ - public ActivityBuilder setAllowRotation(boolean allowRotation) { - mOptions.allowRotation = allowRotation; - return this; - } - - /** - * if to allow flipping during cropping.
- * Default: true - */ - public ActivityBuilder setAllowFlipping(boolean allowFlipping) { - mOptions.allowFlipping = allowFlipping; - return this; - } - - /** - * if to allow counter-clockwise rotation during cropping.
- * Note: if rotation is disabled this option has no effect.
- * Default: false - */ - public ActivityBuilder setAllowCounterRotation(boolean allowCounterRotation) { - mOptions.allowCounterRotation = allowCounterRotation; - return this; - } - - /** - * The amount of degreees to rotate clockwise or counter-clockwise (0-360).
- * Default: 90 - */ - public ActivityBuilder setRotationDegrees(int rotationDegrees) { - mOptions.rotationDegrees = (rotationDegrees + 360) % 360; - return this; - } - - /** - * whether the image should be flipped horizontally.
- * Default: false - */ - public ActivityBuilder setFlipHorizontally(boolean flipHorizontally) { - mOptions.flipHorizontally = flipHorizontally; - return this; - } - - /** - * whether the image should be flipped vertically.
- * Default: false - */ - public ActivityBuilder setFlipVertically(boolean flipVertically) { - mOptions.flipVertically = flipVertically; - return this; - } - - /** - * optional, set crop menu crop button title.
- * Default: null, will use resource string: crop_image_menu_crop - */ - public ActivityBuilder setCropMenuCropButtonTitle(CharSequence title) { - mOptions.cropMenuCropButtonTitle = title; - return this; - } - - /** - * Image resource id to use for crop icon instead of text.
- * Default: 0 - */ - public ActivityBuilder setCropMenuCropButtonIcon(@DrawableRes int drawableResource) { - mOptions.cropMenuCropButtonIcon = drawableResource; - return this; - } - } - // endregion - - // region: Inner class: ActivityResult - - /** - * Result data of Crop Image Activity. - */ - public static final class ActivityResult extends CropImageView.CropResult implements Parcelable { - - public static final Creator CREATOR = - new Creator() { - @Override - public ActivityResult createFromParcel(Parcel in) { - return new ActivityResult(in); - } - - @Override - public ActivityResult[] newArray(int size) { - return new ActivityResult[size]; - } - }; - - public ActivityResult( - Uri originalUri, - Uri uri, - Exception error, - float[] cropPoints, - Rect cropRect, - int rotation, - Rect wholeImageRect, - int sampleSize) { - super( - null, - originalUri, - null, - uri, - error, - cropPoints, - cropRect, - wholeImageRect, - rotation, - sampleSize); - } - - protected ActivityResult(Parcel in) { - super( - null, - (Uri) in.readParcelable(Uri.class.getClassLoader()), - null, - (Uri) in.readParcelable(Uri.class.getClassLoader()), - (Exception) in.readSerializable(), - in.createFloatArray(), - (Rect) in.readParcelable(Rect.class.getClassLoader()), - (Rect) in.readParcelable(Rect.class.getClassLoader()), - in.readInt(), - in.readInt()); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeParcelable(getOriginalUri(), flags); - dest.writeParcelable(getUri(), flags); - dest.writeSerializable(getError()); - dest.writeFloatArray(getCropPoints()); - dest.writeParcelable(getCropRect(), flags); - dest.writeParcelable(getWholeImageRect(), flags); - dest.writeInt(getRotation()); - dest.writeInt(getSampleSize()); - } - - @Override - public int describeContents() { - return 0; - } - } - // endregion -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java deleted file mode 100644 index bd9d0afe..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java +++ /dev/null @@ -1,367 +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.theartofdev.edmodo.cropper; - -import android.Manifest; -import android.annotation.SuppressLint; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Bitmap; -import android.graphics.PorterDuff; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; - -import java.io.File; -import java.io.IOException; - -/** - * Built-in activity for image cropping.
- * Use {@link CropImage#activity(Uri)} to create a builder to start this activity. - */ -public class CropImageActivity extends AppCompatActivity - implements CropImageView.OnSetImageUriCompleteListener, - CropImageView.OnCropImageCompleteListener { - - /** - * The crop image view library widget used in the activity - */ - private CropImageView mCropImageView; - - /** - * Persist URI image to crop URI if specific permissions are required - */ - private Uri mCropImageUri; - - /** - * the options that were set for the crop image - */ - private CropImageOptions mOptions; - - @Override - @SuppressLint("NewApi") - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.crop_image_activity); - - mCropImageView = findViewById(R.id.cropImageView); - - Bundle bundle = getIntent().getBundleExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE); - mCropImageUri = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_SOURCE); - mOptions = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS); - - if (savedInstanceState == null) { - if (mCropImageUri == null || mCropImageUri.equals(Uri.EMPTY)) { - if (CropImage.isExplicitCameraPermissionRequired(this)) { - // request permissions and handle the result in onRequestPermissionsResult() - requestPermissions( - new String[]{Manifest.permission.CAMERA}, - CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE); - } else { - CropImage.startPickImageActivity(this); - } - } else if (CropImage.isReadExternalStoragePermissionsRequired(this, mCropImageUri)) { - // request permissions and handle the result in onRequestPermissionsResult() - requestPermissions( - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE); - } else { - // no permissions required or already grunted, can start crop image activity - mCropImageView.setImageUriAsync(mCropImageUri); - } - } - - ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - CharSequence title = mOptions != null && - mOptions.activityTitle != null && mOptions.activityTitle.length() > 0 - ? mOptions.activityTitle - : getResources().getString(R.string.crop_image_activity_title); - actionBar.setTitle(title); - actionBar.setDisplayHomeAsUpEnabled(true); - } - } - - @Override - protected void onStart() { - super.onStart(); - mCropImageView.setOnSetImageUriCompleteListener(this); - mCropImageView.setOnCropImageCompleteListener(this); - } - - @Override - protected void onStop() { - super.onStop(); - mCropImageView.setOnSetImageUriCompleteListener(null); - mCropImageView.setOnCropImageCompleteListener(null); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.crop_image_menu, menu); - - if (!mOptions.allowRotation) { - menu.removeItem(R.id.crop_image_menu_rotate_left); - menu.removeItem(R.id.crop_image_menu_rotate_right); - } else if (mOptions.allowCounterRotation) { - menu.findItem(R.id.crop_image_menu_rotate_left).setVisible(true); - } - - if (!mOptions.allowFlipping) { - menu.removeItem(R.id.crop_image_menu_flip); - } - - if (mOptions.cropMenuCropButtonTitle != null) { - menu.findItem(R.id.crop_image_menu_crop).setTitle(mOptions.cropMenuCropButtonTitle); - } - - Drawable cropIcon = null; - try { - if (mOptions.cropMenuCropButtonIcon != 0) { - cropIcon = ContextCompat.getDrawable(this, mOptions.cropMenuCropButtonIcon); - menu.findItem(R.id.crop_image_menu_crop).setIcon(cropIcon); - } - } catch (Exception e) { - e.printStackTrace(); - } - - if (mOptions.activityMenuIconColor != 0) { - updateMenuItemIconColor( - menu, R.id.crop_image_menu_rotate_left, mOptions.activityMenuIconColor); - updateMenuItemIconColor( - menu, R.id.crop_image_menu_rotate_right, mOptions.activityMenuIconColor); - updateMenuItemIconColor(menu, R.id.crop_image_menu_flip, mOptions.activityMenuIconColor); - if (cropIcon != null) { - updateMenuItemIconColor(menu, R.id.crop_image_menu_crop, mOptions.activityMenuIconColor); - } - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.crop_image_menu_crop) { - cropImage(); - return true; - } - if (item.getItemId() == R.id.crop_image_menu_rotate_left) { - rotateImage(-mOptions.rotationDegrees); - return true; - } - if (item.getItemId() == R.id.crop_image_menu_rotate_right) { - rotateImage(mOptions.rotationDegrees); - return true; - } - if (item.getItemId() == R.id.crop_image_menu_flip_horizontally) { - mCropImageView.flipImageHorizontally(); - return true; - } - if (item.getItemId() == R.id.crop_image_menu_flip_vertically) { - mCropImageView.flipImageVertically(); - return true; - } - if (item.getItemId() == android.R.id.home) { - setResultCancel(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - setResultCancel(); - } - - @Override - @SuppressLint("NewApi") - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - - // handle result of pick image chooser - if (requestCode == CropImage.PICK_IMAGE_CHOOSER_REQUEST_CODE) { - if (resultCode == Activity.RESULT_CANCELED) { - // User cancelled the picker. We don't have anything to crop - setResultCancel(); - } - - if (resultCode == Activity.RESULT_OK) { - mCropImageUri = CropImage.getPickImageResultUri(this, data); - - // For API >= 23 we need to check specifically that we have permissions to read external - // storage. - if (CropImage.isReadExternalStoragePermissionsRequired(this, mCropImageUri)) { - // request permissions and handle the result in onRequestPermissionsResult() - requestPermissions( - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, - CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE); - } else { - // no permissions required or already grunted, can start crop image activity - mCropImageView.setImageUriAsync(mCropImageUri); - } - } - } - } - - @Override - public void onRequestPermissionsResult( - int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { - if (requestCode == CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE) { - if (mCropImageUri != null - && grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // required permissions granted, start crop image activity - mCropImageView.setImageUriAsync(mCropImageUri); - } else { - Toast.makeText(this, R.string.crop_image_activity_no_permissions, Toast.LENGTH_LONG).show(); - setResultCancel(); - } - } - - if (requestCode == CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE) { - // Irrespective of whether camera permission was given or not, we show the picker - // The picker will not add the camera intent if permission is not available - CropImage.startPickImageActivity(this); - } - } - - @Override - public void onSetImageUriComplete(CropImageView view, Uri uri, Exception error) { - if (error == null) { - if (mOptions.initialCropWindowRectangle != null) { - mCropImageView.setCropRect(mOptions.initialCropWindowRectangle); - } - if (mOptions.initialRotation > -1) { - mCropImageView.setRotatedDegrees(mOptions.initialRotation); - } - } else { - setResult(null, error, 1); - } - } - - @Override - public void onCropImageComplete(CropImageView view, CropImageView.CropResult result) { - setResult(result.getUri(), result.getError(), result.getSampleSize()); - } - - // region: Private methods - - /** - * Execute crop image and save the result tou output uri. - */ - protected void cropImage() { - if (mOptions.noOutputImage) { - setResult(null, null, 1); - } else { - Uri outputUri = getOutputUri(); - mCropImageView.saveCroppedImageAsync( - outputUri, - mOptions.outputCompressFormat, - mOptions.outputCompressQuality, - mOptions.outputRequestWidth, - mOptions.outputRequestHeight, - mOptions.outputRequestSizeOptions); - } - } - - /** - * Rotate the image in the crop image view. - */ - protected void rotateImage(int degrees) { - mCropImageView.rotateImage(degrees); - } - - /** - * Get Android uri to save the cropped image into.
- * Use the given in options or create a temp file. - */ - protected Uri getOutputUri() { - Uri outputUri = mOptions.outputUri; - if (outputUri == null || outputUri.equals(Uri.EMPTY)) { - try { - String ext = - mOptions.outputCompressFormat == Bitmap.CompressFormat.JPEG - ? ".jpg" - : mOptions.outputCompressFormat == Bitmap.CompressFormat.PNG ? ".png" : ".webp"; - outputUri = Uri.fromFile(File.createTempFile("cropped", ext, getCacheDir())); - } catch (IOException e) { - throw new RuntimeException("Failed to create temp file for output image", e); - } - } - return outputUri; - } - - /** - * Result with cropped image data or error if failed. - */ - protected void setResult(Uri uri, Exception error, int sampleSize) { - int resultCode = error == null ? RESULT_OK : CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE; - setResult(resultCode, getResultIntent(uri, error, sampleSize)); - finish(); - } - - /** - * Cancel of cropping activity. - */ - protected void setResultCancel() { - setResult(RESULT_CANCELED); - finish(); - } - - /** - * Get intent instance to be used for the result of this activity. - */ - protected Intent getResultIntent(Uri uri, Exception error, int sampleSize) { - CropImage.ActivityResult result = - new CropImage.ActivityResult( - mCropImageView.getImageUri(), - uri, - error, - mCropImageView.getCropPoints(), - mCropImageView.getCropRect(), - mCropImageView.getRotatedDegrees(), - mCropImageView.getWholeImageRect(), - sampleSize); - Intent intent = new Intent(); - intent.putExtras(getIntent()); - intent.putExtra(CropImage.CROP_IMAGE_EXTRA_RESULT, result); - return intent; - } - - /** - * Update the color of a specific menu item to the given color. - */ - private void updateMenuItemIconColor(Menu menu, int itemId, int color) { - MenuItem menuItem = menu.findItem(itemId); - if (menuItem != null) { - Drawable menuItemIcon = menuItem.getIcon(); - if (menuItemIcon != null) { - try { - menuItemIcon.mutate(); - menuItemIcon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); - menuItem.setIcon(menuItemIcon); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - } -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java deleted file mode 100644 index 2861b857..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java +++ /dev/null @@ -1,123 +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.theartofdev.edmodo.cropper; - -import android.graphics.Matrix; -import android.graphics.RectF; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.Animation; -import android.view.animation.Transformation; -import android.widget.ImageView; - -/** - * Animation to handle smooth cropping image matrix transformation change, specifically for - * zoom-in/out. - */ -final class CropImageAnimation extends Animation implements Animation.AnimationListener { - - // region: Fields and Consts - - private final ImageView mImageView; - - private final CropOverlayView mCropOverlayView; - - private final float[] mStartBoundPoints = new float[8]; - - private final float[] mEndBoundPoints = new float[8]; - - private final RectF mStartCropWindowRect = new RectF(); - - private final RectF mEndCropWindowRect = new RectF(); - - private final float[] mStartImageMatrix = new float[9]; - - private final float[] mEndImageMatrix = new float[9]; - - private final RectF mAnimRect = new RectF(); - - private final float[] mAnimPoints = new float[8]; - - private final float[] mAnimMatrix = new float[9]; - // endregion - - public CropImageAnimation(ImageView cropImageView, CropOverlayView cropOverlayView) { - mImageView = cropImageView; - mCropOverlayView = cropOverlayView; - - setDuration(300); - setFillAfter(true); - setInterpolator(new AccelerateDecelerateInterpolator()); - setAnimationListener(this); - } - - public void setStartState(float[] boundPoints, Matrix imageMatrix) { - reset(); - System.arraycopy(boundPoints, 0, mStartBoundPoints, 0, 8); - mStartCropWindowRect.set(mCropOverlayView.getCropWindowRect()); - imageMatrix.getValues(mStartImageMatrix); - } - - public void setEndState(float[] boundPoints, Matrix imageMatrix) { - System.arraycopy(boundPoints, 0, mEndBoundPoints, 0, 8); - mEndCropWindowRect.set(mCropOverlayView.getCropWindowRect()); - imageMatrix.getValues(mEndImageMatrix); - } - - @Override - protected void applyTransformation(float interpolatedTime, Transformation t) { - - mAnimRect.left = - mStartCropWindowRect.left - + (mEndCropWindowRect.left - mStartCropWindowRect.left) * interpolatedTime; - mAnimRect.top = - mStartCropWindowRect.top - + (mEndCropWindowRect.top - mStartCropWindowRect.top) * interpolatedTime; - mAnimRect.right = - mStartCropWindowRect.right - + (mEndCropWindowRect.right - mStartCropWindowRect.right) * interpolatedTime; - mAnimRect.bottom = - mStartCropWindowRect.bottom - + (mEndCropWindowRect.bottom - mStartCropWindowRect.bottom) * interpolatedTime; - mCropOverlayView.setCropWindowRect(mAnimRect); - - for (int i = 0; i < mAnimPoints.length; i++) { - mAnimPoints[i] = - mStartBoundPoints[i] + (mEndBoundPoints[i] - mStartBoundPoints[i]) * interpolatedTime; - } - mCropOverlayView.setBounds(mAnimPoints, mImageView.getWidth(), mImageView.getHeight()); - - for (int i = 0; i < mAnimMatrix.length; i++) { - mAnimMatrix[i] = - mStartImageMatrix[i] + (mEndImageMatrix[i] - mStartImageMatrix[i]) * interpolatedTime; - } - Matrix m = mImageView.getImageMatrix(); - m.setValues(mAnimMatrix); - mImageView.setImageMatrix(m); - - mImageView.invalidate(); - mCropOverlayView.invalidate(); - } - - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mImageView.clearAnimation(); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java deleted file mode 100644 index 5a9256a8..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java +++ /dev/null @@ -1,541 +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.theartofdev.edmodo.cropper; - -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Rect; -import android.net.Uri; -import android.os.Parcel; -import android.os.Parcelable; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -/** - * All the possible options that can be set to customize crop image.
- * Initialized with default values. - */ -public class CropImageOptions implements Parcelable { - - public static final Creator CREATOR = - new Creator() { - @Override - public CropImageOptions createFromParcel(Parcel in) { - return new CropImageOptions(in); - } - - @Override - public CropImageOptions[] newArray(int size) { - return new CropImageOptions[size]; - } - }; - - /** - * The shape of the cropping window. - */ - public CropImageView.CropShape cropShape; - - /** - * An edge of the crop window will snap to the corresponding edge of a specified bounding box when - * the crop window edge is less than or equal to this distance (in pixels) away from the bounding - * box edge. (in pixels) - */ - public float snapRadius; - - /** - * The radius of the touchable area around the handle. (in pixels)
- * We are basing this value off of the recommended 48dp Rhythm.
- * See: http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm - */ - public float touchRadius; - - /** - * whether the guidelines should be on, off, or only showing when resizing. - */ - public CropImageView.Guidelines guidelines; - - /** - * The initial scale type of the image in the crop image view - */ - public CropImageView.ScaleType scaleType; - - /** - * if to show crop overlay UI what contains the crop window UI surrounded by background over the - * cropping image.
- * default: true, may disable for animation or frame transition. - */ - public boolean showCropOverlay; - - /** - * if to show progress bar when image async loading/cropping is in progress.
- * default: true, disable to provide custom progress bar UI. - */ - public boolean showProgressBar; - - /** - * if auto-zoom functionality is enabled.
- * default: true. - */ - public boolean autoZoomEnabled; - - /** - * if multi-touch should be enabled on the crop box default: false - */ - public boolean multiTouchEnabled; - - /** - * The max zoom allowed during cropping. - */ - public int maxZoom; - - /** - * The initial crop window padding from image borders in percentage of the cropping image - * dimensions. - */ - public float initialCropWindowPaddingRatio; - - /** - * whether the width to height aspect ratio should be maintained or free to change. - */ - public boolean fixAspectRatio; - - /** - * the X value of the aspect ratio. - */ - public int aspectRatioX; - - /** - * the Y value of the aspect ratio. - */ - public int aspectRatioY; - - /** - * the thickness of the guidelines lines in pixels. (in pixels) - */ - public float borderLineThickness; - - /** - * the color of the guidelines lines - */ - public int borderLineColor; - - /** - * thickness of the corner line. (in pixels) - */ - public float borderCornerThickness; - - /** - * the offset of corner line from crop window border. (in pixels) - */ - public float borderCornerOffset; - - /** - * the length of the corner line away from the corner. (in pixels) - */ - public float borderCornerLength; - - /** - * the color of the corner line - */ - public int borderCornerColor; - - /** - * the thickness of the guidelines lines. (in pixels) - */ - public float guidelinesThickness; - - /** - * the color of the guidelines lines - */ - public int guidelinesColor; - - /** - * the color of the overlay background around the crop window cover the image parts not in the - * crop window. - */ - public int backgroundColor; - - /** - * the min width the crop window is allowed to be. (in pixels) - */ - public int minCropWindowWidth; - - /** - * the min height the crop window is allowed to be. (in pixels) - */ - public int minCropWindowHeight; - - /** - * the min width the resulting cropping image is allowed to be, affects the cropping window - * limits. (in pixels) - */ - public int minCropResultWidth; - - /** - * the min height the resulting cropping image is allowed to be, affects the cropping window - * limits. (in pixels) - */ - public int minCropResultHeight; - - /** - * the max width the resulting cropping image is allowed to be, affects the cropping window - * limits. (in pixels) - */ - public int maxCropResultWidth; - - /** - * the max height the resulting cropping image is allowed to be, affects the cropping window - * limits. (in pixels) - */ - public int maxCropResultHeight; - - /** - * the title of the {@link CropImageActivity} - */ - public CharSequence activityTitle; - - /** - * the color to use for action bar items icons - */ - public int activityMenuIconColor; - - /** - * the Android Uri to save the cropped image to - */ - public Uri outputUri; - - /** - * the compression format to use when writing the image - */ - public Bitmap.CompressFormat outputCompressFormat; - - /** - * the quality (if applicable) to use when writing the image (0 - 100) - */ - public int outputCompressQuality; - - /** - * the width to resize the cropped image to (see options) - */ - public int outputRequestWidth; - - /** - * the height to resize the cropped image to (see options) - */ - public int outputRequestHeight; - - /** - * the resize method to use on the cropped bitmap (see options documentation) - */ - public CropImageView.RequestSizeOptions outputRequestSizeOptions; - - /** - * if the result of crop image activity should not save the cropped image bitmap - */ - public boolean noOutputImage; - - /** - * the initial rectangle to set on the cropping image after loading - */ - public Rect initialCropWindowRectangle; - - /** - * the initial rotation to set on the cropping image after loading (0-360 degrees clockwise) - */ - public int initialRotation; - - /** - * if to allow (all) rotation during cropping (activity) - */ - public boolean allowRotation; - - /** - * if to allow (all) flipping during cropping (activity) - */ - public boolean allowFlipping; - - /** - * if to allow counter-clockwise rotation during cropping (activity) - */ - public boolean allowCounterRotation; - - /** - * the amount of degrees to rotate clockwise or counter-clockwise - */ - public int rotationDegrees; - - /** - * whether the image should be flipped horizontally - */ - public boolean flipHorizontally; - - /** - * whether the image should be flipped vertically - */ - public boolean flipVertically; - - /** - * optional, the text of the crop menu crop button - */ - public CharSequence cropMenuCropButtonTitle; - - /** - * optional image resource to be used for crop menu crop icon instead of text - */ - public int cropMenuCropButtonIcon; - - /** - * Init options with defaults. - */ - public CropImageOptions() { - - DisplayMetrics dm = Resources.getSystem().getDisplayMetrics(); - - cropShape = CropImageView.CropShape.RECTANGLE; - snapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm); - touchRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, dm); - guidelines = CropImageView.Guidelines.ON_TOUCH; - scaleType = CropImageView.ScaleType.FIT_CENTER; - showCropOverlay = true; - showProgressBar = true; - autoZoomEnabled = true; - multiTouchEnabled = false; - maxZoom = 4; - initialCropWindowPaddingRatio = 0.1f; - - fixAspectRatio = false; - aspectRatioX = 1; - aspectRatioY = 1; - - borderLineThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm); - borderLineColor = Color.argb(170, 255, 255, 255); - borderCornerThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, dm); - borderCornerOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, dm); - borderCornerLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, dm); - borderCornerColor = Color.WHITE; - - guidelinesThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, dm); - guidelinesColor = Color.argb(170, 255, 255, 255); - backgroundColor = Color.argb(119, 0, 0, 0); - - minCropWindowWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm); - minCropWindowHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm); - minCropResultWidth = 40; - minCropResultHeight = 40; - maxCropResultWidth = 99999; - maxCropResultHeight = 99999; - - activityTitle = ""; - activityMenuIconColor = 0; - - outputUri = Uri.EMPTY; - outputCompressFormat = Bitmap.CompressFormat.JPEG; - outputCompressQuality = 90; - outputRequestWidth = 0; - outputRequestHeight = 0; - outputRequestSizeOptions = CropImageView.RequestSizeOptions.NONE; - noOutputImage = false; - - initialCropWindowRectangle = null; - initialRotation = -1; - allowRotation = true; - allowFlipping = true; - allowCounterRotation = false; - rotationDegrees = 90; - flipHorizontally = false; - flipVertically = false; - cropMenuCropButtonTitle = null; - - cropMenuCropButtonIcon = 0; - } - - /** - * Create object from parcel. - */ - protected CropImageOptions(Parcel in) { - cropShape = CropImageView.CropShape.values()[in.readInt()]; - snapRadius = in.readFloat(); - touchRadius = in.readFloat(); - guidelines = CropImageView.Guidelines.values()[in.readInt()]; - scaleType = CropImageView.ScaleType.values()[in.readInt()]; - showCropOverlay = in.readByte() != 0; - showProgressBar = in.readByte() != 0; - autoZoomEnabled = in.readByte() != 0; - multiTouchEnabled = in.readByte() != 0; - maxZoom = in.readInt(); - initialCropWindowPaddingRatio = in.readFloat(); - fixAspectRatio = in.readByte() != 0; - aspectRatioX = in.readInt(); - aspectRatioY = in.readInt(); - borderLineThickness = in.readFloat(); - borderLineColor = in.readInt(); - borderCornerThickness = in.readFloat(); - borderCornerOffset = in.readFloat(); - borderCornerLength = in.readFloat(); - borderCornerColor = in.readInt(); - guidelinesThickness = in.readFloat(); - guidelinesColor = in.readInt(); - backgroundColor = in.readInt(); - minCropWindowWidth = in.readInt(); - minCropWindowHeight = in.readInt(); - minCropResultWidth = in.readInt(); - minCropResultHeight = in.readInt(); - maxCropResultWidth = in.readInt(); - maxCropResultHeight = in.readInt(); - activityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - activityMenuIconColor = in.readInt(); - outputUri = in.readParcelable(Uri.class.getClassLoader()); - outputCompressFormat = Bitmap.CompressFormat.valueOf(in.readString()); - outputCompressQuality = in.readInt(); - outputRequestWidth = in.readInt(); - outputRequestHeight = in.readInt(); - outputRequestSizeOptions = CropImageView.RequestSizeOptions.values()[in.readInt()]; - noOutputImage = in.readByte() != 0; - initialCropWindowRectangle = in.readParcelable(Rect.class.getClassLoader()); - initialRotation = in.readInt(); - allowRotation = in.readByte() != 0; - allowFlipping = in.readByte() != 0; - allowCounterRotation = in.readByte() != 0; - rotationDegrees = in.readInt(); - flipHorizontally = in.readByte() != 0; - flipVertically = in.readByte() != 0; - cropMenuCropButtonTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - cropMenuCropButtonIcon = in.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(cropShape.ordinal()); - dest.writeFloat(snapRadius); - dest.writeFloat(touchRadius); - dest.writeInt(guidelines.ordinal()); - dest.writeInt(scaleType.ordinal()); - dest.writeByte((byte) (showCropOverlay ? 1 : 0)); - dest.writeByte((byte) (showProgressBar ? 1 : 0)); - dest.writeByte((byte) (autoZoomEnabled ? 1 : 0)); - dest.writeByte((byte) (multiTouchEnabled ? 1 : 0)); - dest.writeInt(maxZoom); - dest.writeFloat(initialCropWindowPaddingRatio); - dest.writeByte((byte) (fixAspectRatio ? 1 : 0)); - dest.writeInt(aspectRatioX); - dest.writeInt(aspectRatioY); - dest.writeFloat(borderLineThickness); - dest.writeInt(borderLineColor); - dest.writeFloat(borderCornerThickness); - dest.writeFloat(borderCornerOffset); - dest.writeFloat(borderCornerLength); - dest.writeInt(borderCornerColor); - dest.writeFloat(guidelinesThickness); - dest.writeInt(guidelinesColor); - dest.writeInt(backgroundColor); - dest.writeInt(minCropWindowWidth); - dest.writeInt(minCropWindowHeight); - dest.writeInt(minCropResultWidth); - dest.writeInt(minCropResultHeight); - dest.writeInt(maxCropResultWidth); - dest.writeInt(maxCropResultHeight); - TextUtils.writeToParcel(activityTitle, dest, flags); - dest.writeInt(activityMenuIconColor); - dest.writeParcelable(outputUri, flags); - dest.writeString(outputCompressFormat.name()); - dest.writeInt(outputCompressQuality); - dest.writeInt(outputRequestWidth); - dest.writeInt(outputRequestHeight); - dest.writeInt(outputRequestSizeOptions.ordinal()); - dest.writeInt(noOutputImage ? 1 : 0); - dest.writeParcelable(initialCropWindowRectangle, flags); - dest.writeInt(initialRotation); - dest.writeByte((byte) (allowRotation ? 1 : 0)); - dest.writeByte((byte) (allowFlipping ? 1 : 0)); - dest.writeByte((byte) (allowCounterRotation ? 1 : 0)); - dest.writeInt(rotationDegrees); - dest.writeByte((byte) (flipHorizontally ? 1 : 0)); - dest.writeByte((byte) (flipVertically ? 1 : 0)); - TextUtils.writeToParcel(cropMenuCropButtonTitle, dest, flags); - dest.writeInt(cropMenuCropButtonIcon); - } - - @Override - public int describeContents() { - return 0; - } - - /** - * Validate all the options are withing valid range. - * - * @throws IllegalArgumentException if any of the options is not valid - */ - public void validate() { - if (maxZoom < 0) { - throw new IllegalArgumentException("Cannot set max zoom to a number < 1"); - } - if (touchRadius < 0) { - throw new IllegalArgumentException("Cannot set touch radius value to a number <= 0 "); - } - if (initialCropWindowPaddingRatio < 0 || initialCropWindowPaddingRatio >= 0.5) { - throw new IllegalArgumentException( - "Cannot set initial crop window padding value to a number < 0 or >= 0.5"); - } - if (aspectRatioX <= 0) { - throw new IllegalArgumentException( - "Cannot set aspect ratio value to a number less than or equal to 0."); - } - if (aspectRatioY <= 0) { - throw new IllegalArgumentException( - "Cannot set aspect ratio value to a number less than or equal to 0."); - } - if (borderLineThickness < 0) { - throw new IllegalArgumentException( - "Cannot set line thickness value to a number less than 0."); - } - if (borderCornerThickness < 0) { - throw new IllegalArgumentException( - "Cannot set corner thickness value to a number less than 0."); - } - if (guidelinesThickness < 0) { - throw new IllegalArgumentException( - "Cannot set guidelines thickness value to a number less than 0."); - } - if (minCropWindowHeight < 0) { - throw new IllegalArgumentException( - "Cannot set min crop window height value to a number < 0 "); - } - if (minCropResultWidth < 0) { - throw new IllegalArgumentException("Cannot set min crop result width value to a number < 0 "); - } - if (minCropResultHeight < 0) { - throw new IllegalArgumentException( - "Cannot set min crop result height value to a number < 0 "); - } - if (maxCropResultWidth < minCropResultWidth) { - throw new IllegalArgumentException( - "Cannot set max crop result width to smaller value than min crop result width"); - } - if (maxCropResultHeight < minCropResultHeight) { - throw new IllegalArgumentException( - "Cannot set max crop result height to smaller value than min crop result height"); - } - if (outputRequestWidth < 0) { - throw new IllegalArgumentException("Cannot set request width value to a number < 0 "); - } - if (outputRequestHeight < 0) { - throw new IllegalArgumentException("Cannot set request height value to a number < 0 "); - } - if (rotationDegrees < 0 || rotationDegrees > 360) { - throw new IllegalArgumentException( - "Cannot set rotation degrees value to a number < 0 or > 360"); - } - } -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java deleted file mode 100644 index 2e835b07..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java +++ /dev/null @@ -1,2296 +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.theartofdev.edmodo.cropper; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -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 android.util.AttributeSet; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ProgressBar; - -import androidx.exifinterface.media.ExifInterface; - -import java.lang.ref.WeakReference; -import java.util.UUID; - -/** - * Custom view that provides cropping capabilities to an image. - */ -public class CropImageView extends FrameLayout { - - // region: Fields and Consts - - /** - * Image view widget used to show the image for cropping. - */ - private final ImageView mImageView; - - /** - * Overlay over the image view to show cropping UI. - */ - private final CropOverlayView mCropOverlayView; - - /** - * The matrix used to transform the cropping image in the image view - */ - private final Matrix mImageMatrix = new Matrix(); - - /** - * Reusing matrix instance for reverse matrix calculations. - */ - private final Matrix mImageInverseMatrix = new Matrix(); - - /** - * Progress bar widget to show progress bar on async image loading and cropping. - */ - private final ProgressBar mProgressBar; - - /** - * Rectangle used in image matrix transformation calculation (reusing rect instance) - */ - private final float[] mImagePoints = new float[8]; - - /** - * Rectangle used in image matrix transformation for scale calculation (reusing rect instance) - */ - private final float[] mScaleImagePoints = new float[8]; - - /** - * Animation class to smooth animate zoom-in/out - */ - private CropImageAnimation mAnimation; - - private Bitmap mBitmap; - - /** - * The image rotation value used during loading of the image so we can reset to it - */ - private int mInitialDegreesRotated; - - /** - * How much the image is rotated from original clockwise - */ - private int mDegreesRotated; - - /** - * if the image flipped horizontally - */ - private boolean mFlipHorizontally; - - /** - * if the image flipped vertically - */ - private boolean mFlipVertically; - - private int mLayoutWidth; - - private int mLayoutHeight; - - private int mImageResource; - - /** - * The initial scale type of the image in the crop image view - */ - private ScaleType mScaleType; - - /** - * if to save bitmap on save instance state.
- * It is best to avoid it by using URI in setting image for cropping.
- * If false the bitmap is not saved and if restore is required to view will be empty, storing the - * bitmap requires saving it to file which can be expensive. default: false. - */ - private boolean mSaveBitmapToInstanceState = false; - - /** - * if to show crop overlay UI what contains the crop window UI surrounded by background over the - * cropping image.
- * default: true, may disable for animation or frame transition. - */ - private boolean mShowCropOverlay = true; - - /** - * if to show progress bar when image async loading/cropping is in progress.
- * default: true, disable to provide custom progress bar UI. - */ - private boolean mShowProgressBar = true; - - /** - * if auto-zoom functionality is enabled.
- * default: true. - */ - private boolean mAutoZoomEnabled = true; - - /** - * The max zoom allowed during cropping - */ - private int mMaxZoom; - - /** - * callback to be invoked when crop overlay is released. - */ - private OnSetCropOverlayReleasedListener mOnCropOverlayReleasedListener; - - /** - * callback to be invoked when crop overlay is moved. - */ - private OnSetCropOverlayMovedListener mOnSetCropOverlayMovedListener; - - /** - * callback to be invoked when crop window is changed. - */ - private OnSetCropWindowChangeListener mOnSetCropWindowChangeListener; - - /** - * callback to be invoked when image async loading is complete. - */ - private OnSetImageUriCompleteListener mOnSetImageUriCompleteListener; - - /** - * callback to be invoked when image async cropping is complete. - */ - private OnCropImageCompleteListener mOnCropImageCompleteListener; - - /** - * The URI that the image was loaded from (if loaded from URI) - */ - private Uri mLoadedImageUri; - - /** - * The sample size the image was loaded by if was loaded by URI - */ - private int mLoadedSampleSize = 1; - - /** - * The current zoom level to to scale the cropping image - */ - private float mZoom = 1; - - /** - * The X offset that the cropping image was translated after zooming - */ - private float mZoomOffsetX; - - /** - * The Y offset that the cropping image was translated after zooming - */ - private float mZoomOffsetY; - - /** - * Used to restore the cropping windows rectangle after state restore - */ - private RectF mRestoreCropWindowRect; - - /** - * Used to restore image rotation after state restore - */ - private int mRestoreDegreesRotated; - - /** - * Used to detect size change to handle auto-zoom using {@link #handleCropWindowChanged(boolean, - * boolean)} in {@link #layout(int, int, int, int)}. - */ - private boolean mSizeChanged; - - /** - * Temp URI used to save bitmap image to disk to preserve for instance state in case cropped was - * set with bitmap - */ - private Uri mSaveInstanceStateBitmapUri; - - /** - * Task used to load bitmap async from UI thread - */ - private WeakReference mBitmapLoadingWorkerTask; - - /** - * Task used to crop bitmap async from UI thread - */ - private WeakReference mBitmapCroppingWorkerTask; - // endregion - - public CropImageView(Context context) { - this(context, null); - } - - public CropImageView(Context context, AttributeSet attrs) { - super(context, attrs); - - CropImageOptions options = null; - Intent intent = context instanceof Activity ? ((Activity) context).getIntent() : null; - if (intent != null) { - Bundle bundle = intent.getBundleExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE); - if (bundle != null) { - options = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS); - } - } - - if (options == null) { - - options = new CropImageOptions(); - - if (attrs != null) { - TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0); - try { - options.fixAspectRatio = - ta.getBoolean(R.styleable.CropImageView_cropFixAspectRatio, options.fixAspectRatio); - options.aspectRatioX = - ta.getInteger(R.styleable.CropImageView_cropAspectRatioX, options.aspectRatioX); - options.aspectRatioY = - ta.getInteger(R.styleable.CropImageView_cropAspectRatioY, options.aspectRatioY); - options.scaleType = - ScaleType.values()[ - ta.getInt(R.styleable.CropImageView_cropScaleType, options.scaleType.ordinal())]; - options.autoZoomEnabled = - ta.getBoolean(R.styleable.CropImageView_cropAutoZoomEnabled, options.autoZoomEnabled); - options.multiTouchEnabled = - ta.getBoolean( - R.styleable.CropImageView_cropMultiTouchEnabled, options.multiTouchEnabled); - options.maxZoom = ta.getInteger(R.styleable.CropImageView_cropMaxZoom, options.maxZoom); - options.cropShape = - CropShape.values()[ - ta.getInt(R.styleable.CropImageView_cropShape, options.cropShape.ordinal())]; - options.guidelines = - Guidelines.values()[ - ta.getInt( - R.styleable.CropImageView_cropGuidelines, options.guidelines.ordinal())]; - options.snapRadius = - ta.getDimension(R.styleable.CropImageView_cropSnapRadius, options.snapRadius); - options.touchRadius = - ta.getDimension(R.styleable.CropImageView_cropTouchRadius, options.touchRadius); - options.initialCropWindowPaddingRatio = - ta.getFloat( - R.styleable.CropImageView_cropInitialCropWindowPaddingRatio, - options.initialCropWindowPaddingRatio); - options.borderLineThickness = - ta.getDimension( - R.styleable.CropImageView_cropBorderLineThickness, options.borderLineThickness); - options.borderLineColor = - ta.getInteger(R.styleable.CropImageView_cropBorderLineColor, options.borderLineColor); - options.borderCornerThickness = - ta.getDimension( - R.styleable.CropImageView_cropBorderCornerThickness, - options.borderCornerThickness); - options.borderCornerOffset = - ta.getDimension( - R.styleable.CropImageView_cropBorderCornerOffset, options.borderCornerOffset); - options.borderCornerLength = - ta.getDimension( - R.styleable.CropImageView_cropBorderCornerLength, options.borderCornerLength); - options.borderCornerColor = - ta.getInteger( - R.styleable.CropImageView_cropBorderCornerColor, options.borderCornerColor); - options.guidelinesThickness = - ta.getDimension( - R.styleable.CropImageView_cropGuidelinesThickness, options.guidelinesThickness); - options.guidelinesColor = - ta.getInteger(R.styleable.CropImageView_cropGuidelinesColor, options.guidelinesColor); - options.backgroundColor = - ta.getInteger(R.styleable.CropImageView_cropBackgroundColor, options.backgroundColor); - options.showCropOverlay = - ta.getBoolean(R.styleable.CropImageView_cropShowCropOverlay, mShowCropOverlay); - options.showProgressBar = - ta.getBoolean(R.styleable.CropImageView_cropShowProgressBar, mShowProgressBar); - options.borderCornerThickness = - ta.getDimension( - R.styleable.CropImageView_cropBorderCornerThickness, - options.borderCornerThickness); - options.minCropWindowWidth = - (int) - ta.getDimension( - R.styleable.CropImageView_cropMinCropWindowWidth, options.minCropWindowWidth); - options.minCropWindowHeight = - (int) - ta.getDimension( - R.styleable.CropImageView_cropMinCropWindowHeight, - options.minCropWindowHeight); - options.minCropResultWidth = - (int) - ta.getFloat( - R.styleable.CropImageView_cropMinCropResultWidthPX, - options.minCropResultWidth); - options.minCropResultHeight = - (int) - ta.getFloat( - R.styleable.CropImageView_cropMinCropResultHeightPX, - options.minCropResultHeight); - options.maxCropResultWidth = - (int) - ta.getFloat( - R.styleable.CropImageView_cropMaxCropResultWidthPX, - options.maxCropResultWidth); - options.maxCropResultHeight = - (int) - ta.getFloat( - R.styleable.CropImageView_cropMaxCropResultHeightPX, - options.maxCropResultHeight); - options.flipHorizontally = - ta.getBoolean( - R.styleable.CropImageView_cropFlipHorizontally, options.flipHorizontally); - options.flipVertically = - ta.getBoolean(R.styleable.CropImageView_cropFlipHorizontally, options.flipVertically); - - mSaveBitmapToInstanceState = - ta.getBoolean( - R.styleable.CropImageView_cropSaveBitmapToInstanceState, - mSaveBitmapToInstanceState); - - // if aspect ratio is set then set fixed to true - if (ta.hasValue(R.styleable.CropImageView_cropAspectRatioX) - && ta.hasValue(R.styleable.CropImageView_cropAspectRatioX) - && !ta.hasValue(R.styleable.CropImageView_cropFixAspectRatio)) { - options.fixAspectRatio = true; - } - } finally { - ta.recycle(); - } - } - } - - options.validate(); - - mScaleType = options.scaleType; - mAutoZoomEnabled = options.autoZoomEnabled; - mMaxZoom = options.maxZoom; - mShowCropOverlay = options.showCropOverlay; - mShowProgressBar = options.showProgressBar; - mFlipHorizontally = options.flipHorizontally; - mFlipVertically = options.flipVertically; - - LayoutInflater inflater = LayoutInflater.from(context); - View v = inflater.inflate(R.layout.crop_image_view, this, true); - - mImageView = v.findViewById(R.id.ImageView_image); - mImageView.setScaleType(ImageView.ScaleType.MATRIX); - - mCropOverlayView = v.findViewById(R.id.CropOverlayView); - mCropOverlayView.setCropWindowChangeListener( - new CropOverlayView.CropWindowChangeListener() { - @Override - public void onCropWindowChanged(boolean inProgress) { - handleCropWindowChanged(inProgress, true); - OnSetCropOverlayReleasedListener listener = mOnCropOverlayReleasedListener; - if (listener != null && !inProgress) { - listener.onCropOverlayReleased(getCropRect()); - } - OnSetCropOverlayMovedListener movedListener = mOnSetCropOverlayMovedListener; - if (movedListener != null && inProgress) { - movedListener.onCropOverlayMoved(getCropRect()); - } - } - }); - mCropOverlayView.setInitialAttributeValues(options); - - mProgressBar = v.findViewById(R.id.CropProgressBar); - setProgressBarVisibility(); - } - - /** - * Determines the specs for the onMeasure function. Calculates the width or height depending on - * the mode. - * - * @param measureSpecMode The mode of the measured width or height. - * @param measureSpecSize The size of the measured width or height. - * @param desiredSize The desired size of the measured width or height. - * @return The final size of the width or height. - */ - private static int getOnMeasureSpec(int measureSpecMode, int measureSpecSize, int desiredSize) { - - // Measure Width - int spec; - if (measureSpecMode == MeasureSpec.EXACTLY) { - // Must be this size - spec = measureSpecSize; - } else if (measureSpecMode == MeasureSpec.AT_MOST) { - // Can't be bigger than...; match_parent value - spec = Math.min(desiredSize, measureSpecSize); - } else { - // Be whatever you want; wrap_content - spec = desiredSize; - } - - return spec; - } - - /** - * Get the scale type of the image in the crop view. - */ - public ScaleType getScaleType() { - return mScaleType; - } - - /** - * Set the scale type of the image in the crop view - */ - public void setScaleType(ScaleType scaleType) { - if (scaleType != mScaleType) { - mScaleType = scaleType; - mZoom = 1; - mZoomOffsetX = mZoomOffsetY = 0; - mCropOverlayView.resetCropOverlayView(); - requestLayout(); - } - } - - /** - * The shape of the cropping area - rectangle/circular. - */ - public CropShape getCropShape() { - return mCropOverlayView.getCropShape(); - } - - /** - * The shape of the cropping area - rectangle/circular.
- * To set square/circle crop shape set aspect ratio to 1:1. - */ - public void setCropShape(CropShape cropShape) { - mCropOverlayView.setCropShape(cropShape); - } - - /** - * if auto-zoom functionality is enabled. default: true. - */ - public boolean isAutoZoomEnabled() { - return mAutoZoomEnabled; - } - - /** - * Set auto-zoom functionality to enabled/disabled. - */ - public void setAutoZoomEnabled(boolean autoZoomEnabled) { - if (mAutoZoomEnabled != autoZoomEnabled) { - mAutoZoomEnabled = autoZoomEnabled; - handleCropWindowChanged(false, false); - mCropOverlayView.invalidate(); - } - } - - /** - * Set multi touch functionality to enabled/disabled. - */ - public void setMultiTouchEnabled(boolean multiTouchEnabled) { - if (mCropOverlayView.setMultiTouchEnabled(multiTouchEnabled)) { - handleCropWindowChanged(false, false); - mCropOverlayView.invalidate(); - } - } - - /** - * The max zoom allowed during cropping. - */ - public int getMaxZoom() { - return mMaxZoom; - } - - /** - * The max zoom allowed during cropping. - */ - public void setMaxZoom(int maxZoom) { - if (mMaxZoom != maxZoom && maxZoom > 0) { - mMaxZoom = maxZoom; - handleCropWindowChanged(false, false); - mCropOverlayView.invalidate(); - } - } - - /** - * the min size the resulting cropping image is allowed to be, affects the cropping window limits - * (in pixels).
- */ - public void setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) { - mCropOverlayView.setMinCropResultSize(minCropResultWidth, minCropResultHeight); - } - - /** - * the max size the resulting cropping image is allowed to be, affects the cropping window limits - * (in pixels).
- */ - public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) { - mCropOverlayView.setMaxCropResultSize(maxCropResultWidth, maxCropResultHeight); - } - - /** - * Get the amount of degrees the cropping image is rotated cloackwise.
- * - * @return 0-360 - */ - public int getRotatedDegrees() { - return mDegreesRotated; - } - - /** - * Set the amount of degrees the cropping image is rotated cloackwise.
- * - * @param degrees 0-360 - */ - public void setRotatedDegrees(int degrees) { - if (mDegreesRotated != degrees) { - rotateImage(degrees - mDegreesRotated); - } - } - - /** - * whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows it to - * be changed. - */ - public boolean isFixAspectRatio() { - return mCropOverlayView.isFixAspectRatio(); - } - - /** - * Sets whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows - * it to be changed. - */ - public void setFixedAspectRatio(boolean fixAspectRatio) { - mCropOverlayView.setFixedAspectRatio(fixAspectRatio); - } - - /** - * whether the image should be flipped horizontally - */ - public boolean isFlippedHorizontally() { - return mFlipHorizontally; - } - - /** - * Sets whether the image should be flipped horizontally - */ - public void setFlippedHorizontally(boolean flipHorizontally) { - if (mFlipHorizontally != flipHorizontally) { - mFlipHorizontally = flipHorizontally; - applyImageMatrix(getWidth(), getHeight(), true, false); - } - } - - /** - * whether the image should be flipped vertically - */ - public boolean isFlippedVertically() { - return mFlipVertically; - } - - /** - * Sets whether the image should be flipped vertically - */ - public void setFlippedVertically(boolean flipVertically) { - if (mFlipVertically != flipVertically) { - mFlipVertically = flipVertically; - applyImageMatrix(getWidth(), getHeight(), true, false); - } - } - - /** - * Get the current guidelines option set. - */ - public Guidelines getGuidelines() { - return mCropOverlayView.getGuidelines(); - } - - /** - * Sets the guidelines for the CropOverlayView to be either on, off, or to show when resizing the - * application. - */ - public void setGuidelines(Guidelines guidelines) { - mCropOverlayView.setGuidelines(guidelines); - } - - /** - * both the X and Y values of the aspectRatio. - */ - public Pair getAspectRatio() { - return new Pair<>(mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY()); - } - - /** - * Sets the both the X and Y values of the aspectRatio.
- * Sets fixed aspect ratio to TRUE. - * - * @param aspectRatioX int that specifies the new X value of the aspect ratio - * @param aspectRatioY int that specifies the new Y value of the aspect ratio - */ - public void setAspectRatio(int aspectRatioX, int aspectRatioY) { - mCropOverlayView.setAspectRatioX(aspectRatioX); - mCropOverlayView.setAspectRatioY(aspectRatioY); - setFixedAspectRatio(true); - } - - /** - * Clears set aspect ratio values and sets fixed aspect ratio to FALSE. - */ - public void clearAspectRatio() { - mCropOverlayView.setAspectRatioX(1); - mCropOverlayView.setAspectRatioY(1); - setFixedAspectRatio(false); - } - - /** - * An edge of the crop window will snap to the corresponding edge of a specified bounding box when - * the crop window edge is less than or equal to this distance (in pixels) away from the bounding - * box edge. (default: 3dp) - */ - public void setSnapRadius(float snapRadius) { - if (snapRadius >= 0) { - mCropOverlayView.setSnapRadius(snapRadius); - } - } - - /** - * if to show progress bar when image async loading/cropping is in progress.
- * default: true, disable to provide custom progress bar UI. - */ - public boolean isShowProgressBar() { - return mShowProgressBar; - } - - /** - * if to show progress bar when image async loading/cropping is in progress.
- * default: true, disable to provide custom progress bar UI. - */ - public void setShowProgressBar(boolean showProgressBar) { - if (mShowProgressBar != showProgressBar) { - mShowProgressBar = showProgressBar; - setProgressBarVisibility(); - } - } - - /** - * if to show crop overlay UI what contains the crop window UI surrounded by background over the - * cropping image.
- * default: true, may disable for animation or frame transition. - */ - public boolean isShowCropOverlay() { - return mShowCropOverlay; - } - - /** - * if to show crop overlay UI what contains the crop window UI surrounded by background over the - * cropping image.
- * default: true, may disable for animation or frame transition. - */ - public void setShowCropOverlay(boolean showCropOverlay) { - if (mShowCropOverlay != showCropOverlay) { - mShowCropOverlay = showCropOverlay; - setCropOverlayVisibility(); - } - } - - /** - * if to save bitmap on save instance state.
- * It is best to avoid it by using URI in setting image for cropping.
- * If false the bitmap is not saved and if restore is required to view will be empty, storing the - * bitmap requires saving it to file which can be expensive. default: false. - */ - public boolean isSaveBitmapToInstanceState() { - return mSaveBitmapToInstanceState; - } - - /** - * if to save bitmap on save instance state.
- * It is best to avoid it by using URI in setting image for cropping.
- * If false the bitmap is not saved and if restore is required to view will be empty, storing the - * bitmap requires saving it to file which can be expensive. default: false. - */ - public void setSaveBitmapToInstanceState(boolean saveBitmapToInstanceState) { - mSaveBitmapToInstanceState = saveBitmapToInstanceState; - } - - /** - * Returns the integer of the imageResource - */ - public int getImageResource() { - return mImageResource; - } - - /** - * Sets a Drawable as the content of the CropImageView. - * - * @param resId the drawable resource ID to set - */ - public void setImageResource(int resId) { - if (resId != 0) { - mCropOverlayView.setInitialCropWindowRect(null); - Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId); - setBitmap(bitmap, resId, null, 1, 0); - } - } - - /** - * Get the URI of an image that was set by URI, null otherwise. - */ - public Uri getImageUri() { - return mLoadedImageUri; - } - - /** - * Gets the source Bitmap's dimensions. This represents the largest possible crop rectangle. - * - * @return a Rect instance dimensions of the source Bitmap - */ - public Rect getWholeImageRect() { - int loadedSampleSize = mLoadedSampleSize; - Bitmap bitmap = mBitmap; - if (bitmap == null) { - return null; - } - - int orgWidth = bitmap.getWidth() * loadedSampleSize; - int orgHeight = bitmap.getHeight() * loadedSampleSize; - return new Rect(0, 0, orgWidth, orgHeight); - } - - /** - * Gets the crop window's position relative to the source Bitmap (not the image displayed in the - * CropImageView) using the original image rotation. - * - * @return a Rect instance containing cropped area boundaries of the source Bitmap - */ - public Rect getCropRect() { - int loadedSampleSize = mLoadedSampleSize; - Bitmap bitmap = mBitmap; - if (bitmap == null) { - return null; - } - - // get the points of the crop rectangle adjusted to source bitmap - float[] points = getCropPoints(); - - int orgWidth = bitmap.getWidth() * loadedSampleSize; - int orgHeight = bitmap.getHeight() * loadedSampleSize; - - // get the rectangle for the points (it may be larger than original if rotation is not stright) - return BitmapUtils.getRectFromPoints( - points, - orgWidth, - orgHeight, - mCropOverlayView.isFixAspectRatio(), - mCropOverlayView.getAspectRatioX(), - mCropOverlayView.getAspectRatioY()); - } - - /** - * Set the crop window position and size to the given rectangle.
- * Image to crop must be first set before invoking this, for async - after complete callback. - * - * @param rect window rectangle (position and size) relative to source bitmap - */ - public void setCropRect(Rect rect) { - mCropOverlayView.setInitialCropWindowRect(rect); - } - - /** - * Gets the crop window's position relative to the parent's view at screen. - * - * @return a Rect instance containing cropped area boundaries of the source Bitmap - */ - public RectF getCropWindowRect() { - if (mCropOverlayView == null) { - return null; - } - return mCropOverlayView.getCropWindowRect(); - } - - /** - * Gets the 4 points of crop window's position relative to the source Bitmap (not the image - * displayed in the CropImageView) using the original image rotation.
- * Note: the 4 points may not be a rectangle if the image was rotates to NOT stright angle (!= - * 90/180/270). - * - * @return 4 points (x0,y0,x1,y1,x2,y2,x3,y3) of cropped area boundaries - */ - public float[] getCropPoints() { - - // Get crop window position relative to the displayed image. - RectF cropWindowRect = mCropOverlayView.getCropWindowRect(); - - float[] points = - new float[]{ - cropWindowRect.left, - cropWindowRect.top, - cropWindowRect.right, - cropWindowRect.top, - cropWindowRect.right, - cropWindowRect.bottom, - cropWindowRect.left, - cropWindowRect.bottom - }; - - mImageMatrix.invert(mImageInverseMatrix); - mImageInverseMatrix.mapPoints(points); - - for (int i = 0; i < points.length; i++) { - points[i] *= mLoadedSampleSize; - } - - return points; - } - - /** - * Reset crop window to initial rectangle. - */ - public void resetCropRect() { - mZoom = 1; - mZoomOffsetX = 0; - mZoomOffsetY = 0; - mDegreesRotated = mInitialDegreesRotated; - mFlipHorizontally = false; - mFlipVertically = false; - applyImageMatrix(getWidth(), getHeight(), false, false); - mCropOverlayView.resetCropWindowRect(); - } - - /** - * Gets the cropped image based on the current crop window. - * - * @return a new Bitmap representing the cropped image - */ - public Bitmap getCroppedImage() { - return getCroppedImage(0, 0, RequestSizeOptions.NONE); - } - - /** - * Gets the cropped image based on the current crop window.
- * Uses {@link RequestSizeOptions#RESIZE_INSIDE} option. - * - * @param reqWidth the width to resize the cropped image to - * @param reqHeight the height to resize the cropped image to - * @return a new Bitmap representing the cropped image - */ - public Bitmap getCroppedImage(int reqWidth, int reqHeight) { - return getCroppedImage(reqWidth, reqHeight, RequestSizeOptions.RESIZE_INSIDE); - } - - /** - * Gets the cropped image based on the current crop window.
- * - * @param reqWidth the width to resize the cropped image to (see options) - * @param reqHeight the height to resize the cropped image to (see options) - * @param options the resize method to use, see its documentation - * @return a new Bitmap representing the cropped image - */ - public Bitmap getCroppedImage(int reqWidth, int reqHeight, RequestSizeOptions options) { - Bitmap croppedBitmap = null; - if (mBitmap != null) { - mImageView.clearAnimation(); - - reqWidth = options != RequestSizeOptions.NONE ? reqWidth : 0; - reqHeight = options != RequestSizeOptions.NONE ? reqHeight : 0; - - if (mLoadedImageUri != null - && (mLoadedSampleSize > 1 || options == RequestSizeOptions.SAMPLING)) { - int orgWidth = mBitmap.getWidth() * mLoadedSampleSize; - int orgHeight = mBitmap.getHeight() * mLoadedSampleSize; - BitmapUtils.BitmapSampled bitmapSampled = - BitmapUtils.cropBitmap( - getContext(), - mLoadedImageUri, - getCropPoints(), - mDegreesRotated, - orgWidth, - orgHeight, - mCropOverlayView.isFixAspectRatio(), - mCropOverlayView.getAspectRatioX(), - mCropOverlayView.getAspectRatioY(), - reqWidth, - reqHeight, - mFlipHorizontally, - mFlipVertically); - croppedBitmap = bitmapSampled.bitmap; - } else { - croppedBitmap = - BitmapUtils.cropBitmapObjectHandleOOM( - mBitmap, - getCropPoints(), - mDegreesRotated, - mCropOverlayView.isFixAspectRatio(), - mCropOverlayView.getAspectRatioX(), - mCropOverlayView.getAspectRatioY(), - mFlipHorizontally, - mFlipVertically) - .bitmap; - } - - croppedBitmap = BitmapUtils.resizeBitmap(croppedBitmap, reqWidth, reqHeight, options); - } - - return croppedBitmap; - } - - /** - * Gets the cropped image based on the current crop window.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - */ - public void getCroppedImageAsync() { - getCroppedImageAsync(0, 0, RequestSizeOptions.NONE); - } - - /** - * Gets the cropped image based on the current crop window.
- * Uses {@link RequestSizeOptions#RESIZE_INSIDE} option.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - * - * @param reqWidth the width to resize the cropped image to - * @param reqHeight the height to resize the cropped image to - */ - public void getCroppedImageAsync(int reqWidth, int reqHeight) { - getCroppedImageAsync(reqWidth, reqHeight, RequestSizeOptions.RESIZE_INSIDE); - } - - /** - * Gets the cropped image based on the current crop window.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - * - * @param reqWidth the width to resize the cropped image to (see options) - * @param reqHeight the height to resize the cropped image to (see options) - * @param options the resize method to use, see its documentation - */ - public void getCroppedImageAsync(int reqWidth, int reqHeight, RequestSizeOptions options) { - if (mOnCropImageCompleteListener == null) { - throw new IllegalArgumentException("mOnCropImageCompleteListener is not set"); - } - startCropWorkerTask(reqWidth, reqHeight, options, null, null, 0); - } - - /** - * Save the cropped image based on the current crop window to the given uri.
- * Uses JPEG image compression with 90 compression quality.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - * - * @param saveUri the Android Uri to save the cropped image to - */ - public void saveCroppedImageAsync(Uri saveUri) { - saveCroppedImageAsync(saveUri, Bitmap.CompressFormat.JPEG, 90, 0, 0, RequestSizeOptions.NONE); - } - - /** - * Save the cropped image based on the current crop window to the given uri.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - * - * @param saveUri the Android Uri to save the cropped image to - * @param saveCompressFormat the compression format to use when writing the image - * @param saveCompressQuality the quality (if applicable) to use when writing the image (0 - 100) - */ - public void saveCroppedImageAsync( - Uri saveUri, Bitmap.CompressFormat saveCompressFormat, int saveCompressQuality) { - saveCroppedImageAsync( - saveUri, saveCompressFormat, saveCompressQuality, 0, 0, RequestSizeOptions.NONE); - } - - /** - * Save the cropped image based on the current crop window to the given uri.
- * Uses {@link RequestSizeOptions#RESIZE_INSIDE} option.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - * - * @param saveUri the Android Uri to save the cropped image to - * @param saveCompressFormat the compression format to use when writing the image - * @param saveCompressQuality the quality (if applicable) to use when writing the image (0 - 100) - * @param reqWidth the width to resize the cropped image to - * @param reqHeight the height to resize the cropped image to - */ - public void saveCroppedImageAsync( - Uri saveUri, - Bitmap.CompressFormat saveCompressFormat, - int saveCompressQuality, - int reqWidth, - int reqHeight) { - saveCroppedImageAsync( - saveUri, - saveCompressFormat, - saveCompressQuality, - reqWidth, - reqHeight, - RequestSizeOptions.RESIZE_INSIDE); - } - - /** - * Save the cropped image based on the current crop window to the given uri.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - * - * @param saveUri the Android Uri to save the cropped image to - * @param saveCompressFormat the compression format to use when writing the image - * @param saveCompressQuality the quality (if applicable) to use when writing the image (0 - 100) - * @param reqWidth the width to resize the cropped image to (see options) - * @param reqHeight the height to resize the cropped image to (see options) - * @param options the resize method to use, see its documentation - */ - public void saveCroppedImageAsync( - Uri saveUri, - Bitmap.CompressFormat saveCompressFormat, - int saveCompressQuality, - int reqWidth, - int reqHeight, - RequestSizeOptions options) { - if (mOnCropImageCompleteListener == null) { - throw new IllegalArgumentException("mOnCropImageCompleteListener is not set"); - } - startCropWorkerTask( - reqWidth, reqHeight, options, saveUri, saveCompressFormat, saveCompressQuality); - } - - /** - * Set the callback t - */ - public void setOnSetCropOverlayReleasedListener(OnSetCropOverlayReleasedListener listener) { - mOnCropOverlayReleasedListener = listener; - } - - /** - * Set the callback when the cropping is moved - */ - public void setOnSetCropOverlayMovedListener(OnSetCropOverlayMovedListener listener) { - mOnSetCropOverlayMovedListener = listener; - } - - /** - * Set the callback when the crop window is changed - */ - public void setOnCropWindowChangedListener(OnSetCropWindowChangeListener listener) { - mOnSetCropWindowChangeListener = listener; - } - - /** - * Set the callback to be invoked when image async loading ({@link #setImageUriAsync(Uri)}) is - * complete (successful or failed). - */ - public void setOnSetImageUriCompleteListener(OnSetImageUriCompleteListener listener) { - mOnSetImageUriCompleteListener = listener; - } - - /** - * Set the callback to be invoked when image async cropping image ({@link #getCroppedImageAsync()} - * or {@link #saveCroppedImageAsync(Uri)}) is complete (successful or failed). - */ - public void setOnCropImageCompleteListener(OnCropImageCompleteListener listener) { - mOnCropImageCompleteListener = listener; - } - - /** - * Sets a Bitmap as the content of the CropImageView. - * - * @param bitmap the Bitmap to set - */ - public void setImageBitmap(Bitmap bitmap) { - mCropOverlayView.setInitialCropWindowRect(null); - setBitmap(bitmap, 0, null, 1, 0); - } - - /** - * Sets a Bitmap and initializes the image rotation according to the EXIT data.
- *
- * The EXIF can be retrieved by doing the following: - * ExifInterface exif = new ExifInterface(path); - * - * @param bitmap the original bitmap to set; if null, this - * @param exif the EXIF information about this bitmap; may be null - */ - public void setImageBitmap(Bitmap bitmap, ExifInterface exif) { - Bitmap setBitmap; - int degreesRotated = 0; - if (bitmap != null && exif != null) { - BitmapUtils.RotateBitmapResult result = BitmapUtils.rotateBitmapByExif(bitmap, exif); - setBitmap = result.bitmap; - degreesRotated = result.degrees; - mInitialDegreesRotated = result.degrees; - } else { - setBitmap = bitmap; - } - mCropOverlayView.setInitialCropWindowRect(null); - setBitmap(setBitmap, 0, null, 1, degreesRotated); - } - - /** - * Sets a bitmap loaded from the given Android URI as the content of the CropImageView.
- * Can be used with URI from gallery or camera source.
- * Will rotate the image by exif data.
- * - * @param uri the URI to load the image from - */ - public void setImageUriAsync(Uri uri) { - if (uri != null) { - BitmapLoadingWorkerTask currentTask = - mBitmapLoadingWorkerTask != null ? mBitmapLoadingWorkerTask.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); - } - - // either no existing task is working or we canceled it, need to load new URI - clearImageInt(); - mRestoreCropWindowRect = null; - mRestoreDegreesRotated = 0; - mCropOverlayView.setInitialCropWindowRect(null); - mBitmapLoadingWorkerTask = new WeakReference<>(new BitmapLoadingWorkerTask(this, uri)); - mBitmapLoadingWorkerTask.get().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - setProgressBarVisibility(); - } - } - - /** - * Clear the current image set for cropping. - */ - public void clearImage() { - clearImageInt(); - mCropOverlayView.setInitialCropWindowRect(null); - } - - /** - * Rotates image by the specified number of degrees clockwise.
- * Negative values represent counter-clockwise rotations. - * - * @param degrees Integer specifying the number of degrees to rotate. - */ - public void rotateImage(int degrees) { - if (mBitmap != null) { - // Force degrees to be a non-zero value between 0 and 360 (inclusive) - if (degrees < 0) { - degrees = (degrees % 360) + 360; - } else { - degrees = degrees % 360; - } - - boolean flipAxes = - !mCropOverlayView.isFixAspectRatio() - && ((degrees > 45 && degrees < 135) || (degrees > 215 && degrees < 305)); - BitmapUtils.RECT.set(mCropOverlayView.getCropWindowRect()); - float halfWidth = (flipAxes ? BitmapUtils.RECT.height() : BitmapUtils.RECT.width()) / 2f; - float halfHeight = (flipAxes ? BitmapUtils.RECT.width() : BitmapUtils.RECT.height()) / 2f; - if (flipAxes) { - boolean isFlippedHorizontally = mFlipHorizontally; - mFlipHorizontally = mFlipVertically; - mFlipVertically = isFlippedHorizontally; - } - - mImageMatrix.invert(mImageInverseMatrix); - - BitmapUtils.POINTS[0] = BitmapUtils.RECT.centerX(); - BitmapUtils.POINTS[1] = BitmapUtils.RECT.centerY(); - BitmapUtils.POINTS[2] = 0; - BitmapUtils.POINTS[3] = 0; - BitmapUtils.POINTS[4] = 1; - BitmapUtils.POINTS[5] = 0; - mImageInverseMatrix.mapPoints(BitmapUtils.POINTS); - - // This is valid because degrees is not negative. - mDegreesRotated = (mDegreesRotated + degrees) % 360; - - applyImageMatrix(getWidth(), getHeight(), true, false); - - // adjust the zoom so the crop window size remains the same even after image scale change - mImageMatrix.mapPoints(BitmapUtils.POINTS2, BitmapUtils.POINTS); - mZoom /= - Math.sqrt( - Math.pow(BitmapUtils.POINTS2[4] - BitmapUtils.POINTS2[2], 2) - + Math.pow(BitmapUtils.POINTS2[5] - BitmapUtils.POINTS2[3], 2)); - mZoom = Math.max(mZoom, 1); - - applyImageMatrix(getWidth(), getHeight(), true, false); - - mImageMatrix.mapPoints(BitmapUtils.POINTS2, BitmapUtils.POINTS); - - // adjust the width/height by the changes in scaling to the image - double change = - Math.sqrt( - Math.pow(BitmapUtils.POINTS2[4] - BitmapUtils.POINTS2[2], 2) - + Math.pow(BitmapUtils.POINTS2[5] - BitmapUtils.POINTS2[3], 2)); - halfWidth *= change; - halfHeight *= change; - - // calculate the new crop window rectangle to center in the same location and have proper - // width/height - BitmapUtils.RECT.set( - BitmapUtils.POINTS2[0] - halfWidth, - BitmapUtils.POINTS2[1] - halfHeight, - BitmapUtils.POINTS2[0] + halfWidth, - BitmapUtils.POINTS2[1] + halfHeight); - - mCropOverlayView.resetCropOverlayView(); - mCropOverlayView.setCropWindowRect(BitmapUtils.RECT); - applyImageMatrix(getWidth(), getHeight(), true, false); - handleCropWindowChanged(false, false); - - // make sure the crop window rectangle is within the cropping image bounds after all the - // changes - mCropOverlayView.fixCurrentCropWindowRect(); - } - } - - /** - * Flips the image horizontally. - */ - public void flipImageHorizontally() { - mFlipHorizontally = !mFlipHorizontally; - applyImageMatrix(getWidth(), getHeight(), true, false); - } - - // region: Private methods - - /** - * Flips the image vertically. - */ - public void flipImageVertically() { - mFlipVertically = !mFlipVertically; - applyImageMatrix(getWidth(), getHeight(), true, false); - } - - /** - * On complete of the async bitmap loading by {@link #setImageUriAsync(Uri)} set the result to the - * widget if still relevant and call listener if set. - * - * @param result the result of bitmap loading - */ - void onSetImageUriAsyncComplete(BitmapLoadingWorkerTask.Result result) { - - mBitmapLoadingWorkerTask = null; - setProgressBarVisibility(); - - if (result.error == null) { - mInitialDegreesRotated = result.degreesRotated; - setBitmap(result.bitmap, 0, result.uri, result.loadSampleSize, result.degreesRotated); - } - - OnSetImageUriCompleteListener listener = mOnSetImageUriCompleteListener; - if (listener != null) { - listener.onSetImageUriComplete(this, result.uri, result.error); - } - } - - /** - * On complete of the async bitmap cropping by {@link #getCroppedImageAsync()} call listener if - * set. - * - * @param result the result of bitmap cropping - */ - void onImageCroppingAsyncComplete(BitmapCroppingWorkerTask.Result result) { - - mBitmapCroppingWorkerTask = null; - setProgressBarVisibility(); - - OnCropImageCompleteListener listener = mOnCropImageCompleteListener; - if (listener != null) { - CropResult cropResult = - new CropResult( - mBitmap, - mLoadedImageUri, - result.bitmap, - result.uri, - result.error, - getCropPoints(), - getCropRect(), - getWholeImageRect(), - getRotatedDegrees(), - result.sampleSize); - listener.onCropImageComplete(this, cropResult); - } - } - - /** - * Set the given bitmap to be used in for cropping
- * Optionally clear full if the bitmap is new, or partial clear if the bitmap has been - * manipulated. - */ - private void setBitmap( - Bitmap bitmap, int imageResource, Uri imageUri, int loadSampleSize, int degreesRotated) { - if (mBitmap == null || !mBitmap.equals(bitmap)) { - - mImageView.clearAnimation(); - - clearImageInt(); - - mBitmap = bitmap; - mImageView.setImageBitmap(mBitmap); - - mLoadedImageUri = imageUri; - mImageResource = imageResource; - mLoadedSampleSize = loadSampleSize; - mDegreesRotated = degreesRotated; - - applyImageMatrix(getWidth(), getHeight(), true, false); - - if (mCropOverlayView != null) { - mCropOverlayView.resetCropOverlayView(); - setCropOverlayVisibility(); - } - } - } - - /** - * Clear the current image set for cropping.
- * Full clear will also clear the data of the set image like Uri or Resource id while partial - * clear will only clear the bitmap and recycle if required. - */ - private void clearImageInt() { - - // if we allocated the bitmap, release it as fast as possible - if (mBitmap != null && (mImageResource > 0 || mLoadedImageUri != null)) { - mBitmap.recycle(); - } - mBitmap = null; - - // clean the loaded image flags for new image - mImageResource = 0; - mLoadedImageUri = null; - mLoadedSampleSize = 1; - mDegreesRotated = 0; - mZoom = 1; - mZoomOffsetX = 0; - mZoomOffsetY = 0; - mImageMatrix.reset(); - mSaveInstanceStateBitmapUri = null; - - mImageView.setImageBitmap(null); - - setCropOverlayVisibility(); - } - - /** - * Gets the cropped image based on the current crop window.
- * If (reqWidth,reqHeight) is given AND image is loaded from URI cropping will try to use sample - * size to fit in the requested width and height down-sampling if possible - optimization to get - * best size to quality.
- * The result will be invoked to listener set by {@link - * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}. - * - * @param reqWidth the width to resize the cropped image to (see options) - * @param reqHeight the height to resize the cropped image to (see options) - * @param options the resize method to use on the cropped bitmap - * @param saveUri optional: to save the cropped image to - * @param saveCompressFormat if saveUri is given, the given compression will be used for saving - * the image - * @param saveCompressQuality if saveUri is given, the given quality will be used for the - * compression. - */ - public void startCropWorkerTask( - int reqWidth, - int reqHeight, - RequestSizeOptions options, - Uri saveUri, - Bitmap.CompressFormat saveCompressFormat, - int saveCompressQuality) { - Bitmap bitmap = mBitmap; - if (bitmap != null) { - mImageView.clearAnimation(); - - BitmapCroppingWorkerTask currentTask = - mBitmapCroppingWorkerTask != null ? mBitmapCroppingWorkerTask.get() : null; - if (currentTask != null) { - // cancel previous cropping - currentTask.cancel(true); - } - - reqWidth = options != RequestSizeOptions.NONE ? reqWidth : 0; - reqHeight = options != RequestSizeOptions.NONE ? reqHeight : 0; - - int orgWidth = bitmap.getWidth() * mLoadedSampleSize; - int orgHeight = bitmap.getHeight() * mLoadedSampleSize; - if (mLoadedImageUri != null - && (mLoadedSampleSize > 1 || options == RequestSizeOptions.SAMPLING)) { - mBitmapCroppingWorkerTask = - new WeakReference<>( - new BitmapCroppingWorkerTask( - this, - mLoadedImageUri, - getCropPoints(), - mDegreesRotated, - orgWidth, - orgHeight, - mCropOverlayView.isFixAspectRatio(), - mCropOverlayView.getAspectRatioX(), - mCropOverlayView.getAspectRatioY(), - reqWidth, - reqHeight, - mFlipHorizontally, - mFlipVertically, - options, - saveUri, - saveCompressFormat, - saveCompressQuality)); - } else { - mBitmapCroppingWorkerTask = - new WeakReference<>( - new BitmapCroppingWorkerTask( - this, - bitmap, - getCropPoints(), - mDegreesRotated, - mCropOverlayView.isFixAspectRatio(), - mCropOverlayView.getAspectRatioX(), - mCropOverlayView.getAspectRatioY(), - reqWidth, - reqHeight, - mFlipHorizontally, - mFlipVertically, - options, - saveUri, - saveCompressFormat, - saveCompressQuality)); - } - mBitmapCroppingWorkerTask.get().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - setProgressBarVisibility(); - } - } - - @Override - public Parcelable onSaveInstanceState() { - if (mLoadedImageUri == null && mBitmap == null && mImageResource < 1) { - return super.onSaveInstanceState(); - } - - Bundle bundle = new Bundle(); - Uri imageUri = mLoadedImageUri; - if (mSaveBitmapToInstanceState && imageUri == null && mImageResource < 1) { - mSaveInstanceStateBitmapUri = - imageUri = - BitmapUtils.writeTempStateStoreBitmap( - getContext(), mBitmap, mSaveInstanceStateBitmapUri); - } - if (imageUri != null && mBitmap != null) { - String key = UUID.randomUUID().toString(); - BitmapUtils.mStateBitmap = new Pair<>(key, new WeakReference<>(mBitmap)); - bundle.putString("LOADED_IMAGE_STATE_BITMAP_KEY", key); - } - if (mBitmapLoadingWorkerTask != null) { - BitmapLoadingWorkerTask task = mBitmapLoadingWorkerTask.get(); - if (task != null) { - bundle.putParcelable("LOADING_IMAGE_URI", task.getUri()); - } - } - bundle.putParcelable("instanceState", super.onSaveInstanceState()); - bundle.putParcelable("LOADED_IMAGE_URI", imageUri); - bundle.putInt("LOADED_IMAGE_RESOURCE", mImageResource); - bundle.putInt("LOADED_SAMPLE_SIZE", mLoadedSampleSize); - bundle.putInt("DEGREES_ROTATED", mDegreesRotated); - bundle.putParcelable("INITIAL_CROP_RECT", mCropOverlayView.getInitialCropWindowRect()); - - BitmapUtils.RECT.set(mCropOverlayView.getCropWindowRect()); - - mImageMatrix.invert(mImageInverseMatrix); - mImageInverseMatrix.mapRect(BitmapUtils.RECT); - - bundle.putParcelable("CROP_WINDOW_RECT", BitmapUtils.RECT); - bundle.putString("CROP_SHAPE", mCropOverlayView.getCropShape().name()); - bundle.putBoolean("CROP_AUTO_ZOOM_ENABLED", mAutoZoomEnabled); - bundle.putInt("CROP_MAX_ZOOM", mMaxZoom); - bundle.putBoolean("CROP_FLIP_HORIZONTALLY", mFlipHorizontally); - bundle.putBoolean("CROP_FLIP_VERTICALLY", mFlipVertically); - - return bundle; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - - if (state instanceof Bundle) { - Bundle bundle = (Bundle) state; - - // prevent restoring state if already set by outside code - if (mBitmapLoadingWorkerTask == null - && mLoadedImageUri == null - && mBitmap == null - && mImageResource == 0) { - - Uri uri = bundle.getParcelable("LOADED_IMAGE_URI"); - if (uri != null) { - String key = bundle.getString("LOADED_IMAGE_STATE_BITMAP_KEY"); - if (key != null) { - Bitmap stateBitmap = - BitmapUtils.mStateBitmap != null && BitmapUtils.mStateBitmap.first.equals(key) - ? BitmapUtils.mStateBitmap.second.get() - : null; - BitmapUtils.mStateBitmap = null; - if (stateBitmap != null && !stateBitmap.isRecycled()) { - setBitmap(stateBitmap, 0, uri, bundle.getInt("LOADED_SAMPLE_SIZE"), 0); - } - } - if (mLoadedImageUri == null) { - setImageUriAsync(uri); - } - } else { - int resId = bundle.getInt("LOADED_IMAGE_RESOURCE"); - if (resId > 0) { - setImageResource(resId); - } else { - uri = bundle.getParcelable("LOADING_IMAGE_URI"); - if (uri != null) { - setImageUriAsync(uri); - } - } - } - - mDegreesRotated = mRestoreDegreesRotated = bundle.getInt("DEGREES_ROTATED"); - - Rect initialCropRect = bundle.getParcelable("INITIAL_CROP_RECT"); - if (initialCropRect != null - && (initialCropRect.width() > 0 || initialCropRect.height() > 0)) { - mCropOverlayView.setInitialCropWindowRect(initialCropRect); - } - - RectF cropWindowRect = bundle.getParcelable("CROP_WINDOW_RECT"); - if (cropWindowRect != null && (cropWindowRect.width() > 0 || cropWindowRect.height() > 0)) { - mRestoreCropWindowRect = cropWindowRect; - } - - mCropOverlayView.setCropShape(CropShape.valueOf(bundle.getString("CROP_SHAPE"))); - - mAutoZoomEnabled = bundle.getBoolean("CROP_AUTO_ZOOM_ENABLED"); - mMaxZoom = bundle.getInt("CROP_MAX_ZOOM"); - - mFlipHorizontally = bundle.getBoolean("CROP_FLIP_HORIZONTALLY"); - mFlipVertically = bundle.getBoolean("CROP_FLIP_VERTICALLY"); - } - - super.onRestoreInstanceState(bundle.getParcelable("instanceState")); - } else { - super.onRestoreInstanceState(state); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - if (mBitmap != null) { - - // Bypasses a baffling bug when used within a ScrollView, where heightSize is set to 0. - if (heightSize == 0) { - heightSize = mBitmap.getHeight(); - } - - int desiredWidth; - int desiredHeight; - - double viewToBitmapWidthRatio = Double.POSITIVE_INFINITY; - double viewToBitmapHeightRatio = Double.POSITIVE_INFINITY; - - // Checks if either width or height needs to be fixed - if (widthSize < mBitmap.getWidth()) { - viewToBitmapWidthRatio = (double) widthSize / (double) mBitmap.getWidth(); - } - if (heightSize < mBitmap.getHeight()) { - viewToBitmapHeightRatio = (double) heightSize / (double) mBitmap.getHeight(); - } - - // If either needs to be fixed, choose smallest ratio and calculate from there - if (viewToBitmapWidthRatio != Double.POSITIVE_INFINITY - || viewToBitmapHeightRatio != Double.POSITIVE_INFINITY) { - if (viewToBitmapWidthRatio <= viewToBitmapHeightRatio) { - desiredWidth = widthSize; - desiredHeight = (int) (mBitmap.getHeight() * viewToBitmapWidthRatio); - } else { - desiredHeight = heightSize; - desiredWidth = (int) (mBitmap.getWidth() * viewToBitmapHeightRatio); - } - } else { - // Otherwise, the picture is within frame layout bounds. Desired width is simply picture - // size - desiredWidth = mBitmap.getWidth(); - desiredHeight = mBitmap.getHeight(); - } - - int width = getOnMeasureSpec(widthMode, widthSize, desiredWidth); - int height = getOnMeasureSpec(heightMode, heightSize, desiredHeight); - - mLayoutWidth = width; - mLayoutHeight = height; - - setMeasuredDimension(mLayoutWidth, mLayoutHeight); - - } else { - setMeasuredDimension(widthSize, heightSize); - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - - super.onLayout(changed, l, t, r, b); - - if (mLayoutWidth > 0 && mLayoutHeight > 0) { - // Gets original parameters, and creates the new parameters - ViewGroup.LayoutParams origParams = this.getLayoutParams(); - origParams.width = mLayoutWidth; - origParams.height = mLayoutHeight; - setLayoutParams(origParams); - - if (mBitmap != null) { - applyImageMatrix(r - l, b - t, true, false); - - // after state restore we want to restore the window crop, possible only after widget size - // is known - if (mRestoreCropWindowRect != null) { - if (mRestoreDegreesRotated != mInitialDegreesRotated) { - mDegreesRotated = mRestoreDegreesRotated; - applyImageMatrix(r - l, b - t, true, false); - } - mImageMatrix.mapRect(mRestoreCropWindowRect); - mCropOverlayView.setCropWindowRect(mRestoreCropWindowRect); - handleCropWindowChanged(false, false); - mCropOverlayView.fixCurrentCropWindowRect(); - mRestoreCropWindowRect = null; - } else if (mSizeChanged) { - mSizeChanged = false; - handleCropWindowChanged(false, false); - } - } else { - updateImageBounds(true); - } - } else { - updateImageBounds(true); - } - } - - /** - * Detect size change to handle auto-zoom using {@link #handleCropWindowChanged(boolean, boolean)} - * in {@link #layout(int, int, int, int)}. - */ - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mSizeChanged = oldw > 0 && oldh > 0; - } - - /** - * Handle crop window change to:
- * 1. Execute auto-zoom-in/out depending on the area covered of cropping window relative to the - * available view area.
- * 2. Slide the zoomed sub-area if the cropping window is outside of the visible view sub-area. - *
- * - * @param inProgress is the crop window change is still in progress by the user - * @param animate if to animate the change to the image matrix, or set it directly - */ - private void handleCropWindowChanged(boolean inProgress, boolean animate) { - int width = getWidth(); - int height = getHeight(); - if (mBitmap != null && width > 0 && height > 0) { - - RectF cropRect = mCropOverlayView.getCropWindowRect(); - if (inProgress) { - if (cropRect.left < 0 - || cropRect.top < 0 - || cropRect.right > width - || cropRect.bottom > height) { - applyImageMatrix(width, height, false, false); - } - } else if (mAutoZoomEnabled || mZoom > 1) { - float newZoom = 0; - // keep the cropping window covered area to 50%-65% of zoomed sub-area - if (mZoom < mMaxZoom - && cropRect.width() < width * 0.5f - && cropRect.height() < height * 0.5f) { - newZoom = - Math.min( - mMaxZoom, - Math.min( - width / (cropRect.width() / mZoom / 0.64f), - height / (cropRect.height() / mZoom / 0.64f))); - } - if (mZoom > 1 && (cropRect.width() > width * 0.65f || cropRect.height() > height * 0.65f)) { - newZoom = - Math.max( - 1, - Math.min( - width / (cropRect.width() / mZoom / 0.51f), - height / (cropRect.height() / mZoom / 0.51f))); - } - if (!mAutoZoomEnabled) { - newZoom = 1; - } - - if (newZoom > 0 && newZoom != mZoom) { - if (animate) { - if (mAnimation == null) { - // lazy create animation single instance - mAnimation = new CropImageAnimation(mImageView, mCropOverlayView); - } - // set the state for animation to start from - mAnimation.setStartState(mImagePoints, mImageMatrix); - } - - mZoom = newZoom; - - applyImageMatrix(width, height, true, animate); - } - } - if (mOnSetCropWindowChangeListener != null && !inProgress) { - mOnSetCropWindowChangeListener.onCropWindowChanged(); - } - } - } - - /** - * Apply matrix to handle the image inside the image view. - * - * @param width the width of the image view - * @param height the height of the image view - */ - private void applyImageMatrix(float width, float height, boolean center, boolean animate) { - if (mBitmap != null && width > 0 && height > 0) { - - mImageMatrix.invert(mImageInverseMatrix); - RectF cropRect = mCropOverlayView.getCropWindowRect(); - mImageInverseMatrix.mapRect(cropRect); - - mImageMatrix.reset(); - - // move the image to the center of the image view first so we can manipulate it from there - mImageMatrix.postTranslate( - (width - mBitmap.getWidth()) / 2, (height - mBitmap.getHeight()) / 2); - mapImagePointsByImageMatrix(); - - // rotate the image the required degrees from center of image - if (mDegreesRotated > 0) { - mImageMatrix.postRotate( - mDegreesRotated, - BitmapUtils.getRectCenterX(mImagePoints), - BitmapUtils.getRectCenterY(mImagePoints)); - mapImagePointsByImageMatrix(); - } - - // scale the image to the image view, image rect transformed to know new width/height - float scale = - Math.min( - width / BitmapUtils.getRectWidth(mImagePoints), - height / BitmapUtils.getRectHeight(mImagePoints)); - if (mScaleType == ScaleType.FIT_CENTER - || (mScaleType == ScaleType.CENTER_INSIDE && scale < 1) - || (scale > 1 && mAutoZoomEnabled)) { - mImageMatrix.postScale( - scale, - scale, - BitmapUtils.getRectCenterX(mImagePoints), - BitmapUtils.getRectCenterY(mImagePoints)); - mapImagePointsByImageMatrix(); - } - - // scale by the current zoom level - float scaleX = mFlipHorizontally ? -mZoom : mZoom; - float scaleY = mFlipVertically ? -mZoom : mZoom; - mImageMatrix.postScale( - scaleX, - scaleY, - BitmapUtils.getRectCenterX(mImagePoints), - BitmapUtils.getRectCenterY(mImagePoints)); - mapImagePointsByImageMatrix(); - - mImageMatrix.mapRect(cropRect); - - if (center) { - // set the zoomed area to be as to the center of cropping window as possible - mZoomOffsetX = - width > BitmapUtils.getRectWidth(mImagePoints) - ? 0 - : Math.max( - Math.min( - width / 2 - cropRect.centerX(), -BitmapUtils.getRectLeft(mImagePoints)), - getWidth() - BitmapUtils.getRectRight(mImagePoints)) - / scaleX; - mZoomOffsetY = - height > BitmapUtils.getRectHeight(mImagePoints) - ? 0 - : Math.max( - Math.min( - height / 2 - cropRect.centerY(), -BitmapUtils.getRectTop(mImagePoints)), - getHeight() - BitmapUtils.getRectBottom(mImagePoints)) - / scaleY; - } else { - // adjust the zoomed area so the crop window rectangle will be inside the area in case it - // was moved outside - mZoomOffsetX = - Math.min(Math.max(mZoomOffsetX * scaleX, -cropRect.left), -cropRect.right + width) - / scaleX; - mZoomOffsetY = - Math.min(Math.max(mZoomOffsetY * scaleY, -cropRect.top), -cropRect.bottom + height) - / scaleY; - } - - // apply to zoom offset translate and update the crop rectangle to offset correctly - mImageMatrix.postTranslate(mZoomOffsetX * scaleX, mZoomOffsetY * scaleY); - cropRect.offset(mZoomOffsetX * scaleX, mZoomOffsetY * scaleY); - mCropOverlayView.setCropWindowRect(cropRect); - mapImagePointsByImageMatrix(); - mCropOverlayView.invalidate(); - - // set matrix to apply - if (animate) { - // set the state for animation to end in, start animation now - mAnimation.setEndState(mImagePoints, mImageMatrix); - mImageView.startAnimation(mAnimation); - } else { - mImageView.setImageMatrix(mImageMatrix); - } - - // update the image rectangle in the crop overlay - updateImageBounds(false); - } - } - - /** - * Adjust the given image rectangle by image transformation matrix to know the final rectangle of - * the image.
- * To get the proper rectangle it must be first reset to original image rectangle. - */ - private void mapImagePointsByImageMatrix() { - mImagePoints[0] = 0; - mImagePoints[1] = 0; - mImagePoints[2] = mBitmap.getWidth(); - mImagePoints[3] = 0; - mImagePoints[4] = mBitmap.getWidth(); - mImagePoints[5] = mBitmap.getHeight(); - mImagePoints[6] = 0; - mImagePoints[7] = mBitmap.getHeight(); - mImageMatrix.mapPoints(mImagePoints); - mScaleImagePoints[0] = 0; - mScaleImagePoints[1] = 0; - mScaleImagePoints[2] = 100; - mScaleImagePoints[3] = 0; - mScaleImagePoints[4] = 100; - mScaleImagePoints[5] = 100; - mScaleImagePoints[6] = 0; - mScaleImagePoints[7] = 100; - mImageMatrix.mapPoints(mScaleImagePoints); - } - - /** - * Set visibility of crop overlay to hide it when there is no image or specificly set by client. - */ - private void setCropOverlayVisibility() { - if (mCropOverlayView != null) { - mCropOverlayView.setVisibility(mShowCropOverlay && mBitmap != null ? VISIBLE : INVISIBLE); - } - } - - /** - * Set visibility of progress bar when async loading/cropping is in process and show is enabled. - */ - private void setProgressBarVisibility() { - boolean visible = - mShowProgressBar - && (mBitmap == null && mBitmapLoadingWorkerTask != null - || mBitmapCroppingWorkerTask != null); - mProgressBar.setVisibility(visible ? VISIBLE : INVISIBLE); - } - - /** - * Update the scale factor between the actual image bitmap and the shown image.
- */ - private void updateImageBounds(boolean clear) { - if (mBitmap != null && !clear) { - - // Get the scale factor between the actual Bitmap dimensions and the displayed dimensions for - // width/height. - float scaleFactorWidth = - 100f * mLoadedSampleSize / BitmapUtils.getRectWidth(mScaleImagePoints); - float scaleFactorHeight = - 100f * mLoadedSampleSize / BitmapUtils.getRectHeight(mScaleImagePoints); - mCropOverlayView.setCropWindowLimits( - getWidth(), getHeight(), scaleFactorWidth, scaleFactorHeight); - } - - // set the bitmap rectangle and update the crop window after scale factor is set - mCropOverlayView.setBounds(clear ? null : mImagePoints, getWidth(), getHeight()); - } - // endregion - - // region: Inner class: CropShape - - /** - * The possible cropping area shape.
- * To set square/circle crop shape set aspect ratio to 1:1. - */ - public enum CropShape { - RECTANGLE, - OVAL - } - // endregion - - // region: Inner class: ScaleType - - /** - * Options for scaling the bounds of cropping image to the bounds of Crop Image View.
- * Note: Some options are affected by auto-zoom, if enabled. - */ - public enum ScaleType { - - /** - * Scale the image uniformly (maintain the image's aspect ratio) to fit in crop image view.
- * The largest dimension will be equals to crop image view and the second dimension will be - * smaller. - */ - FIT_CENTER, - - /** - * Center the image in the view, but perform no scaling.
- * Note: If auto-zoom is enabled and the source image is smaller than crop image view then it - * will be scaled uniformly to fit the crop image view. - */ - CENTER, - - /** - * Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width - * and height) of the image will be equal to or larger than the corresponding dimension - * of the view (minus padding).
- * The image is then centered in the view. - */ - CENTER_CROP, - - /** - * Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width - * and height) of the image will be equal to or less than the corresponding dimension of - * the view (minus padding).
- * The image is then centered in the view.
- * Note: If auto-zoom is enabled and the source image is smaller than crop image view then it - * will be scaled uniformly to fit the crop image view. - */ - CENTER_INSIDE - } - // endregion - - // region: Inner class: Guidelines - - /** - * The possible guidelines showing types. - */ - public enum Guidelines { - /** - * Never show - */ - OFF, - - /** - * Show when crop move action is live - */ - ON_TOUCH, - - /** - * Always show - */ - ON - } - // endregion - - // region: Inner class: RequestSizeOptions - - /** - * Possible options for handling requested width/height for cropping. - */ - public enum RequestSizeOptions { - - /** - * No resize/sampling is done unless required for memory management (OOM). - */ - NONE, - - /** - * Only sample the image during loading (if image set using URI) so the smallest of the image - * dimensions will be between the requested size and x2 requested size.
- * NOTE: resulting image will not be exactly requested width/height see: Loading - * Large Bitmaps Efficiently. - */ - SAMPLING, - - /** - * Resize the image uniformly (maintain the image's aspect ratio) so that both dimensions (width - * and height) of the image will be equal to or less than the corresponding requested - * dimension.
- * If the image is smaller than the requested size it will NOT change. - */ - RESIZE_INSIDE, - - /** - * Resize the image uniformly (maintain the image's aspect ratio) to fit in the given - * width/height.
- * The largest dimension will be equals to the requested and the second dimension will be - * smaller.
- * If the image is smaller than the requested size it will enlarge it. - */ - RESIZE_FIT, - - /** - * Resize the image to fit exactly in the given width/height.
- * This resize method does NOT preserve aspect ratio.
- * If the image is smaller than the requested size it will enlarge it. - */ - RESIZE_EXACT - } - // endregion - - // region: Inner class: OnSetImageUriCompleteListener - - /** - * Interface definition for a callback to be invoked when the crop overlay is released. - */ - public interface OnSetCropOverlayReleasedListener { - - /** - * Called when the crop overlay changed listener is called and inProgress is false. - * - * @param rect The rect coordinates of the cropped overlay - */ - void onCropOverlayReleased(Rect rect); - } - - /** - * Interface definition for a callback to be invoked when the crop overlay is released. - */ - public interface OnSetCropOverlayMovedListener { - - /** - * Called when the crop overlay is moved - * - * @param rect The rect coordinates of the cropped overlay - */ - void onCropOverlayMoved(Rect rect); - } - - /** - * Interface definition for a callback to be invoked when the crop overlay is released. - */ - public interface OnSetCropWindowChangeListener { - - /** - * Called when the crop window is changed - */ - void onCropWindowChanged(); - } - - /** - * Interface definition for a callback to be invoked when image async loading is complete. - */ - public interface OnSetImageUriCompleteListener { - - /** - * Called when a crop image view has completed loading image for cropping.
- * If loading failed error parameter will contain the error. - * - * @param view The crop image view that loading of image was complete. - * @param uri the URI of the image that was loading - * @param error if error occurred during loading will contain the error, otherwise null. - */ - void onSetImageUriComplete(CropImageView view, Uri uri, Exception error); - } - // endregion - - // region: Inner class: OnGetCroppedImageCompleteListener - - /** - * Interface definition for a callback to be invoked when image async crop is complete. - */ - public interface OnCropImageCompleteListener { - - /** - * Called when a crop image view has completed cropping image.
- * Result object contains the cropped bitmap, saved cropped image uri, crop points data or the - * error occured during cropping. - * - * @param view The crop image view that cropping of image was complete. - * @param result the crop image result data (with cropped image or error) - */ - void onCropImageComplete(CropImageView view, CropResult result); - } - // endregion - - // region: Inner class: ActivityResult - - /** - * Result data of crop image. - */ - public static class CropResult { - - /** - * The image bitmap of the original image loaded for cropping.
- * Null if uri used to load image or activity result is used. - */ - private final Bitmap mOriginalBitmap; - - /** - * The Android uri of the original image loaded for cropping.
- * Null if bitmap was used to load image. - */ - private final Uri mOriginalUri; - - /** - * The cropped image bitmap result.
- * Null if save cropped image was executed, no output requested or failure. - */ - private final Bitmap mBitmap; - - /** - * The Android uri of the saved cropped image result.
- * Null if get cropped image was executed, no output requested or failure. - */ - private final Uri mUri; - - /** - * The error that failed the loading/cropping (null if successful) - */ - private final Exception mError; - - /** - * The 4 points of the cropping window in the source image - */ - private final float[] mCropPoints; - - /** - * The rectangle of the cropping window in the source image - */ - private final Rect mCropRect; - - /** - * The rectangle of the source image dimensions - */ - private final Rect mWholeImageRect; - - /** - * The final rotation of the cropped image relative to source - */ - private final int mRotation; - - /** - * sample size used creating the crop bitmap to lower its size - */ - private final int mSampleSize; - - CropResult( - Bitmap originalBitmap, - Uri originalUri, - Bitmap bitmap, - Uri uri, - Exception error, - float[] cropPoints, - Rect cropRect, - Rect wholeImageRect, - int rotation, - int sampleSize) { - mOriginalBitmap = originalBitmap; - mOriginalUri = originalUri; - mBitmap = bitmap; - mUri = uri; - mError = error; - mCropPoints = cropPoints; - mCropRect = cropRect; - mWholeImageRect = wholeImageRect; - mRotation = rotation; - mSampleSize = sampleSize; - } - - /** - * The image bitmap of the original image loaded for cropping.
- * Null if uri used to load image or activity result is used. - */ - public Bitmap getOriginalBitmap() { - return mOriginalBitmap; - } - - /** - * The Android uri of the original image loaded for cropping.
- * Null if bitmap was used to load image. - */ - public Uri getOriginalUri() { - return mOriginalUri; - } - - /** - * Is the result is success or error. - */ - public boolean isSuccessful() { - return mError == null; - } - - /** - * The cropped image bitmap result.
- * Null if save cropped image was executed, no output requested or failure. - */ - public Bitmap getBitmap() { - return mBitmap; - } - - /** - * The Android uri of the saved cropped image result Null if get cropped image was executed, no - * output requested or failure. - */ - public Uri getUri() { - return mUri; - } - - /** - * The error that failed the loading/cropping (null if successful) - */ - public Exception getError() { - return mError; - } - - /** - * The 4 points of the cropping window in the source image - */ - public float[] getCropPoints() { - return mCropPoints; - } - - /** - * The rectangle of the cropping window in the source image - */ - public Rect getCropRect() { - return mCropRect; - } - - /** - * The rectangle of the source image dimensions - */ - public Rect getWholeImageRect() { - return mWholeImageRect; - } - - /** - * The final rotation of the cropped image relative to source - */ - public int getRotation() { - return mRotation; - } - - /** - * sample size used creating the crop bitmap to lower its size - */ - public int getSampleSize() { - return mSampleSize; - } - } - // endregion -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropOverlayView.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropOverlayView.java deleted file mode 100644 index 2a774fe6..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropOverlayView.java +++ /dev/null @@ -1,1118 +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.theartofdev.edmodo.cropper; - -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Region; -import android.os.Build; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; - -import java.util.Arrays; - -/** - * A custom View representing the crop window and the shaded background outside the crop window. - */ -public class CropOverlayView extends View { - - // region: Fields and Consts - - /** - * Handler from crop window stuff, moving and knowing possition. - */ - private final CropWindowHandler mCropWindowHandler = new CropWindowHandler(); - /** - * Rectangle used for drawing - */ - private final RectF mDrawRect = new RectF(); - /** - * Used for oval crop window shape or non-straight rotation drawing. - */ - private final Path mPath = new Path(); - /** - * The bounding box around the Bitmap that we are cropping. - */ - private final float[] mBoundsPoints = new float[8]; - /** - * The bounding box around the Bitmap that we are cropping. - */ - private final RectF mCalcBounds = new RectF(); - /** - * the initial crop window rectangle to set - */ - private final Rect mInitialCropWindowRect = new Rect(); - /** - * Gesture detector used for multi touch box scaling - */ - private ScaleGestureDetector mScaleDetector; - /** - * Boolean to see if multi touch is enabled for the crop rectangle - */ - private boolean mMultiTouchEnabled; - /** - * Listener to publicj crop window changes - */ - private CropWindowChangeListener mCropWindowChangeListener; - /** - * The Paint used to draw the white rectangle around the crop area. - */ - private Paint mBorderPaint; - /** - * The Paint used to draw the corners of the Border - */ - private Paint mBorderCornerPaint; - /** - * The Paint used to draw the guidelines within the crop area when pressed. - */ - private Paint mGuidelinePaint; - /** - * The Paint used to darken the surrounding areas outside the crop area. - */ - private Paint mBackgroundPaint; - /** - * The bounding image view width used to know the crop overlay is at view edges. - */ - private int mViewWidth; - /** - * The bounding image view height used to know the crop overlay is at view edges. - */ - private int mViewHeight; - /** - * The offset to draw the border corener from the border - */ - private float mBorderCornerOffset; - /** - * the length of the border corner to draw - */ - private float mBorderCornerLength; - /** - * The initial crop window padding from image borders - */ - private float mInitialCropWindowPaddingRatio; - /** - * The radius of the touch zone (in pixels) around a given Handle. - */ - private float mTouchRadius; - /** - * An edge of the crop window will snap to the corresponding edge of a specified bounding box when - * the crop window edge is less than or equal to this distance (in pixels) away from the bounding - * box edge. - */ - private float mSnapRadius; - /** - * The Handle that is currently pressed; null if no Handle is pressed. - */ - private CropWindowMoveHandler mMoveHandler; - /** - * Flag indicating if the crop area should always be a certain aspect ratio (indicated by - * mTargetAspectRatio). - */ - private boolean mFixAspectRatio; - /** - * save the current aspect ratio of the image - */ - private int mAspectRatioX; - /** - * save the current aspect ratio of the image - */ - private int mAspectRatioY; - /** - * The aspect ratio that the crop area should maintain; this variable is only used when - * mMaintainAspectRatio is true. - */ - private float mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY; - /** - * Instance variables for customizable attributes - */ - private CropImageView.Guidelines mGuidelines; - /** - * The shape of the cropping area - rectangle/circular. - */ - private CropImageView.CropShape mCropShape; - /** - * Whether the Crop View has been initialized for the first time - */ - private boolean initializedCropWindow; - - /** - * Used to set back LayerType after changing to software. - */ - private Integer mOriginalLayerType; - // endregion - - public CropOverlayView(Context context) { - this(context, null); - } - - public CropOverlayView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - /** - * Creates the Paint object for drawing. - */ - private static Paint getNewPaint(int color) { - Paint paint = new Paint(); - paint.setColor(color); - return paint; - } - - /** - * Creates the Paint object for given thickness and color, if thickness < 0 return null. - */ - private static Paint getNewPaintOrNull(float thickness, int color) { - if (thickness > 0) { - Paint borderPaint = new Paint(); - borderPaint.setColor(color); - borderPaint.setStrokeWidth(thickness); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setAntiAlias(true); - return borderPaint; - } else { - return null; - } - } - - /** - * Set the crop window change listener. - */ - public void setCropWindowChangeListener(CropWindowChangeListener listener) { - mCropWindowChangeListener = listener; - } - - /** - * Get the left/top/right/bottom coordinates of the crop window. - */ - public RectF getCropWindowRect() { - return mCropWindowHandler.getRect(); - } - - /** - * Set the left/top/right/bottom coordinates of the crop window. - */ - public void setCropWindowRect(RectF rect) { - mCropWindowHandler.setRect(rect); - } - - /** - * Fix the current crop window rectangle if it is outside of cropping image or view bounds. - */ - public void fixCurrentCropWindowRect() { - RectF rect = getCropWindowRect(); - fixCropWindowRectByRules(rect); - mCropWindowHandler.setRect(rect); - } - - /** - * Informs the CropOverlayView of the image's position relative to the ImageView. This is - * necessary to call in order to draw the crop window. - * - * @param boundsPoints the image's bounding points - * @param viewWidth The bounding image view width. - * @param viewHeight The bounding image view height. - */ - public void setBounds(float[] boundsPoints, int viewWidth, int viewHeight) { - if (boundsPoints == null || !Arrays.equals(mBoundsPoints, boundsPoints)) { - if (boundsPoints == null) { - Arrays.fill(mBoundsPoints, 0); - } else { - System.arraycopy(boundsPoints, 0, mBoundsPoints, 0, boundsPoints.length); - } - mViewWidth = viewWidth; - mViewHeight = viewHeight; - RectF cropRect = mCropWindowHandler.getRect(); - if (cropRect.width() == 0 || cropRect.height() == 0) { - initCropWindow(); - } - } - } - - /** - * Resets the crop overlay view. - */ - public void resetCropOverlayView() { - if (initializedCropWindow) { - setCropWindowRect(BitmapUtils.EMPTY_RECT_F); - initCropWindow(); - invalidate(); - } - } - - /** - * The shape of the cropping area - rectangle/circular. - */ - public CropImageView.CropShape getCropShape() { - return mCropShape; - } - - /** - * The shape of the cropping area - rectangle/circular. - */ - public void setCropShape(CropImageView.CropShape cropShape) { - if (mCropShape != cropShape) { - mCropShape = cropShape; - if (Build.VERSION.SDK_INT <= 17) { - if (mCropShape == CropImageView.CropShape.OVAL) { - mOriginalLayerType = getLayerType(); - if (mOriginalLayerType != View.LAYER_TYPE_SOFTWARE) { - // TURN off hardware acceleration - setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } else { - mOriginalLayerType = null; - } - } else if (mOriginalLayerType != null) { - // return hardware acceleration back - setLayerType(mOriginalLayerType, null); - mOriginalLayerType = null; - } - } - invalidate(); - } - } - - /** - * Get the current guidelines option set. - */ - public CropImageView.Guidelines getGuidelines() { - return mGuidelines; - } - - /** - * Sets the guidelines for the CropOverlayView to be either on, off, or to show when resizing the - * application. - */ - public void setGuidelines(CropImageView.Guidelines guidelines) { - if (mGuidelines != guidelines) { - mGuidelines = guidelines; - if (initializedCropWindow) { - invalidate(); - } - } - } - - /** - * whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows it to - * be changed. - */ - public boolean isFixAspectRatio() { - return mFixAspectRatio; - } - - /** - * Sets whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows - * it to be changed. - */ - public void setFixedAspectRatio(boolean fixAspectRatio) { - if (mFixAspectRatio != fixAspectRatio) { - mFixAspectRatio = fixAspectRatio; - if (initializedCropWindow) { - initCropWindow(); - invalidate(); - } - } - } - - /** - * the X value of the aspect ratio; - */ - public int getAspectRatioX() { - return mAspectRatioX; - } - - /** - * Sets the X value of the aspect ratio; is defaulted to 1. - */ - public void setAspectRatioX(int aspectRatioX) { - if (aspectRatioX <= 0) { - throw new IllegalArgumentException( - "Cannot set aspect ratio value to a number less than or equal to 0."); - } else if (mAspectRatioX != aspectRatioX) { - mAspectRatioX = aspectRatioX; - mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY; - - if (initializedCropWindow) { - initCropWindow(); - invalidate(); - } - } - } - - /** - * the Y value of the aspect ratio; - */ - public int getAspectRatioY() { - return mAspectRatioY; - } - - /** - * Sets the Y value of the aspect ratio; is defaulted to 1. - * - * @param aspectRatioY int that specifies the new Y value of the aspect ratio - */ - public void setAspectRatioY(int aspectRatioY) { - if (aspectRatioY <= 0) { - throw new IllegalArgumentException( - "Cannot set aspect ratio value to a number less than or equal to 0."); - } else if (mAspectRatioY != aspectRatioY) { - mAspectRatioY = aspectRatioY; - mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY; - - if (initializedCropWindow) { - initCropWindow(); - invalidate(); - } - } - } - - /** - * An edge of the crop window will snap to the corresponding edge of a specified bounding box when - * the crop window edge is less than or equal to this distance (in pixels) away from the bounding - * box edge. (default: 3) - */ - public void setSnapRadius(float snapRadius) { - mSnapRadius = snapRadius; - } - - /** - * Set multi touch functionality to enabled/disabled. - */ - public boolean setMultiTouchEnabled(boolean multiTouchEnabled) { - if (mMultiTouchEnabled != multiTouchEnabled) { - mMultiTouchEnabled = multiTouchEnabled; - if (mMultiTouchEnabled && mScaleDetector == null) { - mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener()); - } - return true; - } - return false; - } - - /** - * the min size the resulting cropping image is allowed to be, affects the cropping window limits - * (in pixels).
- */ - public void setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) { - mCropWindowHandler.setMinCropResultSize(minCropResultWidth, minCropResultHeight); - } - - /** - * the max size the resulting cropping image is allowed to be, affects the cropping window limits - * (in pixels).
- */ - public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) { - mCropWindowHandler.setMaxCropResultSize(maxCropResultWidth, maxCropResultHeight); - } - - /** - * set the max width/height and scale factor of the shown image to original image to scale the - * limits appropriately. - */ - public void setCropWindowLimits( - float maxWidth, float maxHeight, float scaleFactorWidth, float scaleFactorHeight) { - mCropWindowHandler.setCropWindowLimits( - maxWidth, maxHeight, scaleFactorWidth, scaleFactorHeight); - } - - /** - * Get crop window initial rectangle. - */ - public Rect getInitialCropWindowRect() { - return mInitialCropWindowRect; - } - - /** - * Set crop window initial rectangle to be used instead of default. - */ - public void setInitialCropWindowRect(Rect rect) { - mInitialCropWindowRect.set(rect != null ? rect : BitmapUtils.EMPTY_RECT); - if (initializedCropWindow) { - initCropWindow(); - invalidate(); - callOnCropWindowChanged(false); - } - } - - // region: Private methods - - /** - * Reset crop window to initial rectangle. - */ - public void resetCropWindowRect() { - if (initializedCropWindow) { - initCropWindow(); - invalidate(); - callOnCropWindowChanged(false); - } - } - - /** - * Sets all initial values, but does not call initCropWindow to reset the views.
- * Used once at the very start to initialize the attributes. - */ - public void setInitialAttributeValues(CropImageOptions options) { - - mCropWindowHandler.setInitialAttributeValues(options); - - setCropShape(options.cropShape); - - setSnapRadius(options.snapRadius); - - setGuidelines(options.guidelines); - - setFixedAspectRatio(options.fixAspectRatio); - - setAspectRatioX(options.aspectRatioX); - - setAspectRatioY(options.aspectRatioY); - - setMultiTouchEnabled(options.multiTouchEnabled); - - mTouchRadius = options.touchRadius; - - mInitialCropWindowPaddingRatio = options.initialCropWindowPaddingRatio; - - mBorderPaint = getNewPaintOrNull(options.borderLineThickness, options.borderLineColor); - - mBorderCornerOffset = options.borderCornerOffset; - mBorderCornerLength = options.borderCornerLength; - mBorderCornerPaint = - getNewPaintOrNull(options.borderCornerThickness, options.borderCornerColor); - - mGuidelinePaint = getNewPaintOrNull(options.guidelinesThickness, options.guidelinesColor); - - mBackgroundPaint = getNewPaint(options.backgroundColor); - } - - /** - * Set the initial crop window size and position. This is dependent on the size and position of - * the image being cropped. - */ - private void initCropWindow() { - - float leftLimit = Math.max(BitmapUtils.getRectLeft(mBoundsPoints), 0); - float topLimit = Math.max(BitmapUtils.getRectTop(mBoundsPoints), 0); - float rightLimit = Math.min(BitmapUtils.getRectRight(mBoundsPoints), getWidth()); - float bottomLimit = Math.min(BitmapUtils.getRectBottom(mBoundsPoints), getHeight()); - - if (rightLimit <= leftLimit || bottomLimit <= topLimit) { - return; - } - - RectF rect = new RectF(); - - // Tells the attribute functions the crop window has already been initialized - initializedCropWindow = true; - - float horizontalPadding = mInitialCropWindowPaddingRatio * (rightLimit - leftLimit); - float verticalPadding = mInitialCropWindowPaddingRatio * (bottomLimit - topLimit); - - if (mInitialCropWindowRect.width() > 0 && mInitialCropWindowRect.height() > 0) { - // Get crop window position relative to the displayed image. - rect.left = - leftLimit + mInitialCropWindowRect.left / mCropWindowHandler.getScaleFactorWidth(); - rect.top = topLimit + mInitialCropWindowRect.top / mCropWindowHandler.getScaleFactorHeight(); - rect.right = - rect.left + mInitialCropWindowRect.width() / mCropWindowHandler.getScaleFactorWidth(); - rect.bottom = - rect.top + mInitialCropWindowRect.height() / mCropWindowHandler.getScaleFactorHeight(); - - // Correct for floating point errors. Crop rect boundaries should not exceed the source Bitmap - // bounds. - rect.left = Math.max(leftLimit, rect.left); - rect.top = Math.max(topLimit, rect.top); - rect.right = Math.min(rightLimit, rect.right); - rect.bottom = Math.min(bottomLimit, rect.bottom); - - } else if (mFixAspectRatio && rightLimit > leftLimit && bottomLimit > topLimit) { - - // If the image aspect ratio is wider than the crop aspect ratio, - // then the image height is the determining initial length. Else, vice-versa. - float bitmapAspectRatio = (rightLimit - leftLimit) / (bottomLimit - topLimit); - if (bitmapAspectRatio > mTargetAspectRatio) { - - rect.top = topLimit + verticalPadding; - rect.bottom = bottomLimit - verticalPadding; - - float centerX = getWidth() / 2f; - - // dirty fix for wrong crop overlay aspect ratio when using fixed aspect ratio - mTargetAspectRatio = (float) mAspectRatioX / mAspectRatioY; - - // Limits the aspect ratio to no less than 40 wide or 40 tall - float cropWidth = - Math.max(mCropWindowHandler.getMinCropWidth(), rect.height() * mTargetAspectRatio); - - float halfCropWidth = cropWidth / 2f; - rect.left = centerX - halfCropWidth; - rect.right = centerX + halfCropWidth; - - } else { - - rect.left = leftLimit + horizontalPadding; - rect.right = rightLimit - horizontalPadding; - - float centerY = getHeight() / 2f; - - // Limits the aspect ratio to no less than 40 wide or 40 tall - float cropHeight = - Math.max(mCropWindowHandler.getMinCropHeight(), rect.width() / mTargetAspectRatio); - - float halfCropHeight = cropHeight / 2f; - rect.top = centerY - halfCropHeight; - rect.bottom = centerY + halfCropHeight; - } - } else { - // Initialize crop window to have 10% padding w/ respect to image. - rect.left = leftLimit + horizontalPadding; - rect.top = topLimit + verticalPadding; - rect.right = rightLimit - horizontalPadding; - rect.bottom = bottomLimit - verticalPadding; - } - - fixCropWindowRectByRules(rect); - - mCropWindowHandler.setRect(rect); - } - - /** - * Fix the given rect to fit into bitmap rect and follow min, max and aspect ratio rules. - */ - private void fixCropWindowRectByRules(RectF rect) { - if (rect.width() < mCropWindowHandler.getMinCropWidth()) { - float adj = (mCropWindowHandler.getMinCropWidth() - rect.width()) / 2; - rect.left -= adj; - rect.right += adj; - } - if (rect.height() < mCropWindowHandler.getMinCropHeight()) { - float adj = (mCropWindowHandler.getMinCropHeight() - rect.height()) / 2; - rect.top -= adj; - rect.bottom += adj; - } - if (rect.width() > mCropWindowHandler.getMaxCropWidth()) { - float adj = (rect.width() - mCropWindowHandler.getMaxCropWidth()) / 2; - rect.left += adj; - rect.right -= adj; - } - if (rect.height() > mCropWindowHandler.getMaxCropHeight()) { - float adj = (rect.height() - mCropWindowHandler.getMaxCropHeight()) / 2; - rect.top += adj; - rect.bottom -= adj; - } - - calculateBounds(rect); - if (mCalcBounds.width() > 0 && mCalcBounds.height() > 0) { - float leftLimit = Math.max(mCalcBounds.left, 0); - float topLimit = Math.max(mCalcBounds.top, 0); - float rightLimit = Math.min(mCalcBounds.right, getWidth()); - float bottomLimit = Math.min(mCalcBounds.bottom, getHeight()); - if (rect.left < leftLimit) { - rect.left = leftLimit; - } - if (rect.top < topLimit) { - rect.top = topLimit; - } - if (rect.right > rightLimit) { - rect.right = rightLimit; - } - if (rect.bottom > bottomLimit) { - rect.bottom = bottomLimit; - } - } - if (mFixAspectRatio && Math.abs(rect.width() - rect.height() * mTargetAspectRatio) > 0.1) { - if (rect.width() > rect.height() * mTargetAspectRatio) { - float adj = Math.abs(rect.height() * mTargetAspectRatio - rect.width()) / 2; - rect.left += adj; - rect.right -= adj; - } else { - float adj = Math.abs(rect.width() / mTargetAspectRatio - rect.height()) / 2; - rect.top += adj; - rect.bottom -= adj; - } - } - } - - /** - * Draw crop overview by drawing background over image not in the cripping area, then borders and - * guidelines. - */ - @Override - protected void onDraw(Canvas canvas) { - - super.onDraw(canvas); - - // Draw translucent background for the cropped area. - drawBackground(canvas); - - if (mCropWindowHandler.showGuidelines()) { - // Determines whether guidelines should be drawn or not - if (mGuidelines == CropImageView.Guidelines.ON) { - drawGuidelines(canvas); - } else if (mGuidelines == CropImageView.Guidelines.ON_TOUCH && mMoveHandler != null) { - // Draw only when resizing - drawGuidelines(canvas); - } - } - - drawBorders(canvas); - - drawCorners(canvas); - } - - /** - * Draw shadow background over the image not including the crop area. - */ - private void drawBackground(Canvas canvas) { - - RectF rect = mCropWindowHandler.getRect(); - - float left = Math.max(BitmapUtils.getRectLeft(mBoundsPoints), 0); - float top = Math.max(BitmapUtils.getRectTop(mBoundsPoints), 0); - float right = Math.min(BitmapUtils.getRectRight(mBoundsPoints), getWidth()); - float bottom = Math.min(BitmapUtils.getRectBottom(mBoundsPoints), getHeight()); - - if (mCropShape == CropImageView.CropShape.RECTANGLE) { - if (!isNonStraightAngleRotated() || Build.VERSION.SDK_INT <= 17) { - canvas.drawRect(left, top, right, rect.top, mBackgroundPaint); - canvas.drawRect(left, rect.bottom, right, bottom, mBackgroundPaint); - canvas.drawRect(left, rect.top, rect.left, rect.bottom, mBackgroundPaint); - canvas.drawRect(rect.right, rect.top, right, rect.bottom, mBackgroundPaint); - } else { - mPath.reset(); - mPath.moveTo(mBoundsPoints[0], mBoundsPoints[1]); - mPath.lineTo(mBoundsPoints[2], mBoundsPoints[3]); - mPath.lineTo(mBoundsPoints[4], mBoundsPoints[5]); - mPath.lineTo(mBoundsPoints[6], mBoundsPoints[7]); - mPath.close(); - - canvas.save(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - canvas.clipOutPath(mPath); - } else { - canvas.clipPath(mPath, Region.Op.INTERSECT); - } - canvas.clipRect(rect, Region.Op.XOR); - canvas.drawRect(left, top, right, bottom, mBackgroundPaint); - canvas.restore(); - } - } else { - mPath.reset(); - if (Build.VERSION.SDK_INT <= 17 && mCropShape == CropImageView.CropShape.OVAL) { - mDrawRect.set(rect.left + 2, rect.top + 2, rect.right - 2, rect.bottom - 2); - } else { - mDrawRect.set(rect.left, rect.top, rect.right, rect.bottom); - } - mPath.addOval(mDrawRect, Path.Direction.CW); - canvas.save(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - canvas.clipOutPath(mPath); - } else { - canvas.clipPath(mPath, Region.Op.XOR); - } - canvas.drawRect(left, top, right, bottom, mBackgroundPaint); - canvas.restore(); - } - } - - /** - * Draw 2 veritcal and 2 horizontal guidelines inside the cropping area to split it into 9 equal - * parts. - */ - private void drawGuidelines(Canvas canvas) { - if (mGuidelinePaint != null) { - float sw = mBorderPaint != null ? mBorderPaint.getStrokeWidth() : 0; - RectF rect = mCropWindowHandler.getRect(); - rect.inset(sw, sw); - - float oneThirdCropWidth = rect.width() / 3; - float oneThirdCropHeight = rect.height() / 3; - - if (mCropShape == CropImageView.CropShape.OVAL) { - - float w = rect.width() / 2 - sw; - float h = rect.height() / 2 - sw; - - // Draw vertical guidelines. - float x1 = rect.left + oneThirdCropWidth; - float x2 = rect.right - oneThirdCropWidth; - float yv = (float) (h * Math.sin(Math.acos((w - oneThirdCropWidth) / w))); - canvas.drawLine(x1, rect.top + h - yv, x1, rect.bottom - h + yv, mGuidelinePaint); - canvas.drawLine(x2, rect.top + h - yv, x2, rect.bottom - h + yv, mGuidelinePaint); - - // Draw horizontal guidelines. - float y1 = rect.top + oneThirdCropHeight; - float y2 = rect.bottom - oneThirdCropHeight; - float xv = (float) (w * Math.cos(Math.asin((h - oneThirdCropHeight) / h))); - canvas.drawLine(rect.left + w - xv, y1, rect.right - w + xv, y1, mGuidelinePaint); - canvas.drawLine(rect.left + w - xv, y2, rect.right - w + xv, y2, mGuidelinePaint); - } else { - - // Draw vertical guidelines. - float x1 = rect.left + oneThirdCropWidth; - float x2 = rect.right - oneThirdCropWidth; - canvas.drawLine(x1, rect.top, x1, rect.bottom, mGuidelinePaint); - canvas.drawLine(x2, rect.top, x2, rect.bottom, mGuidelinePaint); - - // Draw horizontal guidelines. - float y1 = rect.top + oneThirdCropHeight; - float y2 = rect.bottom - oneThirdCropHeight; - canvas.drawLine(rect.left, y1, rect.right, y1, mGuidelinePaint); - canvas.drawLine(rect.left, y2, rect.right, y2, mGuidelinePaint); - } - } - } - - /** - * Draw borders of the crop area. - */ - private void drawBorders(Canvas canvas) { - if (mBorderPaint != null) { - float w = mBorderPaint.getStrokeWidth(); - RectF rect = mCropWindowHandler.getRect(); - rect.inset(w / 2, w / 2); - - if (mCropShape == CropImageView.CropShape.RECTANGLE) { - // Draw rectangle crop window border. - canvas.drawRect(rect, mBorderPaint); - } else { - // Draw circular crop window border - canvas.drawOval(rect, mBorderPaint); - } - } - } - - /** - * Draw the corner of crop overlay. - */ - private void drawCorners(Canvas canvas) { - if (mBorderCornerPaint != null) { - - float lineWidth = mBorderPaint != null ? mBorderPaint.getStrokeWidth() : 0; - float cornerWidth = mBorderCornerPaint.getStrokeWidth(); - - // for rectangle crop shape we allow the corners to be offset from the borders - float w = - cornerWidth / 2 - + (mCropShape == CropImageView.CropShape.RECTANGLE ? mBorderCornerOffset : 0); - - RectF rect = mCropWindowHandler.getRect(); - rect.inset(w, w); - - float cornerOffset = (cornerWidth - lineWidth) / 2; - float cornerExtension = cornerWidth / 2 + cornerOffset; - - // Top left - canvas.drawLine( - rect.left - cornerOffset, - rect.top - cornerExtension, - rect.left - cornerOffset, - rect.top + mBorderCornerLength, - mBorderCornerPaint); - canvas.drawLine( - rect.left - cornerExtension, - rect.top - cornerOffset, - rect.left + mBorderCornerLength, - rect.top - cornerOffset, - mBorderCornerPaint); - - // Top right - canvas.drawLine( - rect.right + cornerOffset, - rect.top - cornerExtension, - rect.right + cornerOffset, - rect.top + mBorderCornerLength, - mBorderCornerPaint); - canvas.drawLine( - rect.right + cornerExtension, - rect.top - cornerOffset, - rect.right - mBorderCornerLength, - rect.top - cornerOffset, - mBorderCornerPaint); - - // Bottom left - canvas.drawLine( - rect.left - cornerOffset, - rect.bottom + cornerExtension, - rect.left - cornerOffset, - rect.bottom - mBorderCornerLength, - mBorderCornerPaint); - canvas.drawLine( - rect.left - cornerExtension, - rect.bottom + cornerOffset, - rect.left + mBorderCornerLength, - rect.bottom + cornerOffset, - mBorderCornerPaint); - - // Bottom left - canvas.drawLine( - rect.right + cornerOffset, - rect.bottom + cornerExtension, - rect.right + cornerOffset, - rect.bottom - mBorderCornerLength, - mBorderCornerPaint); - canvas.drawLine( - rect.right + cornerExtension, - rect.bottom + cornerOffset, - rect.right - mBorderCornerLength, - rect.bottom + cornerOffset, - mBorderCornerPaint); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // If this View is not enabled, don't allow for touch interactions. - if (isEnabled()) { - if (mMultiTouchEnabled) { - mScaleDetector.onTouchEvent(event); - } - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - onActionDown(event.getX(), event.getY()); - return true; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - getParent().requestDisallowInterceptTouchEvent(false); - onActionUp(); - return true; - case MotionEvent.ACTION_MOVE: - onActionMove(event.getX(), event.getY()); - getParent().requestDisallowInterceptTouchEvent(true); - return true; - default: - return false; - } - } else { - return false; - } - } - - /** - * On press down start crop window movment depending on the location of the press.
- * if press is far from crop window then no move handler is returned (null). - */ - private void onActionDown(float x, float y) { - mMoveHandler = mCropWindowHandler.getMoveHandler(x, y, mTouchRadius, mCropShape); - if (mMoveHandler != null) { - invalidate(); - } - } - - /** - * Clear move handler starting in {@link #onActionDown(float, float)} if exists. - */ - private void onActionUp() { - if (mMoveHandler != null) { - mMoveHandler = null; - callOnCropWindowChanged(false); - invalidate(); - } - } - - /** - * Handle move of crop window using the move handler created in {@link #onActionDown(float, - * float)}.
- * The move handler will do the proper move/resize of the crop window. - */ - private void onActionMove(float x, float y) { - if (mMoveHandler != null) { - float snapRadius = mSnapRadius; - RectF rect = mCropWindowHandler.getRect(); - - if (calculateBounds(rect)) { - snapRadius = 0; - } - - mMoveHandler.move( - rect, - x, - y, - mCalcBounds, - mViewWidth, - mViewHeight, - snapRadius, - mFixAspectRatio, - mTargetAspectRatio); - mCropWindowHandler.setRect(rect); - callOnCropWindowChanged(true); - invalidate(); - } - } - - /** - * Calculate the bounding rectangle for current crop window, handle non-straight rotation angles. - *
- * If the rotation angle is straight then the bounds rectangle is the bitmap rectangle, otherwsie - * we find the max rectangle that is within the image bounds starting from the crop window - * rectangle. - * - * @param rect the crop window rectangle to start finsing bounded rectangle from - * @return true - non straight rotation in place, false - otherwise. - */ - private boolean calculateBounds(RectF rect) { - - float left = BitmapUtils.getRectLeft(mBoundsPoints); - float top = BitmapUtils.getRectTop(mBoundsPoints); - float right = BitmapUtils.getRectRight(mBoundsPoints); - float bottom = BitmapUtils.getRectBottom(mBoundsPoints); - - if (!isNonStraightAngleRotated()) { - mCalcBounds.set(left, top, right, bottom); - return false; - } else { - float x0 = mBoundsPoints[0]; - float y0 = mBoundsPoints[1]; - float x2 = mBoundsPoints[4]; - float y2 = mBoundsPoints[5]; - float x3 = mBoundsPoints[6]; - float y3 = mBoundsPoints[7]; - - if (mBoundsPoints[7] < mBoundsPoints[1]) { - if (mBoundsPoints[1] < mBoundsPoints[3]) { - x0 = mBoundsPoints[6]; - y0 = mBoundsPoints[7]; - x2 = mBoundsPoints[2]; - y2 = mBoundsPoints[3]; - x3 = mBoundsPoints[4]; - y3 = mBoundsPoints[5]; - } else { - x0 = mBoundsPoints[4]; - y0 = mBoundsPoints[5]; - x2 = mBoundsPoints[0]; - y2 = mBoundsPoints[1]; - x3 = mBoundsPoints[2]; - y3 = mBoundsPoints[3]; - } - } else if (mBoundsPoints[1] > mBoundsPoints[3]) { - x0 = mBoundsPoints[2]; - y0 = mBoundsPoints[3]; - x2 = mBoundsPoints[6]; - y2 = mBoundsPoints[7]; - x3 = mBoundsPoints[0]; - y3 = mBoundsPoints[1]; - } - - float a0 = (y3 - y0) / (x3 - x0); - float a1 = -1f / a0; - float b0 = y0 - a0 * x0; - float b1 = y0 - a1 * x0; - float b2 = y2 - a0 * x2; - float b3 = y2 - a1 * x2; - - float c0 = (rect.centerY() - rect.top) / (rect.centerX() - rect.left); - float c1 = -c0; - float d0 = rect.top - c0 * rect.left; - float d1 = rect.top - c1 * rect.right; - - left = Math.max(left, (d0 - b0) / (a0 - c0) < rect.right ? (d0 - b0) / (a0 - c0) : left); - left = Math.max(left, (d0 - b1) / (a1 - c0) < rect.right ? (d0 - b1) / (a1 - c0) : left); - left = Math.max(left, (d1 - b3) / (a1 - c1) < rect.right ? (d1 - b3) / (a1 - c1) : left); - right = Math.min(right, (d1 - b1) / (a1 - c1) > rect.left ? (d1 - b1) / (a1 - c1) : right); - right = Math.min(right, (d1 - b2) / (a0 - c1) > rect.left ? (d1 - b2) / (a0 - c1) : right); - right = Math.min(right, (d0 - b2) / (a0 - c0) > rect.left ? (d0 - b2) / (a0 - c0) : right); - - top = Math.max(top, Math.max(a0 * left + b0, a1 * right + b1)); - bottom = Math.min(bottom, Math.min(a1 * left + b3, a0 * right + b2)); - - mCalcBounds.left = left; - mCalcBounds.top = top; - mCalcBounds.right = right; - mCalcBounds.bottom = bottom; - return true; - } - } - - /** - * Is the cropping image has been rotated by NOT 0,90,180 or 270 degrees. - */ - private boolean isNonStraightAngleRotated() { - return mBoundsPoints[0] != mBoundsPoints[6] && mBoundsPoints[1] != mBoundsPoints[7]; - } - - /** - * Invoke on crop change listener safe, don't let the app crash on exception. - */ - private void callOnCropWindowChanged(boolean inProgress) { - try { - if (mCropWindowChangeListener != null) { - mCropWindowChangeListener.onCropWindowChanged(inProgress); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - // endregion - - // region: Inner class: CropWindowChangeListener - - /** - * Interface definition for a callback to be invoked when crop window rectangle is changing. - */ - public interface CropWindowChangeListener { - - /** - * Called after a change in crop window rectangle. - * - * @param inProgress is the crop window change operation is still in progress by user touch - */ - void onCropWindowChanged(boolean inProgress); - } - // endregion - - // region: Inner class: ScaleListener - - /** - * Handle scaling the rectangle based on two finger input - */ - private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { - - @Override - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public boolean onScale(ScaleGestureDetector detector) { - RectF rect = mCropWindowHandler.getRect(); - - float x = detector.getFocusX(); - float y = detector.getFocusY(); - float dY = detector.getCurrentSpanY() / 2; - float dX = detector.getCurrentSpanX() / 2; - - float newTop = y - dY; - float newLeft = x - dX; - float newRight = x + dX; - float newBottom = y + dY; - - if (newLeft < newRight - && newTop <= newBottom - && newLeft >= 0 - && newRight <= mCropWindowHandler.getMaxCropWidth() - && newTop >= 0 - && newBottom <= mCropWindowHandler.getMaxCropHeight()) { - - rect.set(newLeft, newTop, newRight, newBottom); - mCropWindowHandler.setRect(rect); - invalidate(); - } - - return true; - } - } - // endregion -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowHandler.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowHandler.java deleted file mode 100644 index d9785531..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowHandler.java +++ /dev/null @@ -1,405 +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.theartofdev.edmodo.cropper; - -import android.graphics.RectF; - -/** - * Handler from crop window stuff, moving and knowing possition. - */ -final class CropWindowHandler { - - // region: Fields and Consts - - /** - * The 4 edges of the crop window defining its coordinates and size - */ - private final RectF mEdges = new RectF(); - - /** - * Rectangle used to return the edges rectangle without ability to change it and without creating - * new all the time. - */ - private final RectF mGetEdges = new RectF(); - - /** - * Minimum width in pixels that the crop window can get. - */ - private float mMinCropWindowWidth; - - /** - * Minimum height in pixels that the crop window can get. - */ - private float mMinCropWindowHeight; - - /** - * Maximum width in pixels that the crop window can CURRENTLY get. - */ - private float mMaxCropWindowWidth; - - /** - * Maximum height in pixels that the crop window can CURRENTLY get. - */ - private float mMaxCropWindowHeight; - - /** - * Minimum width in pixels that the result of cropping an image can get, affects crop window width - * adjusted by width scale factor. - */ - private float mMinCropResultWidth; - - /** - * Minimum height in pixels that the result of cropping an image can get, affects crop window - * height adjusted by height scale factor. - */ - private float mMinCropResultHeight; - - /** - * Maximum width in pixels that the result of cropping an image can get, affects crop window width - * adjusted by width scale factor. - */ - private float mMaxCropResultWidth; - - /** - * Maximum height in pixels that the result of cropping an image can get, affects crop window - * height adjusted by height scale factor. - */ - private float mMaxCropResultHeight; - - /** - * The width scale factor of shown image and actual image - */ - private float mScaleFactorWidth = 1; - - /** - * The height scale factor of shown image and actual image - */ - private float mScaleFactorHeight = 1; - // endregion - - /** - * Determines if the specified coordinate is in the target touch zone for a corner handle. - * - * @param x the x-coordinate of the touch point - * @param y the y-coordinate of the touch point - * @param handleX the x-coordinate of the corner handle - * @param handleY the y-coordinate of the corner handle - * @param targetRadius the target radius in pixels - * @return true if the touch point is in the target touch zone; false otherwise - */ - private static boolean isInCornerTargetZone( - float x, float y, float handleX, float handleY, float targetRadius) { - return Math.abs(x - handleX) <= targetRadius && Math.abs(y - handleY) <= targetRadius; - } - - /** - * Determines if the specified coordinate is in the target touch zone for a horizontal bar handle. - * - * @param x the x-coordinate of the touch point - * @param y the y-coordinate of the touch point - * @param handleXStart the left x-coordinate of the horizontal bar handle - * @param handleXEnd the right x-coordinate of the horizontal bar handle - * @param handleY the y-coordinate of the horizontal bar handle - * @param targetRadius the target radius in pixels - * @return true if the touch point is in the target touch zone; false otherwise - */ - private static boolean isInHorizontalTargetZone( - float x, float y, float handleXStart, float handleXEnd, float handleY, float targetRadius) { - return x > handleXStart && x < handleXEnd && Math.abs(y - handleY) <= targetRadius; - } - - /** - * Determines if the specified coordinate is in the target touch zone for a vertical bar handle. - * - * @param x the x-coordinate of the touch point - * @param y the y-coordinate of the touch point - * @param handleX the x-coordinate of the vertical bar handle - * @param handleYStart the top y-coordinate of the vertical bar handle - * @param handleYEnd the bottom y-coordinate of the vertical bar handle - * @param targetRadius the target radius in pixels - * @return true if the touch point is in the target touch zone; false otherwise - */ - private static boolean isInVerticalTargetZone( - float x, float y, float handleX, float handleYStart, float handleYEnd, float targetRadius) { - return Math.abs(x - handleX) <= targetRadius && y > handleYStart && y < handleYEnd; - } - - /** - * Determines if the specified coordinate falls anywhere inside the given bounds. - * - * @param x the x-coordinate of the touch point - * @param y the y-coordinate of the touch point - * @param left the x-coordinate of the left bound - * @param top the y-coordinate of the top bound - * @param right the x-coordinate of the right bound - * @param bottom the y-coordinate of the bottom bound - * @return true if the touch point is inside the bounding rectangle; false otherwise - */ - private static boolean isInCenterTargetZone( - float x, float y, float left, float top, float right, float bottom) { - return x > left && x < right && y > top && y < bottom; - } - - /** - * Get the left/top/right/bottom coordinates of the crop window. - */ - public RectF getRect() { - mGetEdges.set(mEdges); - return mGetEdges; - } - - /** - * Set the left/top/right/bottom coordinates of the crop window. - */ - public void setRect(RectF rect) { - mEdges.set(rect); - } - - /** - * Minimum width in pixels that the crop window can get. - */ - public float getMinCropWidth() { - return Math.max(mMinCropWindowWidth, mMinCropResultWidth / mScaleFactorWidth); - } - - /** - * Minimum height in pixels that the crop window can get. - */ - public float getMinCropHeight() { - return Math.max(mMinCropWindowHeight, mMinCropResultHeight / mScaleFactorHeight); - } - - /** - * Maximum width in pixels that the crop window can get. - */ - public float getMaxCropWidth() { - return Math.min(mMaxCropWindowWidth, mMaxCropResultWidth / mScaleFactorWidth); - } - - /** - * Maximum height in pixels that the crop window can get. - */ - public float getMaxCropHeight() { - return Math.min(mMaxCropWindowHeight, mMaxCropResultHeight / mScaleFactorHeight); - } - - /** - * get the scale factor (on width) of the showen image to original image. - */ - public float getScaleFactorWidth() { - return mScaleFactorWidth; - } - - /** - * get the scale factor (on height) of the showen image to original image. - */ - public float getScaleFactorHeight() { - return mScaleFactorHeight; - } - - /** - * the min size the resulting cropping image is allowed to be, affects the cropping window limits - * (in pixels).
- */ - public void setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) { - mMinCropResultWidth = minCropResultWidth; - mMinCropResultHeight = minCropResultHeight; - } - - /** - * the max size the resulting cropping image is allowed to be, affects the cropping window limits - * (in pixels).
- */ - public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) { - mMaxCropResultWidth = maxCropResultWidth; - mMaxCropResultHeight = maxCropResultHeight; - } - - // region: Private methods - - /** - * set the max width/height and scale factor of the showen image to original image to scale the - * limits appropriately. - */ - public void setCropWindowLimits( - float maxWidth, float maxHeight, float scaleFactorWidth, float scaleFactorHeight) { - mMaxCropWindowWidth = maxWidth; - mMaxCropWindowHeight = maxHeight; - mScaleFactorWidth = scaleFactorWidth; - mScaleFactorHeight = scaleFactorHeight; - } - - /** - * Set the variables to be used during crop window handling. - */ - public void setInitialAttributeValues(CropImageOptions options) { - mMinCropWindowWidth = options.minCropWindowWidth; - mMinCropWindowHeight = options.minCropWindowHeight; - mMinCropResultWidth = options.minCropResultWidth; - mMinCropResultHeight = options.minCropResultHeight; - mMaxCropResultWidth = options.maxCropResultWidth; - mMaxCropResultHeight = options.maxCropResultHeight; - } - - /** - * Indicates whether the crop window is small enough that the guidelines should be shown. Public - * because this function is also used to determine if the center handle should be focused. - * - * @return boolean Whether the guidelines should be shown or not - */ - public boolean showGuidelines() { - return !(mEdges.width() < 100 || mEdges.height() < 100); - } - - /** - * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding - * box, and the touch radius. - * - * @param x the x-coordinate of the touch point - * @param y the y-coordinate of the touch point - * @param targetRadius the target radius in pixels - * @return the Handle that was pressed; null if no Handle was pressed - */ - public CropWindowMoveHandler getMoveHandler( - float x, float y, float targetRadius, CropImageView.CropShape cropShape) { - CropWindowMoveHandler.Type type = - cropShape == CropImageView.CropShape.OVAL - ? getOvalPressedMoveType(x, y) - : getRectanglePressedMoveType(x, y, targetRadius); - return type != null ? new CropWindowMoveHandler(type, this, x, y) : null; - } - - /** - * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding - * box, and the touch radius. - * - * @param x the x-coordinate of the touch point - * @param y the y-coordinate of the touch point - * @param targetRadius the target radius in pixels - * @return the Handle that was pressed; null if no Handle was pressed - */ - private CropWindowMoveHandler.Type getRectanglePressedMoveType( - float x, float y, float targetRadius) { - CropWindowMoveHandler.Type moveType = null; - - // Note: corner-handles take precedence, then side-handles, then center. - if (CropWindowHandler.isInCornerTargetZone(x, y, mEdges.left, mEdges.top, targetRadius)) { - moveType = CropWindowMoveHandler.Type.TOP_LEFT; - } else if (CropWindowHandler.isInCornerTargetZone( - x, y, mEdges.right, mEdges.top, targetRadius)) { - moveType = CropWindowMoveHandler.Type.TOP_RIGHT; - } else if (CropWindowHandler.isInCornerTargetZone( - x, y, mEdges.left, mEdges.bottom, targetRadius)) { - moveType = CropWindowMoveHandler.Type.BOTTOM_LEFT; - } else if (CropWindowHandler.isInCornerTargetZone( - x, y, mEdges.right, mEdges.bottom, targetRadius)) { - moveType = CropWindowMoveHandler.Type.BOTTOM_RIGHT; - } else if (CropWindowHandler.isInCenterTargetZone( - x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) - && focusCenter()) { - moveType = CropWindowMoveHandler.Type.CENTER; - } else if (CropWindowHandler.isInHorizontalTargetZone( - x, y, mEdges.left, mEdges.right, mEdges.top, targetRadius)) { - moveType = CropWindowMoveHandler.Type.TOP; - } else if (CropWindowHandler.isInHorizontalTargetZone( - x, y, mEdges.left, mEdges.right, mEdges.bottom, targetRadius)) { - moveType = CropWindowMoveHandler.Type.BOTTOM; - } else if (CropWindowHandler.isInVerticalTargetZone( - x, y, mEdges.left, mEdges.top, mEdges.bottom, targetRadius)) { - moveType = CropWindowMoveHandler.Type.LEFT; - } else if (CropWindowHandler.isInVerticalTargetZone( - x, y, mEdges.right, mEdges.top, mEdges.bottom, targetRadius)) { - moveType = CropWindowMoveHandler.Type.RIGHT; - } else if (CropWindowHandler.isInCenterTargetZone( - x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom) - && !focusCenter()) { - moveType = CropWindowMoveHandler.Type.CENTER; - } - - return moveType; - } - - /** - * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding - * box/oval, and the touch radius. - * - * @param x the x-coordinate of the touch point - * @param y the y-coordinate of the touch point - * @return the Handle that was pressed; null if no Handle was pressed - */ - private CropWindowMoveHandler.Type getOvalPressedMoveType(float x, float y) { - - /* - Use a 6x6 grid system divided into 9 "handles", with the center the biggest region. While - this is not perfect, it's a good quick-to-ship approach. - - TL T T T T TR - L C C C C R - L C C C C R - L C C C C R - L C C C C R - BL B B B B BR - */ - - float cellLength = mEdges.width() / 6; - float leftCenter = mEdges.left + cellLength; - float rightCenter = mEdges.left + (5 * cellLength); - - float cellHeight = mEdges.height() / 6; - float topCenter = mEdges.top + cellHeight; - float bottomCenter = mEdges.top + 5 * cellHeight; - - CropWindowMoveHandler.Type moveType; - if (x < leftCenter) { - if (y < topCenter) { - moveType = CropWindowMoveHandler.Type.TOP_LEFT; - } else if (y < bottomCenter) { - moveType = CropWindowMoveHandler.Type.LEFT; - } else { - moveType = CropWindowMoveHandler.Type.BOTTOM_LEFT; - } - } else if (x < rightCenter) { - if (y < topCenter) { - moveType = CropWindowMoveHandler.Type.TOP; - } else if (y < bottomCenter) { - moveType = CropWindowMoveHandler.Type.CENTER; - } else { - moveType = CropWindowMoveHandler.Type.BOTTOM; - } - } else { - if (y < topCenter) { - moveType = CropWindowMoveHandler.Type.TOP_RIGHT; - } else if (y < bottomCenter) { - moveType = CropWindowMoveHandler.Type.RIGHT; - } else { - moveType = CropWindowMoveHandler.Type.BOTTOM_RIGHT; - } - } - - return moveType; - } - - /** - * Determines if the cropper should focus on the center handle or the side handles. If it is a - * small image, focus on the center handle so the user can move it. If it is a large image, focus - * on the side handles so user can grab them. Corresponds to the appearance of the - * RuleOfThirdsGuidelines. - * - * @return true if it is small enough such that it should focus on the center; less than - * show_guidelines limit - */ - private boolean focusCenter() { - return !showGuidelines(); - } - // endregion -} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowMoveHandler.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowMoveHandler.java deleted file mode 100644 index 0f1eaab4..00000000 --- a/cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowMoveHandler.java +++ /dev/null @@ -1,786 +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.theartofdev.edmodo.cropper; - -import android.graphics.Matrix; -import android.graphics.PointF; -import android.graphics.RectF; - -/** - * Handler to update crop window edges by the move type - Horizontal, Vertical, Corner or Center. - *
- */ -final class CropWindowMoveHandler { - - // region: Fields and Consts - - /** - * Matrix used for rectangle rotation handling - */ - private static final Matrix MATRIX = new Matrix(); - - /** - * Minimum width in pixels that the crop window can get. - */ - private final float mMinCropWidth; - - /** - * Minimum width in pixels that the crop window can get. - */ - private final float mMinCropHeight; - - /** - * Maximum height in pixels that the crop window can get. - */ - private final float mMaxCropWidth; - - /** - * Maximum height in pixels that the crop window can get. - */ - private final float mMaxCropHeight; - - /** - * The type of crop window move that is handled. - */ - private final Type mType; - - /** - * Holds the x and y offset between the exact touch location and the exact handle location that is - * activated. There may be an offset because we allow for some leeway (specified by mHandleRadius) - * in activating a handle. However, we want to maintain these offset values while the handle is - * being dragged so that the handle doesn't jump. - */ - private final PointF mTouchOffset = new PointF(); - // 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 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 - */ - public CropWindowMoveHandler( - Type type, CropWindowHandler cropWindowHandler, float touchX, float touchY) { - mType = type; - mMinCropWidth = cropWindowHandler.getMinCropWidth(); - mMinCropHeight = cropWindowHandler.getMinCropHeight(); - mMaxCropWidth = cropWindowHandler.getMaxCropWidth(); - mMaxCropHeight = cropWindowHandler.getMaxCropHeight(); - calculateTouchOffset(cropWindowHandler.getRect(), touchX, touchY); - } - - /** - * Calculates the aspect ratio given a rectangle. - */ - private static float calculateAspectRatio(float left, float top, float right, float bottom) { - return (right - left) / (bottom - top); - } - - // region: Private methods - - /** - * Updates the crop window by change in the toch location.
- * Move type handled by this instance, as initialized in creation, affects how the change in toch - * location changes the crop window position and size.
- * After the crop window position/size is changed by toch move it may result in values that - * vialate contraints: outside the bounds of the shown bitmap, smaller/larger than min/max size or - * missmatch in aspect ratio. So a series of fixes is executed on "secondary" edges to adjust it - * by the "primary" edge movement.
- * Primary is the edge directly affected by move type, secondary is the other edge.
- * The crop window is changed by directly setting the Edge coordinates. - * - * @param x the new x-coordinate of this handle - * @param y the new y-coordinate of this handle - * @param bounds the bounding rectangle of the image - * @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 - * @param aspectRatio the aspect ratio to maintain - */ - public void move( - RectF rect, - float x, - float y, - RectF bounds, - int viewWidth, - int viewHeight, - float snapMargin, - boolean fixedAspectRatio, - float aspectRatio) { - - // Adjust the coordinates for the finger position's offset (i.e. the - // distance from the initial touch to the precise handle location). - // We want to maintain the initial touch's distance to the pressed - // handle so that the crop window size does not "jump". - float adjX = x + mTouchOffset.x; - float adjY = y + mTouchOffset.y; - - if (mType == Type.CENTER) { - moveCenter(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin); - } else { - if (fixedAspectRatio) { - moveSizeWithFixedAspectRatio( - rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin, aspectRatio); - } else { - moveSizeWithFreeAspectRatio(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin); - } - } - } - - /** - * Calculates the offset of the touch point from the precise location of the specified handle.
- * Save these values in a member variable since we want to maintain this offset as we drag the - * handle. - */ - private void calculateTouchOffset(RectF rect, float touchX, float touchY) { - - float touchOffsetX = 0; - float touchOffsetY = 0; - - // Calculate the offset from the appropriate handle. - switch (mType) { - case TOP_LEFT: - touchOffsetX = rect.left - touchX; - touchOffsetY = rect.top - touchY; - break; - case TOP_RIGHT: - touchOffsetX = rect.right - touchX; - touchOffsetY = rect.top - touchY; - break; - case BOTTOM_LEFT: - touchOffsetX = rect.left - touchX; - touchOffsetY = rect.bottom - touchY; - break; - case BOTTOM_RIGHT: - touchOffsetX = rect.right - touchX; - touchOffsetY = rect.bottom - touchY; - break; - case LEFT: - touchOffsetX = rect.left - touchX; - touchOffsetY = 0; - break; - case TOP: - touchOffsetX = 0; - touchOffsetY = rect.top - touchY; - break; - case RIGHT: - touchOffsetX = rect.right - touchX; - touchOffsetY = 0; - break; - case BOTTOM: - touchOffsetX = 0; - touchOffsetY = rect.bottom - touchY; - break; - case CENTER: - touchOffsetX = rect.centerX() - touchX; - touchOffsetY = rect.centerY() - touchY; - break; - default: - break; - } - - mTouchOffset.x = touchOffsetX; - mTouchOffset.y = touchOffsetY; - } - - /** - * Center move only changes the position of the crop window without changing the size. - */ - private void moveCenter( - RectF rect, float x, float y, RectF bounds, int viewWidth, int viewHeight, float snapRadius) { - float dx = x - rect.centerX(); - float dy = y - rect.centerY(); - if (rect.left + dx < 0 - || rect.right + dx > viewWidth - || rect.left + dx < bounds.left - || rect.right + dx > bounds.right) { - dx /= 1.05f; - mTouchOffset.x -= dx / 2; - } - if (rect.top + dy < 0 - || rect.bottom + dy > viewHeight - || rect.top + dy < bounds.top - || rect.bottom + dy > bounds.bottom) { - dy /= 1.05f; - mTouchOffset.y -= dy / 2; - } - rect.offset(dx, dy); - snapEdgesToBounds(rect, bounds, snapRadius); - } - - /** - * Change the size of the crop window on the required edge (or edges for corner size move) without - * affecting "secondary" edges.
- * Only the primary edge(s) are fixed to stay within limits. - */ - private void moveSizeWithFreeAspectRatio( - RectF rect, float x, float y, RectF bounds, int viewWidth, int viewHeight, float snapMargin) { - switch (mType) { - case TOP_LEFT: - adjustTop(rect, y, bounds, snapMargin, 0, false, false); - adjustLeft(rect, x, bounds, snapMargin, 0, false, false); - break; - case TOP_RIGHT: - adjustTop(rect, y, bounds, snapMargin, 0, false, false); - adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false); - break; - case BOTTOM_LEFT: - adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false); - adjustLeft(rect, x, bounds, snapMargin, 0, false, false); - break; - case BOTTOM_RIGHT: - adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false); - adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false); - break; - case LEFT: - adjustLeft(rect, x, bounds, snapMargin, 0, false, false); - break; - case TOP: - adjustTop(rect, y, bounds, snapMargin, 0, false, false); - break; - case RIGHT: - adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false); - break; - case BOTTOM: - adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false); - break; - default: - break; - } - } - - /** - * Change the size of the crop window on the required "primary" edge WITH affect to relevant - * "secondary" edge via aspect ratio.
- * Example: change in the left edge (primary) will affect top and bottom edges (secondary) to - * preserve the given aspect ratio. - */ - private void moveSizeWithFixedAspectRatio( - RectF rect, - float x, - float y, - RectF bounds, - int viewWidth, - int viewHeight, - float snapMargin, - float aspectRatio) { - switch (mType) { - case TOP_LEFT: - if (calculateAspectRatio(x, y, rect.right, rect.bottom) < aspectRatio) { - adjustTop(rect, y, bounds, snapMargin, aspectRatio, true, false); - adjustLeftByAspectRatio(rect, aspectRatio); - } else { - adjustLeft(rect, x, bounds, snapMargin, aspectRatio, true, false); - adjustTopByAspectRatio(rect, aspectRatio); - } - break; - case TOP_RIGHT: - if (calculateAspectRatio(rect.left, y, x, rect.bottom) < aspectRatio) { - adjustTop(rect, y, bounds, snapMargin, aspectRatio, false, true); - adjustRightByAspectRatio(rect, aspectRatio); - } else { - adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, true, false); - adjustTopByAspectRatio(rect, aspectRatio); - } - break; - case BOTTOM_LEFT: - if (calculateAspectRatio(x, rect.top, rect.right, y) < aspectRatio) { - adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, true, false); - adjustLeftByAspectRatio(rect, aspectRatio); - } else { - adjustLeft(rect, x, bounds, snapMargin, aspectRatio, false, true); - adjustBottomByAspectRatio(rect, aspectRatio); - } - break; - case BOTTOM_RIGHT: - if (calculateAspectRatio(rect.left, rect.top, x, y) < aspectRatio) { - adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, false, true); - adjustRightByAspectRatio(rect, aspectRatio); - } else { - adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, false, true); - adjustBottomByAspectRatio(rect, aspectRatio); - } - break; - case LEFT: - adjustLeft(rect, x, bounds, snapMargin, aspectRatio, true, true); - adjustTopBottomByAspectRatio(rect, bounds, aspectRatio); - break; - case TOP: - adjustTop(rect, y, bounds, snapMargin, aspectRatio, true, true); - adjustLeftRightByAspectRatio(rect, bounds, aspectRatio); - break; - case RIGHT: - adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, true, true); - adjustTopBottomByAspectRatio(rect, bounds, aspectRatio); - break; - case BOTTOM: - adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, true, true); - adjustLeftRightByAspectRatio(rect, bounds, aspectRatio); - break; - default: - break; - } - } - - /** - * Check if edges have gone out of bounds (including snap margin), and fix if needed. - */ - private void snapEdgesToBounds(RectF edges, RectF bounds, float margin) { - if (edges.left < bounds.left + margin) { - edges.offset(bounds.left - edges.left, 0); - } - if (edges.top < bounds.top + margin) { - edges.offset(0, bounds.top - edges.top); - } - if (edges.right > bounds.right - margin) { - edges.offset(bounds.right - edges.right, 0); - } - if (edges.bottom > bounds.bottom - margin) { - edges.offset(0, bounds.bottom - edges.bottom); - } - } - - /** - * Get the resulting x-position of the left edge of the crop window given the handle's position - * and the image's bounding box and snap radius. - * - * @param left the position that the left edge is dragged to - * @param bounds the bounding box of the image that is being cropped - * @param snapMargin the snap distance to the image edge (in pixels) - */ - private void adjustLeft( - RectF rect, - float left, - RectF bounds, - float snapMargin, - float aspectRatio, - boolean topMoves, - boolean bottomMoves) { - - float newLeft = left; - - if (newLeft < 0) { - newLeft /= 1.05f; - mTouchOffset.x -= newLeft / 1.1f; - } - - if (newLeft < bounds.left) { - mTouchOffset.x -= (newLeft - bounds.left) / 2f; - } - - if (newLeft - bounds.left < snapMargin) { - newLeft = bounds.left; - } - - // Checks if the window is too small horizontally - if (rect.right - newLeft < mMinCropWidth) { - newLeft = rect.right - mMinCropWidth; - } - - // Checks if the window is too large horizontally - if (rect.right - newLeft > mMaxCropWidth) { - newLeft = rect.right - mMaxCropWidth; - } - - if (newLeft - bounds.left < snapMargin) { - newLeft = bounds.left; - } - - // check vertical bounds if aspect ratio is in play - if (aspectRatio > 0) { - float newHeight = (rect.right - newLeft) / aspectRatio; - - // Checks if the window is too small vertically - if (newHeight < mMinCropHeight) { - newLeft = Math.max(bounds.left, rect.right - mMinCropHeight * aspectRatio); - newHeight = (rect.right - newLeft) / aspectRatio; - } - - // Checks if the window is too large vertically - if (newHeight > mMaxCropHeight) { - newLeft = Math.max(bounds.left, rect.right - mMaxCropHeight * aspectRatio); - newHeight = (rect.right - newLeft) / aspectRatio; - } - - // if top AND bottom edge moves by aspect ratio check that it is within full height bounds - if (topMoves && bottomMoves) { - newLeft = - Math.max(newLeft, Math.max(bounds.left, rect.right - bounds.height() * aspectRatio)); - } else { - // if top edge moves by aspect ratio check that it is within bounds - if (topMoves && rect.bottom - newHeight < bounds.top) { - newLeft = Math.max(bounds.left, rect.right - (rect.bottom - bounds.top) * aspectRatio); - newHeight = (rect.right - newLeft) / aspectRatio; - } - - // if bottom edge moves by aspect ratio check that it is within bounds - if (bottomMoves && rect.top + newHeight > bounds.bottom) { - newLeft = - Math.max( - newLeft, - Math.max(bounds.left, rect.right - (bounds.bottom - rect.top) * aspectRatio)); - } - } - } - - rect.left = newLeft; - } - - /** - * Get the resulting x-position of the right edge of the crop window given the handle's position - * and the image's bounding box and snap radius. - * - * @param right the position that the right edge is dragged to - * @param bounds the bounding box of the image that is being cropped - * @param viewWidth - * @param snapMargin the snap distance to the image edge (in pixels) - */ - private void adjustRight( - RectF rect, - float right, - RectF bounds, - int viewWidth, - float snapMargin, - float aspectRatio, - boolean topMoves, - boolean bottomMoves) { - - float newRight = right; - - if (newRight > viewWidth) { - newRight = viewWidth + (newRight - viewWidth) / 1.05f; - mTouchOffset.x -= (newRight - viewWidth) / 1.1f; - } - - if (newRight > bounds.right) { - mTouchOffset.x -= (newRight - bounds.right) / 2f; - } - - // If close to the edge - if (bounds.right - newRight < snapMargin) { - newRight = bounds.right; - } - - // Checks if the window is too small horizontally - if (newRight - rect.left < mMinCropWidth) { - newRight = rect.left + mMinCropWidth; - } - - // Checks if the window is too large horizontally - if (newRight - rect.left > mMaxCropWidth) { - newRight = rect.left + mMaxCropWidth; - } - - // If close to the edge - if (bounds.right - newRight < snapMargin) { - newRight = bounds.right; - } - - // check vertical bounds if aspect ratio is in play - if (aspectRatio > 0) { - float newHeight = (newRight - rect.left) / aspectRatio; - - // Checks if the window is too small vertically - if (newHeight < mMinCropHeight) { - newRight = Math.min(bounds.right, rect.left + mMinCropHeight * aspectRatio); - newHeight = (newRight - rect.left) / aspectRatio; - } - - // Checks if the window is too large vertically - if (newHeight > mMaxCropHeight) { - newRight = Math.min(bounds.right, rect.left + mMaxCropHeight * aspectRatio); - newHeight = (newRight - rect.left) / aspectRatio; - } - - // if top AND bottom edge moves by aspect ratio check that it is within full height bounds - if (topMoves && bottomMoves) { - newRight = - Math.min(newRight, Math.min(bounds.right, rect.left + bounds.height() * aspectRatio)); - } else { - // if top edge moves by aspect ratio check that it is within bounds - if (topMoves && rect.bottom - newHeight < bounds.top) { - newRight = Math.min(bounds.right, rect.left + (rect.bottom - bounds.top) * aspectRatio); - newHeight = (newRight - rect.left) / aspectRatio; - } - - // if bottom edge moves by aspect ratio check that it is within bounds - if (bottomMoves && rect.top + newHeight > bounds.bottom) { - newRight = - Math.min( - newRight, - Math.min(bounds.right, rect.left + (bounds.bottom - rect.top) * aspectRatio)); - } - } - } - - rect.right = newRight; - } - - /** - * Get the resulting y-position of the top edge of the crop window given the handle's position and - * the image's bounding box and snap radius. - * - * @param top the x-position that the top edge is dragged to - * @param bounds the bounding box of the image that is being cropped - * @param snapMargin the snap distance to the image edge (in pixels) - */ - private void adjustTop( - RectF rect, - float top, - RectF bounds, - float snapMargin, - float aspectRatio, - boolean leftMoves, - boolean rightMoves) { - - float newTop = top; - - if (newTop < 0) { - newTop /= 1.05f; - mTouchOffset.y -= newTop / 1.1f; - } - - if (newTop < bounds.top) { - mTouchOffset.y -= (newTop - bounds.top) / 2f; - } - - if (newTop - bounds.top < snapMargin) { - newTop = bounds.top; - } - - // Checks if the window is too small vertically - if (rect.bottom - newTop < mMinCropHeight) { - newTop = rect.bottom - mMinCropHeight; - } - - // Checks if the window is too large vertically - if (rect.bottom - newTop > mMaxCropHeight) { - newTop = rect.bottom - mMaxCropHeight; - } - - if (newTop - bounds.top < snapMargin) { - newTop = bounds.top; - } - - // check horizontal bounds if aspect ratio is in play - if (aspectRatio > 0) { - float newWidth = (rect.bottom - newTop) * aspectRatio; - - // Checks if the crop window is too small horizontally due to aspect ratio adjustment - if (newWidth < mMinCropWidth) { - newTop = Math.max(bounds.top, rect.bottom - (mMinCropWidth / aspectRatio)); - newWidth = (rect.bottom - newTop) * aspectRatio; - } - - // Checks if the crop window is too large horizontally due to aspect ratio adjustment - if (newWidth > mMaxCropWidth) { - newTop = Math.max(bounds.top, rect.bottom - (mMaxCropWidth / aspectRatio)); - newWidth = (rect.bottom - newTop) * aspectRatio; - } - - // if left AND right edge moves by aspect ratio check that it is within full width bounds - if (leftMoves && rightMoves) { - newTop = Math.max(newTop, Math.max(bounds.top, rect.bottom - bounds.width() / aspectRatio)); - } else { - // if left edge moves by aspect ratio check that it is within bounds - if (leftMoves && rect.right - newWidth < bounds.left) { - newTop = Math.max(bounds.top, rect.bottom - (rect.right - bounds.left) / aspectRatio); - newWidth = (rect.bottom - newTop) * aspectRatio; - } - - // if right edge moves by aspect ratio check that it is within bounds - if (rightMoves && rect.left + newWidth > bounds.right) { - newTop = - Math.max( - newTop, - Math.max(bounds.top, rect.bottom - (bounds.right - rect.left) / aspectRatio)); - } - } - } - - rect.top = newTop; - } - - /** - * Get the resulting y-position of the bottom edge of the crop window given the handle's position - * and the image's bounding box and snap radius. - * - * @param bottom the position that the bottom edge is dragged to - * @param bounds the bounding box of the image that is being cropped - * @param viewHeight - * @param snapMargin the snap distance to the image edge (in pixels) - */ - private void adjustBottom( - RectF rect, - float bottom, - RectF bounds, - int viewHeight, - float snapMargin, - float aspectRatio, - boolean leftMoves, - boolean rightMoves) { - - float newBottom = bottom; - - if (newBottom > viewHeight) { - newBottom = viewHeight + (newBottom - viewHeight) / 1.05f; - mTouchOffset.y -= (newBottom - viewHeight) / 1.1f; - } - - if (newBottom > bounds.bottom) { - mTouchOffset.y -= (newBottom - bounds.bottom) / 2f; - } - - if (bounds.bottom - newBottom < snapMargin) { - newBottom = bounds.bottom; - } - - // Checks if the window is too small vertically - if (newBottom - rect.top < mMinCropHeight) { - newBottom = rect.top + mMinCropHeight; - } - - // Checks if the window is too small vertically - if (newBottom - rect.top > mMaxCropHeight) { - newBottom = rect.top + mMaxCropHeight; - } - - if (bounds.bottom - newBottom < snapMargin) { - newBottom = bounds.bottom; - } - - // check horizontal bounds if aspect ratio is in play - if (aspectRatio > 0) { - float newWidth = (newBottom - rect.top) * aspectRatio; - - // Checks if the window is too small horizontally - if (newWidth < mMinCropWidth) { - newBottom = Math.min(bounds.bottom, rect.top + mMinCropWidth / aspectRatio); - newWidth = (newBottom - rect.top) * aspectRatio; - } - - // Checks if the window is too large horizontally - if (newWidth > mMaxCropWidth) { - newBottom = Math.min(bounds.bottom, rect.top + mMaxCropWidth / aspectRatio); - newWidth = (newBottom - rect.top) * aspectRatio; - } - - // if left AND right edge moves by aspect ratio check that it is within full width bounds - if (leftMoves && rightMoves) { - newBottom = - Math.min(newBottom, Math.min(bounds.bottom, rect.top + bounds.width() / aspectRatio)); - } else { - // if left edge moves by aspect ratio check that it is within bounds - if (leftMoves && rect.right - newWidth < bounds.left) { - newBottom = Math.min(bounds.bottom, rect.top + (rect.right - bounds.left) / aspectRatio); - newWidth = (newBottom - rect.top) * aspectRatio; - } - - // if right edge moves by aspect ratio check that it is within bounds - if (rightMoves && rect.left + newWidth > bounds.right) { - newBottom = - Math.min( - newBottom, - Math.min(bounds.bottom, rect.top + (bounds.right - rect.left) / aspectRatio)); - } - } - } - - rect.bottom = newBottom; - } - - /** - * Adjust left edge by current crop window height and the given aspect ratio, the right edge - * remains in possition while the left adjusts to keep aspect ratio to the height. - */ - private void adjustLeftByAspectRatio(RectF rect, float aspectRatio) { - rect.left = rect.right - rect.height() * aspectRatio; - } - - /** - * Adjust top edge by current crop window width and the given aspect ratio, the bottom edge - * remains in possition while the top adjusts to keep aspect ratio to the width. - */ - private void adjustTopByAspectRatio(RectF rect, float aspectRatio) { - rect.top = rect.bottom - rect.width() / aspectRatio; - } - - /** - * Adjust right edge by current crop window height and the given aspect ratio, the left edge - * remains in possition while the left adjusts to keep aspect ratio to the height. - */ - private void adjustRightByAspectRatio(RectF rect, float aspectRatio) { - rect.right = rect.left + rect.height() * aspectRatio; - } - - /** - * Adjust bottom edge by current crop window width and the given aspect ratio, the top edge - * remains in possition while the top adjusts to keep aspect ratio to the width. - */ - private void adjustBottomByAspectRatio(RectF rect, float aspectRatio) { - rect.bottom = rect.top + rect.width() / aspectRatio; - } - - /** - * Adjust left and right edges by current crop window height and the given aspect ratio, both - * right and left edges adjusts equally relative to center to keep aspect ratio to the height. - */ - private void adjustLeftRightByAspectRatio(RectF rect, RectF bounds, float aspectRatio) { - rect.inset((rect.width() - rect.height() * aspectRatio) / 2, 0); - if (rect.left < bounds.left) { - rect.offset(bounds.left - rect.left, 0); - } - if (rect.right > bounds.right) { - rect.offset(bounds.right - rect.right, 0); - } - } - - /** - * Adjust top and bottom edges by current crop window width and the given aspect ratio, both top - * and bottom edges adjusts equally relative to center to keep aspect ratio to the width. - */ - private void adjustTopBottomByAspectRatio(RectF rect, RectF bounds, float aspectRatio) { - rect.inset(0, (rect.height() - rect.width() / aspectRatio) / 2); - if (rect.top < bounds.top) { - rect.offset(0, bounds.top - rect.top); - } - if (rect.bottom > bounds.bottom) { - rect.offset(0, bounds.bottom - rect.bottom); - } - } - // endregion - - // region: Inner class: Type - - /** - * The type of crop window move that is handled. - */ - public enum Type { - TOP_LEFT, - TOP_RIGHT, - BOTTOM_LEFT, - BOTTOM_RIGHT, - LEFT, - TOP, - RIGHT, - BOTTOM, - CENTER - } - // endregion -} diff --git a/cropper/src/main/res/drawable-hdpi/crop_image_menu_flip.png b/cropper/src/main/res/drawable-hdpi/crop_image_menu_flip.png deleted file mode 100644 index 133395df364ccbc3a7e6356252a911d3adbb604b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 262 zcmV+h0r~!kP)r6oV>^ zwVy-JQwj3jDNd!eSn4abduH{DS7Jy0Ua@;&DHBACT8mmSEo#NIs1?(qzAu)vWva!S zK^w+$W>IX6w7JI6i#G6&RHKP;m+~`%>h#dXMlVnYWofaL!_B{m7s({l@QL1$Hvj+t M07*qoM6N<$f&|cTX8-^I diff --git a/cropper/src/main/res/drawable-hdpi/crop_image_menu_rotate_left.png b/cropper/src/main/res/drawable-hdpi/crop_image_menu_rotate_left.png deleted file mode 100644 index e4e26f8c0ec0386544969c3a7ef152baf773daa4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 634 zcmV-=0)_pFP)($=7eh+6zY6DL6sMFgi(sb4tw-2^um5$oXOzaT|~R;e^a5QI7iqB!Vc zq0v@Tj33l%3vPARMJbA=y^kQAgC{w$=k2-NLGrv)?-_nj4sY%an$<>@Z3MCjSlncp zp)(mQ#|-#l0?HQ?P|099K7kJ=rxL)n8Wc=(hx2R^V{%Ft@tguFF(#*UQ|ylq8)e}J z$;5+gt`a3Cu?iCq1)mGNs7#jL3@3Gs1C}#QmAOiF4d$K>vM%TbyNs{4+O+X<|*Ri6t<<0Y}mO*56olryf?y5|OgP`pLWukREQiaBJ9mbA(x5tbxJ~+ROQM_cOPDLb z1h{f6z?EYGt{e++j$G^S1!PXv4{Kw#JD(itv6lV$I3Cc`dYIS zN1E}YiE?bbar@3~m^ETXv+s>drPx^GTYYJ|<gc2Y zoVvL%Hm~lPZrGH%%`o;ppfb@7OZEVjyF%36D*5;BiY=+zGZer+SNCY-&+}N_3ju7= z8Tcx8ESQt?e1LnxnREBXu;dLnFBuMSeJP5K+Lv?S9;toqb6}zLka~+BrZ2?`LbIhK+*@W#6`pSe`ijBR)z4*} HQ$iB}?Eq(9 diff --git a/cropper/src/main/res/drawable-xhdpi/crop_image_menu_rotate_left.png b/cropper/src/main/res/drawable-xhdpi/crop_image_menu_rotate_left.png deleted file mode 100644 index bdfcbca828b68fd778f7d7e6e89ba3c04578d249..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 798 zcmV+(1L6FMP)Fp+FQyA*Y^A}RzeEb`(-L4*zx zl!a7ar1ts`NI`W83Q64+cGI12m*~f^&W`&$&)7ioeU7t-4?8cauHl@;Ya*=H2bthV zF*;al5TwD8qFf};YfuB3^^T3+f>!X1%!*Ru?I~N%Qx@(t-FzjrB+cGl)XR7T;P4#U z#{fy}Su}Zj+vhV#YI3}S=P9J4CP3>6I~>X5fyiS-(~99 z#SbKSPp%#)0+5!}>oS*Zv6Hy011jPt{2_%M|HB@a$#o}jq$s88pqof>${c=`MYqdr z--HBrR6#45K^jU+?{Zmw3?s!TR0B05%~9@hwRNNgZDk(myc%c>sV^-?PO$UQexxxK zP$?GD?nN%yoR956vRIQj=oHdNLi&TxNVS=RZX0|C0F*i?_S-4jfi$6a+t(Wb)$85%ok(|$fbQ!(7*8U7G6MR7RI59= zR%vtg>Q1g9#*zB8K?6vmYU`nMDGN09QArZ1MGaKI4ANr_P8KhaqO4L~n% zfc*|r1r;;(m$P4)Q$+%4ni8EC#TEc&H8>SaBEbdSSLs*GazNu|w{zZ*qr2I#A%#;( z_98FkR(>JD6y@f(V`@n7*8MGnZM?)@l4|q&L)9bzfFZ&Te}5JS8N|Y#MT7Ybt0Tk# z={vogwf&-z9=?&<9B0gL>4k;kxCOIt(;V_*({mFaltAN@d9^K^%OzqNfu?9B*V~Qr zQaXt(3N+0PN|xr+LLm`Gu^d73jL=BIQh%N*;W)RjuL2%&onsXG;WN3$2tWZS00p1` c6o53wZ-Cqgpa}7w>;M1&07*qoM6N<$f}b#H$p8QV diff --git a/cropper/src/main/res/drawable-xhdpi/crop_image_menu_rotate_right.png b/cropper/src/main/res/drawable-xhdpi/crop_image_menu_rotate_right.png deleted file mode 100644 index 6d73012561e6e9418f2d630d8193a4fd87f61049..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 787 zcmV+u1MK{XP)hVL z2{Sx34oW;V3ZC-R7%-k10WMGJqnxL-QOQ#}Si<{!%4Mo~nqPD>$e&D8o2U6g7pIwI zcAk<)h67A6SDuo^t6U?T50o5sFiAMSu~122FGZ@|;B!7C*d-PyDGX4l$Pe`D34&bY z1toaNT0PDp2lQ+7yUBe8y;1 z92~-Ril<1Xh_~>W@rk0Ni%HBB>l6U<6)g%x7N3$lor;Qcxb`RzPkly<0@00+ zIj(5v`L z?Ld9u2od{>k2}mKtaUx2{M2C4wABK^b}zqcD{Yoi|FkFnxBHJ!V=?Qa|K*Z-j9-PN zi>%K(_&WNs-(Ua4LoJ-bYCaJsCz(pW+FvHtU~6_{`I^W8JASig2YTIu+Y|ct@7$FB zh37^Kw}X_#ftjnca}MQuus2(Wir<*Qy`<$}XQE75!xn=Zu8FGKuWV?mRSMbW~IJ&5bnI1XkxVlgtF(Xtwj#A(zt6y?Wd3 u6;^Zc^A?})ToChr`qw^NZ;;6On|-w&d+>q$zt4a{&*16m=d#Wzp$PzI|y9LMqhc5ls|ge+$g#hsF@gl!pZ)Ry!_fiE(qnZ-c2bhFGX2+D}MyQ3GDy@ z#|b91;SrQRQq8B6uS)#3wZ;i1v{EPyJG-md!8pY#uqsY4VKoyF`VgFD7sZ<3<~YHG zm5dvm?r-N;f*T?m1uVk{T!)0%a>fi`!fd(;uAf=%Vb1_3L(-OUrUXoH3&Cx34XbAY zfImXg?%=c$OlYHkKaXaYuyXnU&eE13A$BW&8^MHjg8G>Xcd!QljA={x%LpbsNl+VI z!OrD4Oo?_Qr;K1i7ybsB?FP0PfBTV8Yv802>;ewqZ;K091xL|&+<=7K5=IPQ!fp6F zPNjX=O8mWv7_62d1DNnO{#xzBK1Fw$`H0aL^Sc2o%NV*ZZNo0bL-z_|yc&KB4HLGb z^O$EBwgH{Tl?dj$iesT+HB6%0XczV&x=)ZPZUJkNkoz3n$2MUZ{zSJ1!TmbWjZ$t8 zR*%l(;z(d~@zB-TgSDgk28n3>=sN7dwxN49GT3%>FWG~wAg9(C8EhT8-3G82?-q3Z z$$(|i9Z3dk4!V(Kz|KK8mcr6UF@1Tsf88hX5A^%VI8wci?nQgBHgw-c20MUmy**eR zI*&^tVO8UytF;Hqa0*>pB(P3&Cn>iF6ZW9{EE3ok==LHuVV&qES?HW~4gMaq3!6s) z-D}QaJJ1!VwhI$_(EY<@PFV{WN4F2L4Qs*QPN%SU@pr#{Scb#sCTVuYx)XoLsI(6g zHsP<=5oyD_9x5004|o@AkBz zI!J{(m{7~OV^#+q1b;hq2@{@j$SNa8a9a`AuyVS&)(Pt(_7L26RGGu1;ftdUWQ^$TyY6j5tpCkH9jX8V(@>%POy%$_+7I)pT{{qW!ND$P!->c zah0s1hk_AojQ6;U%uKkWIhS_287c`o!7kP?XQthQE@Kg^$p;*a@8(%nun2KCA#qD# vDJ+GhuoRZUQdkO0VJR$yrLYv1!W`^hu2AEoBTAo?00000NkvXXu0mjfp*bNJ diff --git a/cropper/src/main/res/drawable-xxhdpi/crop_image_menu_rotate_right.png b/cropper/src/main/res/drawable-xxhdpi/crop_image_menu_rotate_right.png deleted file mode 100644 index 796114cc4a028793ed39cb0b2a49175b45e0101c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1165 zcmV;81akX{P)?x|hpY&+N4b)B6ZuJ`9Jc*#7w4m+3l zrg}afi;?svxSvAh6$UD=Fi?4gfyyfkR9<1A@(Kf$R~V?g!a(H}1}d*GPd~4$)7X)YO2@pn)$bVv@=_tcqvoB}po~uxae1K=M>JVYQrQ zqN%LGDtVOv8|}>!teWq!RaM0j)?@U`-4&b^$-|E=L)!5>Fsmfi)8_R>#@F zJOn|F%X%Ei3hYrzjMfluQ4{N|2vDQ}$qH;8BaBuDi((&wVT6Z~tiW!dkLY@+GdkT5 zu@T7%Y$n}AcaT|#0c?;BNQ~XZrRJMj9?d8gT&aX%UA%7u3%TuN4Nus z5$pnY$7Bug|9+>^4eUk2^-yI5>t_`bYu^yA)dj4QbLa)sA_lMmw;?fiKLL8bP;MW# znQ-qT2C(z2Kn(UF;U2UPJAvLMt}}q0WjSKB1q`D1xoz0Bl+b$}i4E&zDPp|0(Hmio zUD#9T1>6)H_6Ij6d_ND+YqksPM6VNxxh-6eB;0ZI4%viF<05)nkrcKay*|q9!B(PI zWKJruc?9S!vj=NMuM0_Or_p=N9<1H`iD+%_ptsK+>=lLA?X?^F;*vOV*xV8mhd!PXxX9($+w9? z#_K0OV0FaDDIJ_a%Pvir$X4@rqB{@@09?|9iERmAJAlEl$%Y9PyiCa|EZ`MpWaHA# zJ#<-vb+aZ**R0Cf!MO>;&eO*9>|KnT$&>t+7}m>fsm)!#vEx fJj}y9%)|Zziy-WXhtC%200000NkvXXu0mjfSN$KD diff --git a/cropper/src/main/res/drawable-xxxhdpi/crop_image_menu_flip.png b/cropper/src/main/res/drawable-xxxhdpi/crop_image_menu_flip.png deleted file mode 100644 index 4200cb86992ebb9a57da74735668d5f43e0d74f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 508 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ)^zFdp`FaSW+oe0$gM)T00q_mA5r z7#8}PHNA8EF~hOYG)(E8!Xv+&qr5Xrx<2}(u2iT@RGVJ>Z|#yFrQ1KM8XFtG7JBdX zzpnFtB8SQZ2PRH9A*!>&BJgwS-3F^a|IW24XCM2^+q`xx9n$JOLJtw4IG^CITEQmYv|<(iq^d;WTH+g*N6a&CiUw3Niv~ zJ~4(WHuC*!@KbC&_p{;E1c&W44*8!VL_HK5S%eV8io@Sok+>iU3`L(JdYRzvg}D65 zMlBRyaAF9kOgO#@*`J&$7w+2s{lBOCyj#sZLzRH3{28)B88i7ae5CnaI8Nl(*vQYm zMfE9*R;CkU?9Zv<8ixF=CaO=_y#8=9e{p`rtYNrV&gQ~Y&6WBKTn_e?ZE)b<*5gsj zxHoLmqXRQPgg&ebxsLQ E0OOCgjQ{`u diff --git a/cropper/src/main/res/drawable-xxxhdpi/crop_image_menu_rotate_left.png b/cropper/src/main/res/drawable-xxxhdpi/crop_image_menu_rotate_left.png deleted file mode 100644 index 1eb686127baefb68d3453e1717cd6eb10702856a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1577 zcmV+^2G;qBP)Nklx)&@AII_UIdgMj#>?o~q^1@YNQMMDR#TP~rjB?+5v5WKdlEemB8nii@={9SrMy6j zI$=`3_-PnnWfGZ_xA8`rrjCtxDMBgcdd|$d2j@WqYn|med!M!TsrCN+1BVx$eca2v z6`#-N^Z9(?$3n#Ygs5&Ic%ZaA9>~Pwf%H5c$i(A;G+P87$jsw`^gJHO%;SOdJRV5T z=Fm%w;6q90R%MF_fTzl}w@RzXJ3I+11W& z=2PJo$UTplX*-WO2XfD2=s0sIbPVL4$I#C_Ma(^rV;)0?8SNg(Igg=D^mY&An8(l+ zYV#YYir>g}YbefNAYmBS$aM!9kk8kkppR3{w${ja|tHqFOblW zQ&|C}b{nfnV5*f-`3of6%E>H&QX9?>n7Ycqr~t*NLNZ(>#{vKeaTd@{`VLVN4WL(P zWH6H9DmWbQOuE|mo476tK=+e`tcZFVzbIzpy`*o7YoJoj0Axwjoqd6TgcANox_+gP zTcG9qT_blO8Lo_P0|5zfHj%D6mq7QDfPh4|uqP0ZP+<5|G{zm!HvoZ&O4%I;gT2t~Q2bjb6ak06@Y}I?y%FF3=Wqy@do= zoQ(m1gf-~eVH2nat*EjMDw$#-Xm0^4tSeqb^BVWPrNUiMoe!4>S*T z4M>D`40X@t9%u>bHbx3)J?d731C;-J5p^GXpwyx)kO%T#F@KTvKl9Q3i{gF{U5d;<`&>iaT020K(Di5} zfX1L}x*ec0+RcyLI^1^DC8)3iBz%s#Hu^f}=}!W6dk|YdbJ6vtbD*W@dfpyTIW4HW zL4{MEDwa0>K3y8e&Y1R8~|cJ6k@GmHef#@YoE>e01_m=m4?4xsB4 zG21{>&^O=R5r?L^E?z}wK-~ofIpC?L9bFeFvJWKGqUyX8o=Sc}-_x#Mhx#faJl)uf zz6Rp1fbOBgJr9NKAYBi;d?j<~KX?W@`R%GBU7Hb?K;1a)l&6%Pr0XgbZh?f6B!F{H zc>1u9^wlD+frOVF@zijM^sR}=e@$ULFck@J7I`$CL;u9ZHqMh-fABaG z=3Min63sHLiTjXz02)LSSO@x^DkMLEig?#DP#4Q7LZSzx-*_%t18UM-c_IxY^x^}$ zat_qRT6!SS2GZ|A4&@YRKcf&Ne}RM;vpACzpdXou*B#z`Gkba}7 zqct#43!AAyY|Z}r$aC09YgRxle8!Wu9~Bi`tdu8M#&2Fol9*6N?Pe(wkrM66ZnG8tO0OAk{!4RNTRK#3uY z31QNPfC*qQAp`|e0U7SSObRqmkujG<5PEt#Jih1)->h|Z&))m2wRc_5_YZ)45*B-} zbB+Yb<#M@PE*D88suQ9xJct~SCy@j4CUQV}A_wG2c*azK!DK#?V~3sb^V%wZvzCyF-Ey}ZP7j?+dqCI1OP<;-Ib zY5bM!$v}Bju~koFpU5Uq0dqM?I3;@;P=Yx$5oXDr1T>lBgkQ2}09CMoT$PMJ&@8T# z%aU;hDq$tL?i!VG1scLpa@`e1Bk=^9z;$xnB}T^e44O#`+0@A|Ss&#W8G*zTsE&5J z+jZ75lQIM$o0=Gk#1rUsI_OSqY-I`w{dI>v&ha1;PoNiR$Gbax&LGWwkauSogv1kQ z0xfveMm+-%#k$u?1|abS8paL0YUHs1H;V9IDv@{s_2M|0OYuGlBmmG+%8@KLki2LB zts`?yj1PDxeV8&N%MGWQ52FC|3YptW84?Jnky0efji4DwGu}2(fBsLV8Yn^n0Ue;P zPac5N6x#)AAX95dAc27P(Z>hRZ~Watn?O}$s==Gb185Ju1M)zc(YAmJIEB7F6d{3t zc2g3V2jDwfK=aVoL_Z`D&<-D`$3qGpfT=csin)ZYyNp8u0d1!+G#>cb2GHB+dk+Z& z)IdSVf1_&CKnc#F>i~IyfHslu^P|b{(e;CApc&|DWfT$sXgvwU=N=}FuJLApcA{%3 zlI?svIq9|pU8_w34WJ!$e{g>QpcR2%FUz@wy4&Ixl@19Jbw+#&7$n|BY2kq6r5UNtgriF;Y~cinL;Qb4CrS8MaatZxi1 z%*NQfa;=KNm207iOZ(HP`!X^>t5Mfz;*i$=@s~+Nf+wGAsQbbUPz|~!MFMClx~7@| zs-OdPnfBJ&{c00s2*Lb5R*XF=xSxOHJ(RkL)SR7Knc#GtI^W)P#*iywa@$)$=?Eq z)YZqk=$mF9sF-GSrFg;u&~PrI?x39?v%HDEiwTo2^}(8^fE zHjuE2%r*0jC8GIA6o5)NLgvzZ6!K|Q$iMlQ=PR{D14tOcRlGXFfL z%5FLVz$B3n=3b+X?sk)nyiA4n9W!t6HMdb@f=CE=)44--HL(TJY%3SgWQs@#drxwi zY!l5inIy{12QP<=1HpL)#|20zqTV>rGKwM#q&bZ)8w0w+^yr>J`t@TiojC{UWF2Kl zw1M=S$l;s<9bsZT9u)PZ9$1n4Za - \ No newline at end of file diff --git a/cropper/src/main/res/layout/crop_image_view.xml b/cropper/src/main/res/layout/crop_image_view.xml deleted file mode 100644 index 50f897b5..00000000 --- a/cropper/src/main/res/layout/crop_image_view.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/cropper/src/main/res/menu/crop_image_menu.xml b/cropper/src/main/res/menu/crop_image_menu.xml deleted file mode 100644 index ff42bc69..00000000 --- a/cropper/src/main/res/menu/crop_image_menu.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cropper/src/main/res/values-ar/strings.xml b/cropper/src/main/res/values-ar/strings.xml deleted file mode 100644 index 4b570989..00000000 --- a/cropper/src/main/res/values-ar/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - أدر عكس اتجاه عقارب الساعة - أدر - قُصّ - اقلب - اقلب أفقيًا - اقلب رأسيًا - - اختر مصدرًا - - إلغاء؛ الأذونات المطلوبة غير ممنوحة - - diff --git a/cropper/src/main/res/values-cs/strings.xml b/cropper/src/main/res/values-cs/strings.xml deleted file mode 100644 index a8cef3c9..00000000 --- a/cropper/src/main/res/values-cs/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Otočit proti směru hodinových ručiček - Otočit - Oříznout - Překlopit - Překlopit vodorovně - Překlopit svisle - - Vybrat zdroj - - Probíhá storno, požadovaná povolení nejsou udělena - - diff --git a/cropper/src/main/res/values-de/strings.xml b/cropper/src/main/res/values-de/strings.xml deleted file mode 100644 index 1ef0f3d7..00000000 --- a/cropper/src/main/res/values-de/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - gegen den Uhrzeigersinn drehen - drehen - zuschneiden - spiegeln - horizontal spiegeln - vertikal spiegeln - - Quelle wählen - - Vorgang wird abgebrochen, benötigte Berechtigungen wurden nicht erteilt. - - diff --git a/cropper/src/main/res/values-es-rGT/strings.xml b/cropper/src/main/res/values-es-rGT/strings.xml deleted file mode 100644 index c9b0864f..00000000 --- a/cropper/src/main/res/values-es-rGT/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - Girar a la izquierda - Girar a la derecha - Cortar - Dar la vuelta - Voltear horizontalmente - Voltear verticalmente - Seleccionar fuente - Cancelando, los permisos requeridos no se otorgaron - \ No newline at end of file diff --git a/cropper/src/main/res/values-es/strings.xml b/cropper/src/main/res/values-es/strings.xml deleted file mode 100644 index a14c240a..00000000 --- a/cropper/src/main/res/values-es/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - Rotar a la izquierda - Rotar a la derecha - Cortar - Dar la vuelta - Voltear horizontalmente - Voltear verticalmente - Seleccionar fuente - Cancelando, los permisos requeridos no han sido otorgados - \ No newline at end of file diff --git a/cropper/src/main/res/values-fa/strings.xml b/cropper/src/main/res/values-fa/strings.xml deleted file mode 100644 index b6745743..00000000 --- a/cropper/src/main/res/values-fa/strings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - چرخش در جهت عقربه های ساعت - چرخش - بریدن (کراپ) - آیینه کردن - آیینه کردن به صورت افقی - آیینه کردن به صورت عمودی - منبع را انتخاب کنید - لغو، مجوزهای مورد نیاز ارائه نشده - \ No newline at end of file diff --git a/cropper/src/main/res/values-fr/strings.xml b/cropper/src/main/res/values-fr/strings.xml deleted file mode 100644 index b0ec3bd0..00000000 --- a/cropper/src/main/res/values-fr/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - Pivoter à gauche - Pivoter à droite - Redimensionner - Retourner - Retourner horizontalement - Retourner verticalement - Sélectionner la source - Annulation, il manque des permissions requises - diff --git a/cropper/src/main/res/values-he/strings.xml b/cropper/src/main/res/values-he/strings.xml deleted file mode 100644 index 8a781d36..00000000 --- a/cropper/src/main/res/values-he/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - סובב נגד כיוון השעון - סובב - חתוך - הפוך - הפוך אופקית - הפוך אנכית - - בחר מקור - - ההרשאות הנדרשות חסרות, מבטל - - diff --git a/cropper/src/main/res/values-hi/strings.xml b/cropper/src/main/res/values-hi/strings.xml deleted file mode 100644 index 8549a125..00000000 --- a/cropper/src/main/res/values-hi/strings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - घड़ी की सुई के विपरीत दिशा में घुमाइए - घुमाएँ - फ़सल - फ्लिप - क्षैतिज फ्लिप - लंबवत फ्लिप करें - सोर्स चुनें - रद्द करना, आवश्यक अनुमतियां नहीं दी गई हैं - \ No newline at end of file diff --git a/cropper/src/main/res/values-id/strings.xml b/cropper/src/main/res/values-id/strings.xml deleted file mode 100644 index 5d0570ea..00000000 --- a/cropper/src/main/res/values-id/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Putar berlawanan arah jarum jam - Putar - Potong - Balik - Balik secara horizontal - Balik secara vertikal - Pilih sumber - Membatalkan, tidak mendapatkan izin yang diperlukan - - \ No newline at end of file diff --git a/cropper/src/main/res/values-in/strings.xml b/cropper/src/main/res/values-in/strings.xml deleted file mode 100644 index 5d0570ea..00000000 --- a/cropper/src/main/res/values-in/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Putar berlawanan arah jarum jam - Putar - Potong - Balik - Balik secara horizontal - Balik secara vertikal - Pilih sumber - Membatalkan, tidak mendapatkan izin yang diperlukan - - \ No newline at end of file diff --git a/cropper/src/main/res/values-it/strings.xml b/cropper/src/main/res/values-it/strings.xml deleted file mode 100644 index fa266660..00000000 --- a/cropper/src/main/res/values-it/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Ruota in senso antiorario - Ruota - Ritaglia - Capovolgi - Capovolgi orizzontalmente - Capovolgi verticalmente - - Seleziona origine - - Annullamento in corso, autorizzazione richieste non concesse - - diff --git a/cropper/src/main/res/values-ja/strings.xml b/cropper/src/main/res/values-ja/strings.xml deleted file mode 100644 index 4ab0ca56..00000000 --- a/cropper/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - 左回転 - 右回転 - 切り取り - 反転 - 左右反転 - 上下反転 - 画像を選択 - 必要な権限がありません、キャンセルしています。 - diff --git a/cropper/src/main/res/values-ko/strings.xml b/cropper/src/main/res/values-ko/strings.xml deleted file mode 100644 index a33967bd..00000000 --- a/cropper/src/main/res/values-ko/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 반시계 회전 - 회전 - 자르기 - 반전 - 좌우반전 - 상하반전 - - 이미지 선택 - - 필수 권한이 없어서 취소합니다. - - diff --git a/cropper/src/main/res/values-ms/strings.xml b/cropper/src/main/res/values-ms/strings.xml deleted file mode 100644 index 000ac2eb..00000000 --- a/cropper/src/main/res/values-ms/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Putar arah berlawanan jam - Putar - Potong - Flip - Flip melintang - Flip menegak - Pilih sumber - Membatal, tidak mendapat kebenaran yang diperlukan - - \ No newline at end of file diff --git a/cropper/src/main/res/values-nb/strings.xml b/cropper/src/main/res/values-nb/strings.xml deleted file mode 100644 index c177d252..00000000 --- a/cropper/src/main/res/values-nb/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Roter teller med urviseren - Roter - Beskjær - Vend - Vend vannrett - Vend loddrett - - Velg kilde - - Avbryter, nødvendige tillatelser er ikke gitt - - diff --git a/cropper/src/main/res/values-nl/strings.xml b/cropper/src/main/res/values-nl/strings.xml deleted file mode 100644 index 6b25e03b..00000000 --- a/cropper/src/main/res/values-nl/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Tegen de klok in draaien - Draaien - Bijsnijden - Spiegelen - Horizontaal spiegelen - Verticaal spiegelen - - Bron selecteren - - Wordt geannuleerd, vereiste machtigingen zijn niet toegekend - - diff --git a/cropper/src/main/res/values-pl/strings.xml b/cropper/src/main/res/values-pl/strings.xml deleted file mode 100644 index 9db22a14..00000000 --- a/cropper/src/main/res/values-pl/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Obróć w lewo - Obróć - Przytnij - Odbij - Odbij poziomo - Odbij pionowo - - Wybierz źródło - - Przerywaniem, potrzebne uprawnienia nie zostały nadane - - diff --git a/cropper/src/main/res/values-pt-rBR/strings.xml b/cropper/src/main/res/values-pt-rBR/strings.xml deleted file mode 100644 index e60b8bfb..00000000 --- a/cropper/src/main/res/values-pt-rBR/strings.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - Girar para a esquerda - Girar para a direita - Cortar - Espelhar - Espelhar na horizontal - Espelhar na vertifcal - - Escolher foto a partir de - - diff --git a/cropper/src/main/res/values-ru-rRU/strings.xml b/cropper/src/main/res/values-ru-rRU/strings.xml deleted file mode 100644 index cd8e62d9..00000000 --- a/cropper/src/main/res/values-ru-rRU/strings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - Повернуть налево - Повернуть направо - Обрезать - Отразить - Отразить по горизонтали - Отразить по вертикали - Выбрать источник - \ No newline at end of file diff --git a/cropper/src/main/res/values-sv/strings.xml b/cropper/src/main/res/values-sv/strings.xml deleted file mode 100644 index ce8beb96..00000000 --- a/cropper/src/main/res/values-sv/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Rotera vänster - Rotera höger - Beskär - Vänd - Vänd horisontellt - Vänd vertikalt - - Välj bild - - Avbryter, nödvändiga behörigheter beviljas inte - - diff --git a/cropper/src/main/res/values-tr/strings.xml b/cropper/src/main/res/values-tr/strings.xml deleted file mode 100644 index ba0f6f4e..00000000 --- a/cropper/src/main/res/values-tr/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - Saat yönünde döndür - döndürmek - ekin - fiske - Yatay olarak çevir - Dikey olarak çevir - Kaynağı seçin - İptal ediliyor, gerekli izinler verilmiyor - - \ No newline at end of file diff --git a/cropper/src/main/res/values-ur/strings.xml b/cropper/src/main/res/values-ur/strings.xml deleted file mode 100644 index b62a923f..00000000 --- a/cropper/src/main/res/values-ur/strings.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - گھڑی وار گھڑی گھومیں - گھمائیں - فصل - پلٹائیں - افقی پلٹائیں - عمودی طور پر پلٹائیں - ذریعہ منتخب کریں - منسوخ کرنا، ضروری اجازت نہیں دی جاتی ہیں - - \ No newline at end of file diff --git a/cropper/src/main/res/values-vi/strings.xml b/cropper/src/main/res/values-vi/strings.xml deleted file mode 100644 index d6301f41..00000000 --- a/cropper/src/main/res/values-vi/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Xoay theo chiều kim đồng hồ - Xoay - Cắt - Lật - Lật theo chiều ngang - Lật theo chiều dọc - - Chọn nguồn - - Đang hủy, các quyền đã yêu cầu không được cấp - - diff --git a/cropper/src/main/res/values-zh-rCN/strings.xml b/cropper/src/main/res/values-zh-rCN/strings.xml deleted file mode 100644 index 7ceb799a..00000000 --- a/cropper/src/main/res/values-zh-rCN/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 逆时针旋转 - 旋转 - 裁切 - 翻转 - 水平翻转 - 垂直翻转 - - 选择来源 - - 取消中,未授予所需权限 - - \ No newline at end of file diff --git a/cropper/src/main/res/values-zh-rTW/strings.xml b/cropper/src/main/res/values-zh-rTW/strings.xml deleted file mode 100644 index 269b9653..00000000 --- a/cropper/src/main/res/values-zh-rTW/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 逆時針旋轉 - 旋轉 - 裁切 - 翻轉 - 水平翻轉 - 垂直翻轉 - - 選擇來源 - - 取消中,未授予所需權限 - - \ No newline at end of file diff --git a/cropper/src/main/res/values-zh/strings.xml b/cropper/src/main/res/values-zh/strings.xml deleted file mode 100644 index b197f48f..00000000 --- a/cropper/src/main/res/values-zh/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - 逆时针旋转 - 旋转 - 裁剪 - 翻转 - 水平翻转 - 垂直翻转 - - 选择来源 - - 正在取消,该操作未获得所需权限。 - - diff --git a/cropper/src/main/res/values/attrs.xml b/cropper/src/main/res/values/attrs.xml deleted file mode 100644 index 756a2c1b..00000000 --- a/cropper/src/main/res/values/attrs.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cropper/src/main/res/values/strings.xml b/cropper/src/main/res/values/strings.xml deleted file mode 100644 index 6dc1fd22..00000000 --- a/cropper/src/main/res/values/strings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - Rotate counter clockwise - Rotate - Crop - Flip - Flip horizontally - Flip vertically - - Select source - - Cancelling, required permissions are not granted - - diff --git a/settings.gradle b/settings.gradle index 48eb5e95..eea69bc0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,4 +3,3 @@ include ':app' include ':autoimageslider' include ':mytransl' include ':ratethisapp' -include ':cropper'