Showing preview only (397K chars total). Download the full file or copy to clipboard to get everything.
Repository: ArthurHub/Android-Image-Cropper
Branch: master
Commit: 5f35aeed8a73
Files: 99
Total size: 367.5 KB
Directory structure:
gitextract_9z8z5ozt/
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── cropper/
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── theartofdev/
│ │ └── edmodo/
│ │ └── cropper/
│ │ ├── BitmapCroppingWorkerTask.java
│ │ ├── BitmapLoadingWorkerTask.java
│ │ ├── BitmapUtils.java
│ │ ├── CropImage.java
│ │ ├── CropImageActivity.java
│ │ ├── CropImageAnimation.java
│ │ ├── CropImageOptions.java
│ │ ├── CropImageView.java
│ │ ├── CropOverlayView.java
│ │ ├── CropWindowHandler.java
│ │ └── CropWindowMoveHandler.java
│ └── res/
│ ├── layout/
│ │ ├── crop_image_activity.xml
│ │ └── crop_image_view.xml
│ ├── menu/
│ │ └── crop_image_menu.xml
│ ├── values/
│ │ ├── attrs.xml
│ │ └── strings.xml
│ ├── values-ar/
│ │ └── strings.xml
│ ├── values-cs/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-es-rGT/
│ │ └── strings.xml
│ ├── values-fa/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-he/
│ │ └── strings.xml
│ ├── values-hi/
│ │ └── strings.xml
│ ├── values-id/
│ │ └── strings.xml
│ ├── values-in/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-ja/
│ │ └── strings.xml
│ ├── values-ko/
│ │ └── strings.xml
│ ├── values-ms/
│ │ └── strings.xml
│ ├── values-nb/
│ │ └── strings.xml
│ ├── values-nl/
│ │ └── strings.xml
│ ├── values-pl/
│ │ └── strings.xml
│ ├── values-pt-rBR/
│ │ └── strings.xml
│ ├── values-ru-rRU/
│ │ └── strings.xml
│ ├── values-sv/
│ │ └── strings.xml
│ ├── values-tr/
│ │ └── strings.xml
│ ├── values-ur/
│ │ └── strings.xml
│ ├── values-vi/
│ │ └── strings.xml
│ ├── values-zh/
│ │ └── strings.xml
│ ├── values-zh-rCN/
│ │ └── strings.xml
│ └── values-zh-rTW/
│ └── strings.xml
├── gradle/
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.sh
├── quick-start/
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── theartofdev/
│ │ └── edmodo/
│ │ └── cropper/
│ │ └── quick/
│ │ └── start/
│ │ └── MainActivity.java
│ └── res/
│ ├── layout/
│ │ └── activity_main.xml
│ └── values/
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── release.sh
├── sample/
│ ├── build.gradle
│ ├── project.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── theartofdev/
│ │ └── edmodo/
│ │ └── cropper/
│ │ └── sample/
│ │ ├── CropDemoPreset.java
│ │ ├── CropImageViewOptions.java
│ │ ├── CropResultActivity.java
│ │ ├── MainActivity.java
│ │ └── MainFragment.java
│ └── res/
│ ├── drawable/
│ │ ├── backdrop.xml
│ │ ├── checkerboard.xml
│ │ └── muted.xml
│ ├── layout/
│ │ ├── activity_crop_result.xml
│ │ ├── activity_main.xml
│ │ ├── fragment_main_customized.xml
│ │ ├── fragment_main_min_max.xml
│ │ ├── fragment_main_oval.xml
│ │ ├── fragment_main_rect.xml
│ │ └── fragment_main_scale_center.xml
│ ├── menu/
│ │ └── main.xml
│ └── values/
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── settings.gradle
└── test/
├── .gitignore
├── build.gradle
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── theartofdev/
│ └── edmodo/
│ └── cropper/
│ └── test/
│ └── MainActivity.java
└── res/
├── layout/
│ └── activity_main.xml
└── values/
├── colors.xml
├── strings.xml
└── styles.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated files
bin/
gen/
out/
# Local configuration file (sdk path, etc)
local.properties
# Mac OS X internal files
.DS_Store
# Eclipse generated files/folders
.metadata/
.settings/
#IntelliJ IDEA
.idea
*.iml
*.ipr
*.iws
out
# Gradle folder
.gradle/
build/
================================================
FILE: .travis.yml
================================================
language: android
jdk: oraclejdk8
sudo: false
android:
components:
- tools
- platform-tools
- build-tools-28.0.3
- android-28
- extra-android-m2repository
script:
- ./gradlew clean build
================================================
FILE: LICENSE.txt
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2016, Arthur Teplitzki 2013, Edmodo, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
Android Image Cropper
=======
# :triangular_flag_on_post: The Project is NOT currently maintained :triangular_flag_on_post:
## Please use **[CanHub's fork](https://github.com/CanHub/Android-Image-Cropper)!**
### Thank everybody for using the library. It was very fun to create and a privilage to help you build awesome apps.
### The same way I took an unmaintained initial implementation from [edmodo](https://github.com/edmodo/cropper), I'm happy to see [CanHub](https://github.com/CanHub/Android-Image-Cropper) taking it now.
### Good luck and happy coding :octocat:
----
----
[](https://android-arsenal.com/details/1/3487)
[](https://travis-ci.org/ArthurHub/Android-Image-Cropper)
**Powerful** (Zoom, Rotation, Multi-Source), **customizable** (Shape, Limits, Style), **optimized** (Async, Sampling, Matrix) and **simple** image cropping library for Android.

## Usage
*For a working implementation, please have a look at the Sample Project*
[See GitHub Wiki for more info.](https://github.com/ArthurHub/Android-Image-Cropper/wiki)
1. Include the library
```
dependencies {
api 'com.theartofdev.edmodo:android-image-cropper:2.8.+'
}
```
Add permissions to manifest
```
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
```
Add this line to your Proguard config file
```
-keep class androidx.appcompat.widget.** { *; }
```
### Using Activity
2. Add `CropImageActivity` into your AndroidManifest.xml
```xml
<activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat"/> <!-- optional (needed if default theme has no action bar) -->
```
3. Start `CropImageActivity` using builder pattern from your activity
```java
// start picker to get image for cropping and then use the image in cropping activity
CropImage.activity()
.setGuidelines(CropImageView.Guidelines.ON)
.start(this);
// start cropping activity for pre-acquired image saved on the device
CropImage.activity(imageUri)
.start(this);
// for fragment (DO NOT use `getActivity()`)
CropImage.activity()
.start(getContext(), this);
```
4. Override `onActivityResult` method in your activity to get crop result
```java
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
CropImage.ActivityResult result = CropImage.getActivityResult(data);
if (resultCode == RESULT_OK) {
Uri resultUri = result.getUri();
} else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
Exception error = result.getError();
}
}
}
```
### Using View
2. Add `CropImageView` into your activity
```xml
<!-- Image Cropper fill the remaining available height -->
<com.theartofdev.edmodo.cropper.CropImageView
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:id="@+id/cropImageView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
```
3. Set image to crop
```java
cropImageView.setImageUriAsync(uri);
// or (prefer using uri for performance and better user experience)
cropImageView.setImageBitmap(bitmap);
```
4. Get cropped image
```java
// subscribe to async event using cropImageView.setOnCropImageCompleteListener(listener)
cropImageView.getCroppedImageAsync();
// or
Bitmap cropped = cropImageView.getCroppedImage();
```
## Features
- Built-in `CropImageActivity`.
- Set cropping image as Bitmap, Resource or Android URI (Gallery, Camera, Dropbox, etc.).
- Image rotation/flipping during cropping.
- Auto zoom-in/out to relevant cropping area.
- Auto rotate bitmap by image Exif data.
- Set result image min/max limits in pixels.
- Set initial crop window size/location.
- Request cropped image resize to specific size.
- Bitmap memory optimization, OOM handling (should never occur)!
- API Level 14.
- More..
## Customizations
- Cropping window shape: Rectangular or Oval (cube/circle by fixing aspect ratio).
- Cropping window aspect ratio: Free, 1:1, 4:3, 16:9 or Custom.
- Guidelines appearance: Off / Always On / Show on Toch.
- Cropping window Border line, border corner and guidelines thickness and color.
- Cropping background color.
For more information, see the [GitHub Wiki](https://github.com/ArthurHub/Android-Image-Cropper/wiki).
## Posts
- [Android cropping image from camera or gallery](http://theartofdev.com/2015/02/15/android-cropping-image-from-camera-or-gallery/)
- [Android Image Cropper async support and custom progress UI](http://theartofdev.com/2016/01/15/android-image-cropper-async-support-and-custom-progress-ui/)
- [Adding auto-zoom feature to Android-Image-Cropper](https://theartofdev.com/2016/04/25/adding-auto-zoom-feature-to-android-image-cropper/)
## Change log
*2.8.0*
- Fix crash on Android O (thx @juliooa)
- Update to support library to AndroidX (thx @mradzinski)
- Handle failure when selecting non image file (thx @uncledoc)
- More translations (thx @jkwiecien, @david-serrano)
*2.7.0*
- Update gradle wrapper to 4.4
- Update support library to 27.1.1 and set is statically! (thx @androideveloper)
- Fix NPE in activity creation by tools (thx @unverbraucht)
- More translations (thx @gwharvey, @dlackty, @JairoGeek, @shaymargolis)
See [full change log](https://github.com/ArthurHub/Android-Image-Cropper/wiki/Change-Log).
## License
Originally forked from [edmodo/cropper](https://github.com/edmodo/cropper).
Copyright 2016, Arthur Teplitzki, 2013, Edmodo, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License.
You may obtain a copy of the License in the LICENSE file, or at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
================================================
FILE: build.gradle
================================================
buildscript {
repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
}
}
ext {
compileSdkVersion = 28
buildToolsVersion = '28.0.3'
androidXLibraryVersion = '1.0.0'
PUBLISH_GROUP_ID = 'com.theartofdev.edmodo'
PUBLISH_ARTIFACT_ID = 'android-image-cropper'
PUBLISH_VERSION = '2.8.0'
// gradlew clean build generateRelease
}
================================================
FILE: cropper/build.gradle
================================================
apply plugin: 'com.android.library'
// https://docs.gradle.org/current/userguide/publishing_maven.html
// http://www.flexlabs.org/2013/06/using-local-aar-android-library-packages-in-gradle-builds
apply plugin: 'maven-publish'
android {
compileSdkVersion rootProject.compileSdkVersion
buildToolsVersion rootProject.buildToolsVersion
defaultConfig {
minSdkVersion 14
targetSdkVersion rootProject.compileSdkVersion
versionCode 1
versionName PUBLISH_VERSION
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
lintOptions {
abortOnError false
}
}
// This configuration is used to publish the library to a local repo while a being forked and modified.
// It should really be set up so that the version are all in line, and set to be a SNAPSHOT.
// The version listed here is a temp hack to allow me to keep working.
android.libraryVariants
publishing {
publications {
maven(MavenPublication) {
groupId PUBLISH_GROUP_ID
artifactId PUBLISH_ARTIFACT_ID
version PUBLISH_VERSION + '-SNAPSHOT'
//artifact bundleRelease
}
}
}
apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle'
dependencies {
api "androidx.appcompat:appcompat:$androidXLibraryVersion"
implementation "androidx.exifinterface:exifinterface:$androidXLibraryVersion"
}
================================================
FILE: cropper/src/main/AndroidManifest.xml
================================================
<manifest
package="com.theartofdev.edmodo.cropper">
</manifest>
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java
================================================
// "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<Void, Void, BitmapCroppingWorkerTask.Result> {
// region: Fields and Consts
/** Use a WeakReference to ensure the ImageView can be garbage collected */
private final WeakReference<CropImageView> 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
}
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java
================================================
// "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<Void, Void, BitmapLoadingWorkerTask.Result> {
// region: Fields and Consts
/** Use a WeakReference to ensure the ImageView can be garbage collected */
private final WeakReference<CropImageView> 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
}
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java
================================================
// "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 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;
import androidx.exifinterface.media.ExifInterface;
/** 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 know the max texture size allowed to be rendered */
private static int mMaxTextureSize;
/** used to save bitmaps during state save and restore so not to reload them. */
static Pair<String, WeakReference<Bitmap>> mStateBitmap;
/**
* Rotate the given image by reading the Exif value of the image (uri).<br>
* If no rotation is required the image will not be rotated.<br>
* 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.<br>
* If no rotation is required the image will not be rotated.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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.<br> */
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.<br>
* 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.<br>
* 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.<br>
* 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
}
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java
================================================
// "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 java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
/**
* Helper to simplify crop image work like starting pick-image acitvity and handling camera/gallery
* intents.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).<br>
* All possible sources are added to the intent chooser.<br>
* 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.<br>
* The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).<br>
* 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<Intent> 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<Intent> 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<Intent> getCameraIntents(
@NonNull Context context, @NonNull PackageManager packageManager) {
List<Intent> allIntents = new ArrayList<>();
// Determine Uri of camera image to save.
Uri outputFileUri = getCaptureImageOutputUri(context);
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
List<ResolveInfo> 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<Intent> getGalleryIntents(
@NonNull PackageManager packageManager, String action, boolean includeDocuments) {
List<Intent> 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<ResolveInfo> 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.<br>
* It is required in Android Marshmellow and above if "CAMERA" permission is requested in the
* manifest.<br>
* See <a
* href="http://stackoverflow.com/questions/32789027/android-m-camera-intent-permission-bug">StackOverflow
* question</a>.
*/
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)}.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* To set square/circle crop shape set aspect ratio to 1:1.<br>
* <i>Default: RECTANGLE</i>
*/
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).<br>
* <i>Default: 3dp</i>
*/
public ActivityBuilder setSnapRadius(float snapRadius) {
mOptions.snapRadius = snapRadius;
return this;
}
/**
* The radius of the touchable area around the handle (in pixels).<br>
* We are basing this value off of the recommended 48dp Rhythm.<br>
* See: http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm<br>
* <i>Default: 48dp</i>
*/
public ActivityBuilder setTouchRadius(float touchRadius) {
mOptions.touchRadius = touchRadius;
return this;
}
/**
* whether the guidelines should be on, off, or only showing when resizing.<br>
* <i>Default: ON_TOUCH</i>
*/
public ActivityBuilder setGuidelines(@NonNull CropImageView.Guidelines guidelines) {
mOptions.guidelines = guidelines;
return this;
}
/**
* The initial scale type of the image in the crop image view<br>
* <i>Default: FIT_CENTER</i>
*/
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.<br>
* <i>default: true, may disable for animation or frame transition.</i>
*/
public ActivityBuilder setShowCropOverlay(boolean showCropOverlay) {
mOptions.showCropOverlay = showCropOverlay;
return this;
}
/**
* if auto-zoom functionality is enabled.<br>
* default: true.
*/
public ActivityBuilder setAutoZoomEnabled(boolean autoZoomEnabled) {
mOptions.autoZoomEnabled = autoZoomEnabled;
return this;
}
/**
* if multi touch functionality is enabled.<br>
* default: true.
*/
public ActivityBuilder setMultiTouchEnabled(boolean multiTouchEnabled) {
mOptions.multiTouchEnabled = multiTouchEnabled;
return this;
}
/**
* The max zoom allowed during cropping.<br>
* <i>Default: 4</i>
*/
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.<br>
* <i>Default: 0.1</i>
*/
public ActivityBuilder setInitialCropWindowPaddingRatio(float initialCropWindowPaddingRatio) {
mOptions.initialCropWindowPaddingRatio = initialCropWindowPaddingRatio;
return this;
}
/**
* whether the width to height aspect ratio should be maintained or free to change.<br>
* <i>Default: false</i>
*/
public ActivityBuilder setFixAspectRatio(boolean fixAspectRatio) {
mOptions.fixAspectRatio = fixAspectRatio;
return this;
}
/**
* the X,Y value of the aspect ratio.<br>
* Also sets fixes aspect ratio to TRUE.<br>
* <i>Default: 1/1</i>
*
* @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).<br>
* <i>Default: 3dp</i>
*/
public ActivityBuilder setBorderLineThickness(float borderLineThickness) {
mOptions.borderLineThickness = borderLineThickness;
return this;
}
/**
* the color of the guidelines lines.<br>
* <i>Default: Color.argb(170, 255, 255, 255)</i>
*/
public ActivityBuilder setBorderLineColor(int borderLineColor) {
mOptions.borderLineColor = borderLineColor;
return this;
}
/**
* thickness of the corner line (in pixels).<br>
* <i>Default: 2dp</i>
*/
public ActivityBuilder setBorderCornerThickness(float borderCornerThickness) {
mOptions.borderCornerThickness = borderCornerThickness;
return this;
}
/**
* the offset of corner line from crop window border (in pixels).<br>
* <i>Default: 5dp</i>
*/
public ActivityBuilder setBorderCornerOffset(float borderCornerOffset) {
mOptions.borderCornerOffset = borderCornerOffset;
return this;
}
/**
* the length of the corner line away from the corner (in pixels).<br>
* <i>Default: 14dp</i>
*/
public ActivityBuilder setBorderCornerLength(float borderCornerLength) {
mOptions.borderCornerLength = borderCornerLength;
return this;
}
/**
* the color of the corner line.<br>
* <i>Default: WHITE</i>
*/
public ActivityBuilder setBorderCornerColor(int borderCornerColor) {
mOptions.borderCornerColor = borderCornerColor;
return this;
}
/**
* the thickness of the guidelines lines (in pixels).<br>
* <i>Default: 1dp</i>
*/
public ActivityBuilder setGuidelinesThickness(float guidelinesThickness) {
mOptions.guidelinesThickness = guidelinesThickness;
return this;
}
/**
* the color of the guidelines lines.<br>
* <i>Default: Color.argb(170, 255, 255, 255)</i>
*/
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.<br>
* <i>Default: Color.argb(119, 0, 0, 0)</i>
*/
public ActivityBuilder setBackgroundColor(int backgroundColor) {
mOptions.backgroundColor = backgroundColor;
return this;
}
/**
* the min size the crop window is allowed to be (in pixels).<br>
* <i>Default: 42dp, 42dp</i>
*/
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).<br>
* <i>Default: 40px, 40px</i>
*/
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).<br>
* <i>Default: 99999, 99999</i>
*/
public ActivityBuilder setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) {
mOptions.maxCropResultWidth = maxCropResultWidth;
mOptions.maxCropResultHeight = maxCropResultHeight;
return this;
}
/**
* the title of the {@link CropImageActivity}.<br>
* <i>Default: ""</i>
*/
public ActivityBuilder setActivityTitle(CharSequence activityTitle) {
mOptions.activityTitle = activityTitle;
return this;
}
/**
* the color to use for action bar items icons.<br>
* <i>Default: NONE</i>
*/
public ActivityBuilder setActivityMenuIconColor(int activityMenuIconColor) {
mOptions.activityMenuIconColor = activityMenuIconColor;
return this;
}
/**
* the Android Uri to save the cropped image to.<br>
* <i>Default: NONE, will create a temp file</i>
*/
public ActivityBuilder setOutputUri(Uri outputUri) {
mOptions.outputUri = outputUri;
return this;
}
/**
* the compression format to use when writting the image.<br>
* <i>Default: JPEG</i>
*/
public ActivityBuilder setOutputCompressFormat(Bitmap.CompressFormat outputCompressFormat) {
mOptions.outputCompressFormat = outputCompressFormat;
return this;
}
/**
* the quility (if applicable) to use when writting the image (0 - 100).<br>
* <i>Default: 90</i>
*/
public ActivityBuilder setOutputCompressQuality(int outputCompressQuality) {
mOptions.outputCompressQuality = outputCompressQuality;
return this;
}
/**
* the size to resize the cropped image to.<br>
* Uses {@link CropImageView.RequestSizeOptions#RESIZE_INSIDE} option.<br>
* <i>Default: 0, 0 - not set, will not resize</i>
*/
public ActivityBuilder setRequestedSize(int reqWidth, int reqHeight) {
return setRequestedSize(reqWidth, reqHeight, CropImageView.RequestSizeOptions.RESIZE_INSIDE);
}
/**
* the size to resize the cropped image to.<br>
* <i>Default: 0, 0 - not set, will not resize</i>
*/
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.<br>
* Used if you want to crop the image manually and need only the crop rectangle and rotation
* data.<br>
* <i>Default: false</i>
*/
public ActivityBuilder setNoOutputImage(boolean noOutputImage) {
mOptions.noOutputImage = noOutputImage;
return this;
}
/**
* the initial rectangle to set on the cropping image after loading.<br>
* <i>Default: NONE - will initialize using initial crop window padding ratio</i>
*/
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).
* <br>
* <i>Default: NONE - will read image exif data</i>
*/
public ActivityBuilder setInitialRotation(int initialRotation) {
mOptions.initialRotation = (initialRotation + 360) % 360;
return this;
}
/**
* if to allow rotation during cropping.<br>
* <i>Default: true</i>
*/
public ActivityBuilder setAllowRotation(boolean allowRotation) {
mOptions.allowRotation = allowRotation;
return this;
}
/**
* if to allow flipping during cropping.<br>
* <i>Default: true</i>
*/
public ActivityBuilder setAllowFlipping(boolean allowFlipping) {
mOptions.allowFlipping = allowFlipping;
return this;
}
/**
* if to allow counter-clockwise rotation during cropping.<br>
* Note: if rotation is disabled this option has no effect.<br>
* <i>Default: false</i>
*/
public ActivityBuilder setAllowCounterRotation(boolean allowCounterRotation) {
mOptions.allowCounterRotation = allowCounterRotation;
return this;
}
/**
* The amount of degreees to rotate clockwise or counter-clockwise (0-360).<br>
* <i>Default: 90</i>
*/
public ActivityBuilder setRotationDegrees(int rotationDegrees) {
mOptions.rotationDegrees = (rotationDegrees + 360) % 360;
return this;
}
/**
* whether the image should be flipped horizontally.<br>
* <i>Default: false</i>
*/
public ActivityBuilder setFlipHorizontally(boolean flipHorizontally) {
mOptions.flipHorizontally = flipHorizontally;
return this;
}
/**
* whether the image should be flipped vertically.<br>
* <i>Default: false</i>
*/
public ActivityBuilder setFlipVertically(boolean flipVertically) {
mOptions.flipVertically = flipVertically;
return this;
}
/**
* optional, set crop menu crop button title.<br>
* <i>Default: null, will use resource string: crop_image_menu_crop</i>
*/
public ActivityBuilder setCropMenuCropButtonTitle(CharSequence title) {
mOptions.cropMenuCropButtonTitle = title;
return this;
}
/**
* Image resource id to use for crop icon instead of text.<br>
* <i>Default: 0</i>
*/
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<ActivityResult> CREATOR =
new Creator<ActivityResult>() {
@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
}
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java
================================================
// "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 androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import java.io.File;
import java.io.IOException;
/**
* Built-in activity for image cropping.<br>
* 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) {
Log.w("AIC", "Failed to read menu crop drawable", e);
}
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.<br>
* 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) {
Log.w("AIC", "Failed to update menu item color", e);
}
}
}
}
// endregion
}
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java
================================================
// "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) {}
}
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java
================================================
// "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.<br>
* Initialized with default values.
*/
public class CropImageOptions implements Parcelable {
public static final Creator<CropImageOptions> CREATOR =
new Creator<CropImageOptions>() {
@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)<br>
* We are basing this value off of the recommended 48dp Rhythm.<br>
* 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.<br>
* 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.<br>
* default: true, disable to provide custom progress bar UI.
*/
public boolean showProgressBar;
/**
* if auto-zoom functionality is enabled.<br>
* 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");
}
}
}
================================================
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java
================================================
// "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 androidx.exifinterface.media.ExifInterface;
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 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.<br>
* It is best to avoid it by using URI in setting image for cropping.<br>
* 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.<br>
* 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.<br>
* default: true, disable to provide custom progress bar UI.
*/
private boolean mShowProgressBar = true;
/**
* if auto-zoom functionality is enabled.<br>
* 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<BitmapLoadingWorkerTask> mBitmapLoadingWorkerTask;
/** Task used to crop bitmap async from UI thread */
private WeakReference<BitmapCroppingWorkerTask> 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();
}
/** 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.<br>
* 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).<br>
*/
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).<br>
*/
public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) {
mCropOverlayView.setMaxCropResultSize(maxCropResultWidth, maxCropResultHeight);
}
/**
* Get the amount of degrees the cropping image is rotated cloackwise.<br>
*
* @return 0-360
*/
public int getRotatedDegrees() {
return mDegreesRotated;
}
/**
* Set the amount of degrees the cropping image is rotated cloackwise.<br>
*
* @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<Integer, Integer> getAspectRatio() {
return new Pair<>(mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY());
}
/**
* Sets the both the X and Y values of the aspectRatio.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* It is best to avoid it by using URI in setting image for cropping.<br>
* 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.<br>
* It is best to avoid it by using URI in setting image for cropping.<br>
* 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;
}
/** 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());
}
/**
* 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.<br>
* 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;
}
/**
* Set the crop window position and size to the given rectangle.<br>
* 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);
}
/** 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.<br>
* 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.<br>
*
* @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.<br>
* 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.<br>
* Uses {@link RequestSizeOptions#RESIZE_INSIDE} option.<br>
* 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.<br>
* 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.<br>
* Uses JPEG image compression with 90 compression quality.<br>
* 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.<br>
* 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.<br>
* Uses {@link RequestSizeOptions#RESIZE_INSIDE} option.<br>
* 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.<br>
* 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.<br>
* <br>
* The EXIF can be retrieved by doing the following: <code>
* ExifInterface exif = new ExifInterface(path);</code>
*
* @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 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);
}
}
/**
* Sets a bitmap loaded from the given Android URI as the content of the CropImageView.<br>
* Can be used with URI from gallery or camera source.<br>
* Will rotate the image by exif data.<br>
*
* @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.<br>
* 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);
}
/** Flips the image vertically. */
public void flipImageVertically() {
mFlipVertically = !mFlipVertically;
applyImageMatrix(getWidth(), getHeight(), true, false);
}
// region: Private methods
/**
* 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<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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:<br>
* 1. Execute auto-zoom-in/out depending on the area covered of cropping window relative to the
* available view area.<br>
* 2. Slide the zoomed sub-area if the cropping window is outside of the visible view sub-area.
* <br>
*
* @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.<br>
* 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);
}
/**
* 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;
}
/**
* 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.<br> */
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.<br>
* 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.<br>
* 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.<br>
* The largest dimension will be equals to crop image view and the second dimensi
gitextract_9z8z5ozt/
├── .gitignore
├── .travis.yml
├── LICENSE.txt
├── README.md
├── build.gradle
├── cropper/
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── theartofdev/
│ │ └── edmodo/
│ │ └── cropper/
│ │ ├── BitmapCroppingWorkerTask.java
│ │ ├── BitmapLoadingWorkerTask.java
│ │ ├── BitmapUtils.java
│ │ ├── CropImage.java
│ │ ├── CropImageActivity.java
│ │ ├── CropImageAnimation.java
│ │ ├── CropImageOptions.java
│ │ ├── CropImageView.java
│ │ ├── CropOverlayView.java
│ │ ├── CropWindowHandler.java
│ │ └── CropWindowMoveHandler.java
│ └── res/
│ ├── layout/
│ │ ├── crop_image_activity.xml
│ │ └── crop_image_view.xml
│ ├── menu/
│ │ └── crop_image_menu.xml
│ ├── values/
│ │ ├── attrs.xml
│ │ └── strings.xml
│ ├── values-ar/
│ │ └── strings.xml
│ ├── values-cs/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-es-rGT/
│ │ └── strings.xml
│ ├── values-fa/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-he/
│ │ └── strings.xml
│ ├── values-hi/
│ │ └── strings.xml
│ ├── values-id/
│ │ └── strings.xml
│ ├── values-in/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-ja/
│ │ └── strings.xml
│ ├── values-ko/
│ │ └── strings.xml
│ ├── values-ms/
│ │ └── strings.xml
│ ├── values-nb/
│ │ └── strings.xml
│ ├── values-nl/
│ │ └── strings.xml
│ ├── values-pl/
│ │ └── strings.xml
│ ├── values-pt-rBR/
│ │ └── strings.xml
│ ├── values-ru-rRU/
│ │ └── strings.xml
│ ├── values-sv/
│ │ └── strings.xml
│ ├── values-tr/
│ │ └── strings.xml
│ ├── values-ur/
│ │ └── strings.xml
│ ├── values-vi/
│ │ └── strings.xml
│ ├── values-zh/
│ │ └── strings.xml
│ ├── values-zh-rCN/
│ │ └── strings.xml
│ └── values-zh-rTW/
│ └── strings.xml
├── gradle/
│ ├── gradle/
│ │ └── wrapper/
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.sh
├── quick-start/
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── theartofdev/
│ │ └── edmodo/
│ │ └── cropper/
│ │ └── quick/
│ │ └── start/
│ │ └── MainActivity.java
│ └── res/
│ ├── layout/
│ │ └── activity_main.xml
│ └── values/
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── release.sh
├── sample/
│ ├── build.gradle
│ ├── project.properties
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── theartofdev/
│ │ └── edmodo/
│ │ └── cropper/
│ │ └── sample/
│ │ ├── CropDemoPreset.java
│ │ ├── CropImageViewOptions.java
│ │ ├── CropResultActivity.java
│ │ ├── MainActivity.java
│ │ └── MainFragment.java
│ └── res/
│ ├── drawable/
│ │ ├── backdrop.xml
│ │ ├── checkerboard.xml
│ │ └── muted.xml
│ ├── layout/
│ │ ├── activity_crop_result.xml
│ │ ├── activity_main.xml
│ │ ├── fragment_main_customized.xml
│ │ ├── fragment_main_min_max.xml
│ │ ├── fragment_main_oval.xml
│ │ ├── fragment_main_rect.xml
│ │ └── fragment_main_scale_center.xml
│ ├── menu/
│ │ └── main.xml
│ └── values/
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── settings.gradle
└── test/
├── .gitignore
├── build.gradle
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── theartofdev/
│ └── edmodo/
│ └── cropper/
│ └── test/
│ └── MainActivity.java
└── res/
├── layout/
│ └── activity_main.xml
└── values/
├── colors.xml
├── strings.xml
└── styles.xml
SYMBOL INDEX (407 symbols across 18 files)
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java
class BitmapCroppingWorkerTask (line 23) | final class BitmapCroppingWorkerTask
method BitmapCroppingWorkerTask (line 86) | BitmapCroppingWorkerTask(
method BitmapCroppingWorkerTask (line 124) | BitmapCroppingWorkerTask(
method getUri (line 165) | public Uri getUri() {
method doInBackground (line 175) | @Override
method onPostExecute (line 237) | @Override
class Result (line 258) | static final class Result {
method Result (line 275) | Result(Bitmap bitmap, int sampleSize) {
method Result (line 283) | Result(Uri uri, int sampleSize) {
method Result (line 291) | Result(Exception error, boolean isSave) {
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java
class BitmapLoadingWorkerTask (line 24) | final class BitmapLoadingWorkerTask extends AsyncTask<Void, Void, Bitmap...
method BitmapLoadingWorkerTask (line 44) | public BitmapLoadingWorkerTask(CropImageView cropImageView, Uri uri) {
method getUri (line 57) | public Uri getUri() {
method doInBackground (line 67) | @Override
method onPostExecute (line 95) | @Override
class Result (line 116) | public static final class Result {
method Result (line 133) | Result(Uri uri, Bitmap bitmap, int loadSampleSize, int degreesRotate...
method Result (line 141) | Result(Uri uri, Exception error) {
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java
class BitmapUtils (line 43) | final class BitmapUtils {
method rotateBitmapByExif (line 69) | static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, Context co...
method rotateBitmapByExif (line 87) | static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, ExifInterf...
method decodeSampledBitmap (line 109) | static BitmapSampled decodeSampledBitmap(Context context, Uri uri, int...
method cropBitmapObjectHandleOOM (line 146) | static BitmapSampled cropBitmapObjectHandleOOM(
method cropBitmapObjectWithScale (line 188) | private static Bitmap cropBitmapObjectWithScale(
method cropBitmap (line 240) | static BitmapSampled cropBitmap(
method getRectLeft (line 291) | static float getRectLeft(float[] points) {
method getRectTop (line 296) | static float getRectTop(float[] points) {
method getRectRight (line 301) | static float getRectRight(float[] points) {
method getRectBottom (line 306) | static float getRectBottom(float[] points) {
method getRectWidth (line 311) | static float getRectWidth(float[] points) {
method getRectHeight (line 316) | static float getRectHeight(float[] points) {
method getRectCenterX (line 321) | static float getRectCenterX(float[] points) {
method getRectCenterY (line 326) | static float getRectCenterY(float[] points) {
method getRectFromPoints (line 334) | static Rect getRectFromPoints(
method fixRectForAspectRatio (line 358) | private static void fixRectForAspectRatio(Rect rect, int aspectRatioX,...
method writeTempStateStoreBitmap (line 376) | static Uri writeTempStateStoreBitmap(Context context, Bitmap bitmap, U...
method writeBitmapToUri (line 397) | static void writeBitmapToUri(
method resizeBitmap (line 414) | static Bitmap resizeBitmap(
method cropBitmap (line 459) | private static BitmapSampled cropBitmap(
method cropBitmap (line 539) | private static BitmapSampled cropBitmap(
method decodeImageForOption (line 602) | private static BitmapFactory.Options decodeImageForOption(ContentResol...
method decodeImage (line 621) | private static Bitmap decodeImage(
method decodeSampledBitmapRegion (line 644) | private static BitmapSampled decodeSampledBitmapRegion(
method cropForRotatedImage (line 682) | private static Bitmap cropForRotatedImage(
method calculateInSampleSizeByReqestedSize (line 726) | private static int calculateInSampleSizeByReqestedSize(
method calculateInSampleSizeByMaxTextureSize (line 741) | private static int calculateInSampleSizeByMaxTextureSize(int width, in...
method rotateAndFlipBitmapInt (line 759) | private static Bitmap rotateAndFlipBitmapInt(
method getMaxTextureSize (line 780) | private static int getMaxTextureSize() {
method closeSafe (line 832) | private static void closeSafe(Closeable closeable) {
class BitmapSampled (line 845) | static final class BitmapSampled {
method BitmapSampled (line 853) | BitmapSampled(Bitmap bitmap, int sampleSize) {
class RotateBitmapResult (line 863) | static final class RotateBitmapResult {
method RotateBitmapResult (line 871) | RotateBitmapResult(Bitmap bitmap, int degrees) {
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java
class CropImage (line 59) | @SuppressWarnings("WeakerAccess, unused")
method CropImage (line 98) | private CropImage() {}
method toOvalBitmap (line 104) | public static Bitmap toOvalBitmap(@NonNull Bitmap bitmap) {
method startPickImageActivity (line 136) | public static void startPickImageActivity(@NonNull Activity activity) {
method startPickImageActivity (line 148) | public static void startPickImageActivity(@NonNull Context context, @N...
method getPickImageChooserIntent (line 162) | public static Intent getPickImageChooserIntent(@NonNull Context contex...
method getPickImageChooserIntent (line 178) | public static Intent getPickImageChooserIntent(
method getCameraIntent (line 228) | public static Intent getCameraIntent(@NonNull Context context, Uri out...
method getCameraIntents (line 238) | public static List<Intent> getCameraIntents(
method getGalleryIntents (line 265) | public static List<Intent> getGalleryIntents(
method isExplicitCameraPermissionRequired (line 304) | public static boolean isExplicitCameraPermissionRequired(@NonNull Cont...
method hasPermissionInManifest (line 317) | public static boolean hasPermissionInManifest(
method getCaptureImageOutputUri (line 342) | public static Uri getCaptureImageOutputUri(@NonNull Context context) {
method getPickImageResultUri (line 359) | public static Uri getPickImageResultUri(@NonNull Context context, @Nul...
method isReadExternalStoragePermissionsRequired (line 380) | public static boolean isReadExternalStoragePermissionsRequired(
method isUriRequiresPermissions (line 396) | public static boolean isUriRequiresPermissions(@NonNull Context contex...
method activity (line 417) | public static ActivityBuilder activity() {
method activity (line 430) | public static ActivityBuilder activity(@Nullable Uri uri) {
method getActivityResult (line 442) | public static ActivityResult getActivityResult(@Nullable Intent data) {
class ActivityBuilder (line 449) | public static final class ActivityBuilder {
method ActivityBuilder (line 457) | private ActivityBuilder(@Nullable Uri source) {
method getIntent (line 463) | public Intent getIntent(@NonNull Context context) {
method getIntent (line 468) | public Intent getIntent(@NonNull Context context, @Nullable Class<?>...
method start (line 485) | public void start(@NonNull Activity activity) {
method start (line 495) | public void start(@NonNull Activity activity, @Nullable Class<?> cls) {
method start (line 505) | public void start(@NonNull Context context, @NonNull Fragment fragme...
method start (line 514) | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
method start (line 524) | public void start(
method start (line 534) | @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
method setCropShape (line 545) | public ActivityBuilder setCropShape(@NonNull CropImageView.CropShape...
method setSnapRadius (line 556) | public ActivityBuilder setSnapRadius(float snapRadius) {
method setTouchRadius (line 567) | public ActivityBuilder setTouchRadius(float touchRadius) {
method setGuidelines (line 576) | public ActivityBuilder setGuidelines(@NonNull CropImageView.Guidelin...
method setScaleType (line 585) | public ActivityBuilder setScaleType(@NonNull CropImageView.ScaleType...
method setShowCropOverlay (line 595) | public ActivityBuilder setShowCropOverlay(boolean showCropOverlay) {
method setAutoZoomEnabled (line 604) | public ActivityBuilder setAutoZoomEnabled(boolean autoZoomEnabled) {
method setMultiTouchEnabled (line 613) | public ActivityBuilder setMultiTouchEnabled(boolean multiTouchEnable...
method setMaxZoom (line 622) | public ActivityBuilder setMaxZoom(int maxZoom) {
method setInitialCropWindowPaddingRatio (line 632) | public ActivityBuilder setInitialCropWindowPaddingRatio(float initia...
method setFixAspectRatio (line 641) | public ActivityBuilder setFixAspectRatio(boolean fixAspectRatio) {
method setAspectRatio (line 654) | public ActivityBuilder setAspectRatio(int aspectRatioX, int aspectRa...
method setBorderLineThickness (line 665) | public ActivityBuilder setBorderLineThickness(float borderLineThickn...
method setBorderLineColor (line 674) | public ActivityBuilder setBorderLineColor(int borderLineColor) {
method setBorderCornerThickness (line 683) | public ActivityBuilder setBorderCornerThickness(float borderCornerTh...
method setBorderCornerOffset (line 692) | public ActivityBuilder setBorderCornerOffset(float borderCornerOffse...
method setBorderCornerLength (line 701) | public ActivityBuilder setBorderCornerLength(float borderCornerLengt...
method setBorderCornerColor (line 710) | public ActivityBuilder setBorderCornerColor(int borderCornerColor) {
method setGuidelinesThickness (line 719) | public ActivityBuilder setGuidelinesThickness(float guidelinesThickn...
method setGuidelinesColor (line 728) | public ActivityBuilder setGuidelinesColor(int guidelinesColor) {
method setBackgroundColor (line 738) | public ActivityBuilder setBackgroundColor(int backgroundColor) {
method setMinCropWindowSize (line 747) | public ActivityBuilder setMinCropWindowSize(int minCropWindowWidth, ...
method setMinCropResultSize (line 758) | public ActivityBuilder setMinCropResultSize(int minCropResultWidth, ...
method setMaxCropResultSize (line 769) | public ActivityBuilder setMaxCropResultSize(int maxCropResultWidth, ...
method setActivityTitle (line 779) | public ActivityBuilder setActivityTitle(CharSequence activityTitle) {
method setActivityMenuIconColor (line 788) | public ActivityBuilder setActivityMenuIconColor(int activityMenuIcon...
method setOutputUri (line 797) | public ActivityBuilder setOutputUri(Uri outputUri) {
method setOutputCompressFormat (line 806) | public ActivityBuilder setOutputCompressFormat(Bitmap.CompressFormat...
method setOutputCompressQuality (line 815) | public ActivityBuilder setOutputCompressQuality(int outputCompressQu...
method setRequestedSize (line 825) | public ActivityBuilder setRequestedSize(int reqWidth, int reqHeight) {
method setRequestedSize (line 833) | public ActivityBuilder setRequestedSize(
method setNoOutputImage (line 847) | public ActivityBuilder setNoOutputImage(boolean noOutputImage) {
method setInitialCropWindowRectangle (line 856) | public ActivityBuilder setInitialCropWindowRectangle(Rect initialCro...
method setInitialRotation (line 866) | public ActivityBuilder setInitialRotation(int initialRotation) {
method setAllowRotation (line 875) | public ActivityBuilder setAllowRotation(boolean allowRotation) {
method setAllowFlipping (line 884) | public ActivityBuilder setAllowFlipping(boolean allowFlipping) {
method setAllowCounterRotation (line 894) | public ActivityBuilder setAllowCounterRotation(boolean allowCounterR...
method setRotationDegrees (line 903) | public ActivityBuilder setRotationDegrees(int rotationDegrees) {
method setFlipHorizontally (line 912) | public ActivityBuilder setFlipHorizontally(boolean flipHorizontally) {
method setFlipVertically (line 921) | public ActivityBuilder setFlipVertically(boolean flipVertically) {
method setCropMenuCropButtonTitle (line 930) | public ActivityBuilder setCropMenuCropButtonTitle(CharSequence title) {
method setCropMenuCropButtonIcon (line 939) | public ActivityBuilder setCropMenuCropButtonIcon(@DrawableRes int dr...
class ActivityResult (line 949) | public static final class ActivityResult extends CropImageView.CropRes...
method createFromParcel (line 953) | @Override
method newArray (line 958) | @Override
method ActivityResult (line 964) | public ActivityResult(
method ActivityResult (line 986) | protected ActivityResult(Parcel in) {
method writeToParcel (line 1000) | @Override
method describeContents (line 1012) | @Override
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java
class CropImageActivity (line 41) | public class CropImageActivity extends AppCompatActivity
method onCreate (line 54) | @Override
method onStart (line 98) | @Override
method onStop (line 105) | @Override
method onCreateOptionsMenu (line 112) | @Override
method onOptionsItemSelected (line 154) | @Override
method onBackPressed (line 183) | @Override
method onActivityResult (line 189) | @Override
method onRequestPermissionsResult (line 218) | @Override
method onSetImageUriComplete (line 240) | @Override
method onCropImageComplete (line 254) | @Override
method cropImage (line 262) | protected void cropImage() {
method rotateImage (line 278) | protected void rotateImage(int degrees) {
method getOutputUri (line 286) | protected Uri getOutputUri() {
method setResult (line 303) | protected void setResult(Uri uri, Exception error, int sampleSize) {
method setResultCancel (line 310) | protected void setResultCancel() {
method getResultIntent (line 316) | protected Intent getResultIntent(Uri uri, Exception error, int sampleS...
method updateMenuItemIconColor (line 334) | private void updateMenuItemIconColor(Menu menu, int itemId, int color) {
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java
class CropImageAnimation (line 26) | final class CropImageAnimation extends Animation implements Animation.An...
method CropImageAnimation (line 53) | public CropImageAnimation(ImageView cropImageView, CropOverlayView cro...
method setStartState (line 63) | public void setStartState(float[] boundPoints, Matrix imageMatrix) {
method setEndState (line 70) | public void setEndState(float[] boundPoints, Matrix imageMatrix) {
method applyTransformation (line 76) | @Override
method onAnimationStart (line 111) | @Override
method onAnimationEnd (line 114) | @Override
method onAnimationRepeat (line 119) | @Override
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java
class CropImageOptions (line 30) | public class CropImageOptions implements Parcelable {
method createFromParcel (line 34) | @Override
method newArray (line 39) | @Override
method CropImageOptions (line 226) | public CropImageOptions() {
method CropImageOptions (line 289) | protected CropImageOptions(Parcel in) {
method writeToParcel (line 340) | @Override
method describeContents (line 392) | @Override
method validate (line 402) | public void validate() {
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java
class CropImageView (line 42) | public class CropImageView extends FrameLayout {
method CropImageView (line 178) | public CropImageView(Context context) {
method CropImageView (line 182) | public CropImageView(Context context, AttributeSet attrs) {
method getScaleType (line 355) | public ScaleType getScaleType() {
method setScaleType (line 360) | public void setScaleType(ScaleType scaleType) {
method getCropShape (line 371) | public CropShape getCropShape() {
method setCropShape (line 379) | public void setCropShape(CropShape cropShape) {
method isAutoZoomEnabled (line 384) | public boolean isAutoZoomEnabled() {
method setAutoZoomEnabled (line 389) | public void setAutoZoomEnabled(boolean autoZoomEnabled) {
method setMultiTouchEnabled (line 398) | public void setMultiTouchEnabled(boolean multiTouchEnabled) {
method getMaxZoom (line 406) | public int getMaxZoom() {
method setMaxZoom (line 411) | public void setMaxZoom(int maxZoom) {
method setMinCropResultSize (line 423) | public void setMinCropResultSize(int minCropResultWidth, int minCropRe...
method setMaxCropResultSize (line 431) | public void setMaxCropResultSize(int maxCropResultWidth, int maxCropRe...
method getRotatedDegrees (line 440) | public int getRotatedDegrees() {
method setRotatedDegrees (line 449) | public void setRotatedDegrees(int degrees) {
method isFixAspectRatio (line 459) | public boolean isFixAspectRatio() {
method setFixedAspectRatio (line 467) | public void setFixedAspectRatio(boolean fixAspectRatio) {
method isFlippedHorizontally (line 472) | public boolean isFlippedHorizontally() {
method setFlippedHorizontally (line 477) | public void setFlippedHorizontally(boolean flipHorizontally) {
method isFlippedVertically (line 485) | public boolean isFlippedVertically() {
method setFlippedVertically (line 490) | public void setFlippedVertically(boolean flipVertically) {
method getGuidelines (line 498) | public Guidelines getGuidelines() {
method setGuidelines (line 506) | public void setGuidelines(Guidelines guidelines) {
method getAspectRatio (line 511) | public Pair<Integer, Integer> getAspectRatio() {
method setAspectRatio (line 522) | public void setAspectRatio(int aspectRatioX, int aspectRatioY) {
method clearAspectRatio (line 529) | public void clearAspectRatio() {
method setSnapRadius (line 540) | public void setSnapRadius(float snapRadius) {
method isShowProgressBar (line 550) | public boolean isShowProgressBar() {
method setShowProgressBar (line 558) | public void setShowProgressBar(boolean showProgressBar) {
method isShowCropOverlay (line 570) | public boolean isShowCropOverlay() {
method setShowCropOverlay (line 579) | public void setShowCropOverlay(boolean showCropOverlay) {
method isSaveBitmapToInstanceState (line 592) | public boolean isSaveBitmapToInstanceState() {
method setSaveBitmapToInstanceState (line 602) | public void setSaveBitmapToInstanceState(boolean saveBitmapToInstanceS...
method getImageResource (line 607) | public int getImageResource() {
method getImageUri (line 612) | public Uri getImageUri() {
method getWholeImageRect (line 621) | public Rect getWholeImageRect() {
method getCropRect (line 639) | public Rect getCropRect() {
method getCropWindowRect (line 667) | public RectF getCropWindowRect() {
method getCropPoints (line 682) | public float[] getCropPoints() {
method setCropRect (line 715) | public void setCropRect(Rect rect) {
method resetCropRect (line 720) | public void resetCropRect() {
method getCroppedImage (line 736) | public Bitmap getCroppedImage() {
method getCroppedImage (line 748) | public Bitmap getCroppedImage(int reqWidth, int reqHeight) {
method getCroppedImage (line 760) | public Bitmap getCroppedImage(int reqWidth, int reqHeight, RequestSize...
method getCroppedImageAsync (line 813) | public void getCroppedImageAsync() {
method getCroppedImageAsync (line 826) | public void getCroppedImageAsync(int reqWidth, int reqHeight) {
method getCroppedImageAsync (line 839) | public void getCroppedImageAsync(int reqWidth, int reqHeight, RequestS...
method saveCroppedImageAsync (line 854) | public void saveCroppedImageAsync(Uri saveUri) {
method saveCroppedImageAsync (line 867) | public void saveCroppedImageAsync(
method saveCroppedImageAsync (line 885) | public void saveCroppedImageAsync(
method saveCroppedImageAsync (line 912) | public void saveCroppedImageAsync(
method setOnSetCropOverlayReleasedListener (line 927) | public void setOnSetCropOverlayReleasedListener(OnSetCropOverlayReleas...
method setOnSetCropOverlayMovedListener (line 932) | public void setOnSetCropOverlayMovedListener(OnSetCropOverlayMovedList...
method setOnCropWindowChangedListener (line 937) | public void setOnCropWindowChangedListener(OnSetCropWindowChangeListen...
method setOnSetImageUriCompleteListener (line 945) | public void setOnSetImageUriCompleteListener(OnSetImageUriCompleteList...
method setOnCropImageCompleteListener (line 953) | public void setOnCropImageCompleteListener(OnCropImageCompleteListener...
method setImageBitmap (line 962) | public void setImageBitmap(Bitmap bitmap) {
method setImageBitmap (line 976) | public void setImageBitmap(Bitmap bitmap, ExifInterface exif) {
method setImageResource (line 996) | public void setImageResource(int resId) {
method setImageUriAsync (line 1011) | public void setImageUriAsync(Uri uri) {
method clearImage (line 1033) | public void clearImage() {
method rotateImage (line 1044) | public void rotateImage(int degrees) {
method flipImageHorizontally (line 1120) | public void flipImageHorizontally() {
method flipImageVertically (line 1126) | public void flipImageVertically() {
method onSetImageUriAsyncComplete (line 1139) | void onSetImageUriAsyncComplete(BitmapLoadingWorkerTask.Result result) {
method onImageCroppingAsyncComplete (line 1161) | void onImageCroppingAsyncComplete(BitmapCroppingWorkerTask.Result resu...
method setBitmap (line 1189) | private void setBitmap(
method clearImageInt (line 1219) | private void clearImageInt() {
method startCropWorkerTask (line 1260) | public void startCropWorkerTask(
method onSaveInstanceState (line 1330) | @Override
method onRestoreInstanceState (line 1377) | @Override
method onMeasure (line 1445) | @Override
method onLayout (line 1505) | @Override
method onSizeChanged (line 1548) | @Override
method handleCropWindowChanged (line 1564) | private void handleCropWindowChanged(boolean inProgress, boolean anima...
method applyImageMatrix (line 1629) | private void applyImageMatrix(float width, float height, boolean cente...
method mapImagePointsByImageMatrix (line 1735) | private void mapImagePointsByImageMatrix() {
method getOnMeasureSpec (line 1765) | private static int getOnMeasureSpec(int measureSpecMode, int measureSp...
method setCropOverlayVisibility (line 1786) | private void setCropOverlayVisibility() {
method setProgressBarVisibility (line 1795) | private void setProgressBarVisibility() {
method updateImageBounds (line 1804) | private void updateImageBounds(boolean clear) {
type CropShape (line 1828) | public enum CropShape {
type ScaleType (line 1840) | public enum ScaleType {
type Guidelines (line 1879) | public enum Guidelines {
type RequestSizeOptions (line 1894) | public enum RequestSizeOptions {
type OnSetCropOverlayReleasedListener (line 1937) | public interface OnSetCropOverlayReleasedListener {
method onCropOverlayReleased (line 1944) | void onCropOverlayReleased(Rect rect);
type OnSetCropOverlayMovedListener (line 1948) | public interface OnSetCropOverlayMovedListener {
method onCropOverlayMoved (line 1955) | void onCropOverlayMoved(Rect rect);
type OnSetCropWindowChangeListener (line 1959) | public interface OnSetCropWindowChangeListener {
method onCropWindowChanged (line 1962) | void onCropWindowChanged();
type OnSetImageUriCompleteListener (line 1966) | public interface OnSetImageUriCompleteListener {
method onSetImageUriComplete (line 1976) | void onSetImageUriComplete(CropImageView view, Uri uri, Exception er...
type OnCropImageCompleteListener (line 1983) | public interface OnCropImageCompleteListener {
method onCropImageComplete (line 1993) | void onCropImageComplete(CropImageView view, CropResult result);
class CropResult (line 2000) | public static class CropResult {
method CropResult (line 2044) | CropResult(
method getOriginalBitmap (line 2071) | public Bitmap getOriginalBitmap() {
method getOriginalUri (line 2079) | public Uri getOriginalUri() {
method isSuccessful (line 2084) | public boolean isSuccessful() {
method getBitmap (line 2092) | public Bitmap getBitmap() {
method getUri (line 2100) | public Uri getUri() {
method getError (line 2105) | public Exception getError() {
method getCropPoints (line 2110) | public float[] getCropPoints() {
method getCropRect (line 2115) | public Rect getCropRect() {
method getWholeImageRect (line 2120) | public Rect getWholeImageRect() {
method getRotation (line 2125) | public int getRotation() {
method getSampleSize (line 2130) | public int getSampleSize() {
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropOverlayView.java
class CropOverlayView (line 33) | public class CropOverlayView extends View {
method CropOverlayView (line 135) | public CropOverlayView(Context context) {
method CropOverlayView (line 139) | public CropOverlayView(Context context, AttributeSet attrs) {
method setCropWindowChangeListener (line 144) | public void setCropWindowChangeListener(CropWindowChangeListener liste...
method getCropWindowRect (line 149) | public RectF getCropWindowRect() {
method setCropWindowRect (line 154) | public void setCropWindowRect(RectF rect) {
method fixCurrentCropWindowRect (line 159) | public void fixCurrentCropWindowRect() {
method setBounds (line 173) | public void setBounds(float[] boundsPoints, int viewWidth, int viewHei...
method resetCropOverlayView (line 190) | public void resetCropOverlayView() {
method getCropShape (line 199) | public CropImageView.CropShape getCropShape() {
method setCropShape (line 204) | public void setCropShape(CropImageView.CropShape cropShape) {
method getGuidelines (line 227) | public CropImageView.Guidelines getGuidelines() {
method setGuidelines (line 235) | public void setGuidelines(CropImageView.Guidelines guidelines) {
method isFixAspectRatio (line 248) | public boolean isFixAspectRatio() {
method setFixedAspectRatio (line 256) | public void setFixedAspectRatio(boolean fixAspectRatio) {
method getAspectRatioX (line 267) | public int getAspectRatioX() {
method setAspectRatioX (line 272) | public void setAspectRatioX(int aspectRatioX) {
method getAspectRatioY (line 288) | public int getAspectRatioY() {
method setAspectRatioY (line 297) | public void setAspectRatioY(int aspectRatioY) {
method setSnapRadius (line 317) | public void setSnapRadius(float snapRadius) {
method setMultiTouchEnabled (line 322) | public boolean setMultiTouchEnabled(boolean multiTouchEnabled) {
method setMinCropResultSize (line 337) | public void setMinCropResultSize(int minCropResultWidth, int minCropRe...
method setMaxCropResultSize (line 345) | public void setMaxCropResultSize(int maxCropResultWidth, int maxCropRe...
method setCropWindowLimits (line 353) | public void setCropWindowLimits(
method getInitialCropWindowRect (line 360) | public Rect getInitialCropWindowRect() {
method setInitialCropWindowRect (line 365) | public void setInitialCropWindowRect(Rect rect) {
method resetCropWindowRect (line 375) | public void resetCropWindowRect() {
method setInitialAttributeValues (line 387) | public void setInitialAttributeValues(CropImageOptions options) {
method initCropWindow (line 427) | private void initCropWindow() {
method fixCropWindowRectByRules (line 515) | private void fixCropWindowRectByRules(RectF rect) {
method onDraw (line 573) | @Override
method drawBackground (line 597) | private void drawBackground(Canvas canvas) {
method drawGuidelines (line 653) | private void drawGuidelines(Canvas canvas) {
method drawBorders (line 698) | private void drawBorders(Canvas canvas) {
method drawCorners (line 715) | private void drawCorners(Canvas canvas) {
method getNewPaint (line 791) | private static Paint getNewPaint(int color) {
method getNewPaintOrNull (line 798) | private static Paint getNewPaintOrNull(float thickness, int color) {
method onTouchEvent (line 811) | @Override
method onActionDown (line 844) | private void onActionDown(float x, float y) {
method onActionUp (line 852) | private void onActionUp() {
method onActionMove (line 865) | private void onActionMove(float x, float y) {
method calculateBounds (line 900) | private boolean calculateBounds(RectF rect) {
method isNonStraightAngleRotated (line 974) | private boolean isNonStraightAngleRotated() {
method callOnCropWindowChanged (line 979) | private void callOnCropWindowChanged(boolean inProgress) {
type CropWindowChangeListener (line 993) | public interface CropWindowChangeListener {
method onCropWindowChanged (line 1000) | void onCropWindowChanged(boolean inProgress);
class ScaleListener (line 1007) | private class ScaleListener extends ScaleGestureDetector.SimpleOnScale...
method onScale (line 1009) | @Override
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowHandler.java
class CropWindowHandler (line 18) | final class CropWindowHandler {
method getRect (line 75) | public RectF getRect() {
method getMinCropWidth (line 81) | public float getMinCropWidth() {
method getMinCropHeight (line 86) | public float getMinCropHeight() {
method getMaxCropWidth (line 91) | public float getMaxCropWidth() {
method getMaxCropHeight (line 96) | public float getMaxCropHeight() {
method getScaleFactorWidth (line 101) | public float getScaleFactorWidth() {
method getScaleFactorHeight (line 106) | public float getScaleFactorHeight() {
method setMinCropResultSize (line 114) | public void setMinCropResultSize(int minCropResultWidth, int minCropRe...
method setMaxCropResultSize (line 123) | public void setMaxCropResultSize(int maxCropResultWidth, int maxCropRe...
method setCropWindowLimits (line 132) | public void setCropWindowLimits(
method setInitialAttributeValues (line 141) | public void setInitialAttributeValues(CropImageOptions options) {
method setRect (line 151) | public void setRect(RectF rect) {
method showGuidelines (line 161) | public boolean showGuidelines() {
method getMoveHandler (line 174) | public CropWindowMoveHandler getMoveHandler(
method getRectanglePressedMoveType (line 194) | private CropWindowMoveHandler.Type getRectanglePressedMoveType(
method getOvalPressedMoveType (line 243) | private CropWindowMoveHandler.Type getOvalPressedMoveType(float x, flo...
method isInCornerTargetZone (line 305) | private static boolean isInCornerTargetZone(
method isInHorizontalTargetZone (line 321) | private static boolean isInHorizontalTargetZone(
method isInVerticalTargetZone (line 337) | private static boolean isInVerticalTargetZone(
method isInCenterTargetZone (line 353) | private static boolean isInCenterTargetZone(
method focusCenter (line 367) | private boolean focusCenter() {
FILE: cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowMoveHandler.java
class CropWindowMoveHandler (line 23) | final class CropWindowMoveHandler {
method CropWindowMoveHandler (line 62) | public CropWindowMoveHandler(
method move (line 95) | public void move(
method calculateTouchOffset (line 132) | private void calculateTouchOffset(RectF rect, float touchX, float touc...
method moveCenter (line 184) | private void moveCenter(
method moveSizeWithFreeAspectRatio (line 211) | private void moveSizeWithFreeAspectRatio(
method moveSizeWithFixedAspectRatio (line 253) | private void moveSizeWithFixedAspectRatio(
method snapEdgesToBounds (line 321) | private void snapEdgesToBounds(RectF edges, RectF bounds, float margin) {
method adjustLeft (line 344) | private void adjustLeft(
method adjustRight (line 431) | private void adjustRight(
method adjustTop (line 520) | private void adjustTop(
method adjustBottom (line 606) | private void adjustBottom(
method adjustLeftByAspectRatio (line 689) | private void adjustLeftByAspectRatio(RectF rect, float aspectRatio) {
method adjustTopByAspectRatio (line 697) | private void adjustTopByAspectRatio(RectF rect, float aspectRatio) {
method adjustRightByAspectRatio (line 705) | private void adjustRightByAspectRatio(RectF rect, float aspectRatio) {
method adjustBottomByAspectRatio (line 713) | private void adjustBottomByAspectRatio(RectF rect, float aspectRatio) {
method adjustLeftRightByAspectRatio (line 721) | private void adjustLeftRightByAspectRatio(RectF rect, RectF bounds, fl...
method adjustTopBottomByAspectRatio (line 735) | private void adjustTopBottomByAspectRatio(RectF rect, RectF bounds, fl...
method calculateAspectRatio (line 746) | private static float calculateAspectRatio(float left, float top, float...
type Type (line 754) | public enum Type {
FILE: quick-start/src/main/java/com/theartofdev/edmodo/cropper/quick/start/MainActivity.java
class MainActivity (line 25) | public class MainActivity extends AppCompatActivity {
method onCreate (line 27) | @Override
method onSelectImageClick (line 34) | public void onSelectImageClick(View view) {
method onActivityResult (line 45) | @Override
FILE: sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropDemoPreset.java
type CropDemoPreset (line 15) | enum CropDemoPreset {
FILE: sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropImageViewOptions.java
class CropImageViewOptions (line 20) | final class CropImageViewOptions {
FILE: sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropResultActivity.java
class CropResultActivity (line 28) | public final class CropResultActivity extends Activity {
method onCreate (line 35) | @Override
method onBackPressed (line 76) | @Override
method onImageViewClicked (line 82) | public void onImageViewClicked(View view) {
method releaseBitmap (line 87) | private void releaseBitmap() {
FILE: sample/src/main/java/com/theartofdev/edmodo/cropper/sample/MainActivity.java
class MainActivity (line 37) | public class MainActivity extends AppCompatActivity {
method setCurrentFragment (line 52) | public void setCurrentFragment(MainFragment fragment) {
method setCurrentOptions (line 56) | public void setCurrentOptions(CropImageViewOptions options) {
method onCreate (line 61) | @Override
method onPostCreate (line 82) | @Override
method onCreateOptionsMenu (line 89) | @Override
method onOptionsItemSelected (line 96) | @Override
method onActivityResult (line 107) | @Override
method onRequestPermissionsResult (line 136) | @Override
method onDrawerOptionClicked (line 159) | @SuppressLint("NewApi")
method setMainFragmentByPreset (line 284) | private void setMainFragmentByPreset(CropDemoPreset demoPreset) {
method updateDrawerTogglesByOptions (line 292) | private void updateDrawerTogglesByOptions(CropImageViewOptions options) {
FILE: sample/src/main/java/com/theartofdev/edmodo/cropper/sample/MainFragment.java
class MainFragment (line 33) | public final class MainFragment extends Fragment
method newInstance (line 45) | public static MainFragment newInstance(CropDemoPreset demoPreset) {
method setImageUri (line 54) | public void setImageUri(Uri imageUri) {
method setCropImageViewOptions (line 61) | public void setCropImageViewOptions(CropImageViewOptions options) {
method setInitialCropRect (line 77) | public void setInitialCropRect() {
method resetCropRect (line 82) | public void resetCropRect() {
method updateCurrentCropViewOptions (line 86) | public void updateCurrentCropViewOptions() {
method onCreateView (line 102) | @Override
method onViewCreated (line 131) | @Override
method onOptionsItemSelected (line 150) | @Override
method onAttach (line 168) | @Override
method onDetach (line 175) | @Override
method onSetImageUriComplete (line 184) | @Override
method onCropImageComplete (line 195) | @Override
method onActivityResult (line 200) | @Override
method handleCropResult (line 209) | private void handleCropResult(CropImageView.CropResult result) {
FILE: test/src/main/java/com/theartofdev/edmodo/cropper/test/MainActivity.java
class MainActivity (line 14) | public class MainActivity extends AppCompatActivity {
method onCreate (line 16) | @Override
method onSelectImageClick (line 23) | public void onSelectImageClick(View view) {
method onActivityResult (line 27) | @Override
Condensed preview — 99 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (397K chars).
[
{
"path": ".gitignore",
"chars": 364,
"preview": "\n# built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/"
},
{
"path": ".travis.yml",
"chars": 212,
"preview": "language: android\njdk: oraclejdk8\nsudo: false\n\nandroid:\n components:\n - tools\n - platform-tools\n - build-tools"
},
{
"path": "LICENSE.txt",
"chars": 11365,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 6458,
"preview": "Android Image Cropper\n=======\n\n# :triangular_flag_on_post: The Project is NOT currently maintained :triangular_flag_on_p"
},
{
"path": "build.gradle",
"chars": 565,
"preview": "buildscript {\n repositories {\n jcenter()\n google()\n }\n dependencies {\n classpath 'com.andr"
},
{
"path": "cropper/build.gradle",
"chars": 1516,
"preview": "apply plugin: 'com.android.library'\n// https://docs.gradle.org/current/userguide/publishing_maven.html\n// http://www.fle"
},
{
"path": "cropper/src/main/AndroidManifest.xml",
"chars": 69,
"preview": "<manifest\n package=\"com.theartofdev.edmodo.cropper\">\n\n</manifest>\n"
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java",
"chars": 8630,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java",
"chars": 4253,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java",
"chars": 29304,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java",
"chars": 35342,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java",
"chars": 12151,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java",
"chars": 3835,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java",
"chars": 16536,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth;\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java",
"chars": 76198,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropOverlayView.java",
"chars": 35303,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowHandler.java",
"chars": 14151,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowMoveHandler.java",
"chars": 27033,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "cropper/src/main/res/layout/crop_image_activity.xml",
"chars": 266,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.theartofdev.edmodo.cropper.CropImageView\n android:id=\"@+id/cropImageView\""
},
{
"path": "cropper/src/main/res/layout/crop_image_view.xml",
"chars": 857,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "cropper/src/main/res/menu/crop_image_menu.xml",
"chars": 1357,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "cropper/src/main/res/values/attrs.xml",
"chars": 2446,
"preview": "<resources>\n\n <declare-styleable name=\"CropImageView\">\n <attr name=\"cropGuidelines\">\n <enum name=\"o"
},
{
"path": "cropper/src/main/res/values/strings.xml",
"chars": 718,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-ar/strings.xml",
"chars": 692,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-cs/strings.xml",
"chars": 744,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-de/strings.xml",
"chars": 763,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-es/strings.xml",
"chars": 758,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_activity_title\"></string>\n <string na"
},
{
"path": "cropper/src/main/res/values-es-rGT/strings.xml",
"chars": 752,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_activity_title\"></string>\n <string na"
},
{
"path": "cropper/src/main/res/values-fa/strings.xml",
"chars": 678,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">چرخش در جهت عقربه های "
},
{
"path": "cropper/src/main/res/values-fr/strings.xml",
"chars": 754,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_activity_title\"></string>\n <string na"
},
{
"path": "cropper/src/main/res/values-he/strings.xml",
"chars": 675,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-hi/strings.xml",
"chars": 661,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">घड़ी की सुई के विपरीत "
},
{
"path": "cropper/src/main/res/values-id/strings.xml",
"chars": 682,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">Putar berlawanan arah "
},
{
"path": "cropper/src/main/res/values-in/strings.xml",
"chars": 682,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">Putar berlawanan arah "
},
{
"path": "cropper/src/main/res/values-it/strings.xml",
"chars": 759,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-ja/strings.xml",
"chars": 576,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">左回転</string>\n <stri"
},
{
"path": "cropper/src/main/res/values-ko/strings.xml",
"chars": 631,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-ms/strings.xml",
"chars": 656,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">Putar arah berlawanan "
},
{
"path": "cropper/src/main/res/values-nb/strings.xml",
"chars": 710,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-nl/strings.xml",
"chars": 752,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-pl/strings.xml",
"chars": 711,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-pt-rBR/strings.xml",
"chars": 645,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-ru-rRU/strings.xml",
"chars": 572,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">Повернуть налево</stri"
},
{
"path": "cropper/src/main/res/values-sv/strings.xml",
"chars": 710,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-tr/strings.xml",
"chars": 656,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">Saat yönünde döndür</s"
},
{
"path": "cropper/src/main/res/values-ur/strings.xml",
"chars": 653,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"crop_image_menu_rotate_left\">گھڑی وار گھڑی گھومیں</"
},
{
"path": "cropper/src/main/res/values-vi/strings.xml",
"chars": 717,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-zh/strings.xml",
"chars": 626,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-zh-rCN/strings.xml",
"chars": 620,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "cropper/src/main/res/values-zh-rTW/strings.xml",
"chars": 620,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"crop_image_activity_title\"></string>\n <string n"
},
{
"path": "gradle/gradle/wrapper/gradle-wrapper.properties",
"chars": 229,
"preview": "#Thu Jan 16 17:26:55 PST 2014\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle/gradlew",
"chars": 5080,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradle/gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 230,
"preview": "#Fri Apr 06 15:20:13 IDT 2018\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_"
},
{
"path": "gradle.properties",
"chars": 432,
"preview": "#\n# Copyright (c) 2018. DNA Software. All rights reserved.\n#\n# Unless required by applicable law or agreed to in writing"
},
{
"path": "gradlew",
"chars": 5080,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2404,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@r"
},
{
"path": "local.sh",
"chars": 49,
"preview": "#!/bin/sh\ngradle clean build publishToMavenLocal\n"
},
{
"path": "quick-start/build.gradle",
"chars": 472,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion rootProject.compileSdkVersion\n buildToolsVer"
},
{
"path": "quick-start/src/main/AndroidManifest.xml",
"chars": 949,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n package=\"com.theartofdev.edmodo.cropper.quick.start\"\n xmlns:andr"
},
{
"path": "quick-start/src/main/java/com/theartofdev/edmodo/cropper/quick/start/MainActivity.java",
"chars": 2040,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "quick-start/src/main/res/layout/activity_main.xml",
"chars": 1058,
"preview": "<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n "
},
{
"path": "quick-start/src/main/res/values/dimens.xml",
"chars": 212,
"preview": "<resources>\n\n <!-- Default screen margins, per the Android Design guidelines. -->\n <dimen name=\"activity_horizonta"
},
{
"path": "quick-start/src/main/res/values/strings.xml",
"chars": 395,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\">Image Cropper Quick Start</string>\n <s"
},
{
"path": "quick-start/src/main/res/values/styles.xml",
"chars": 25,
"preview": "<resources>\n\n</resources>"
},
{
"path": "release.sh",
"chars": 46,
"preview": "#!/bin/sh\ngradlew clean build generateRelease\n"
},
{
"path": "sample/build.gradle",
"chars": 472,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion rootProject.compileSdkVersion\n buildToolsVer"
},
{
"path": "sample/project.properties",
"chars": 601,
"preview": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# T"
},
{
"path": "sample/src/main/AndroidManifest.xml",
"chars": 1097,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n package=\"com.example.croppersample\"\n xmlns:android=\"http://schem"
},
{
"path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropDemoPreset.java",
"chars": 459,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropImageViewOptions.java",
"chars": 1101,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropResultActivity.java",
"chars": 2521,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/MainActivity.java",
"chars": 13618,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/MainFragment.java",
"chars": 8275,
"preview": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers."
},
{
"path": "sample/src/main/res/drawable/backdrop.xml",
"chars": 343,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <!--"
},
{
"path": "sample/src/main/res/drawable/checkerboard.xml",
"chars": 166,
"preview": "<bitmap\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:dither=\"true\"\n android:src=\"@drawa"
},
{
"path": "sample/src/main/res/drawable/muted.xml",
"chars": 186,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:"
},
{
"path": "sample/src/main/res/layout/activity_crop_result.xml",
"chars": 684,
"preview": "<FrameLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n "
},
{
"path": "sample/src/main/res/layout/activity_main.xml",
"chars": 6603,
"preview": "<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to"
},
{
"path": "sample/src/main/res/layout/fragment_main_customized.xml",
"chars": 726,
"preview": "<com.theartofdev.edmodo.cropper.CropImageView\n android:id=\"@+id/cropImageView\"\n xmlns:android=\"http://schemas.andr"
},
{
"path": "sample/src/main/res/layout/fragment_main_min_max.xml",
"chars": 443,
"preview": "<com.theartofdev.edmodo.cropper.CropImageView\n android:id=\"@+id/cropImageView\"\n xmlns:android=\"http://schemas.andr"
},
{
"path": "sample/src/main/res/layout/fragment_main_oval.xml",
"chars": 366,
"preview": "<com.theartofdev.edmodo.cropper.CropImageView\n android:id=\"@+id/cropImageView\"\n xmlns:android=\"http://schemas.andr"
},
{
"path": "sample/src/main/res/layout/fragment_main_rect.xml",
"chars": 227,
"preview": "<com.theartofdev.edmodo.cropper.CropImageView\n android:id=\"@+id/cropImageView\"\n xmlns:android=\"http://schemas.andr"
},
{
"path": "sample/src/main/res/layout/fragment_main_scale_center.xml",
"chars": 356,
"preview": "<com.theartofdev.edmodo.cropper.CropImageView\n android:id=\"@+id/cropImageView\"\n xmlns:android=\"http://schemas.andr"
},
{
"path": "sample/src/main/res/menu/main.xml",
"chars": 1008,
"preview": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk/res-aut"
},
{
"path": "sample/src/main/res/values/dimens.xml",
"chars": 539,
"preview": "<resources>\n\n <!-- Default screen margins, per the Android Design guidelines. -->\n <dimen name=\"activity_horizonta"
},
{
"path": "sample/src/main/res/values/strings.xml",
"chars": 2123,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <string name=\"app_name\">Image Cropper Demo</string>\n <string "
},
{
"path": "sample/src/main/res/values/styles.xml",
"chars": 1560,
"preview": "<resources>\n\n <style name=\"Cropper.Widget.Drawer.Seperator\" parent=\"android:Widget.DeviceDefault.Light.TextView\">\n "
},
{
"path": "settings.gradle",
"chars": 72,
"preview": "include ':test'\ninclude 'cropper'\ninclude 'sample'\ninclude 'quick-start'"
},
{
"path": "test/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "test/build.gradle",
"chars": 457,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdkVersion rootProject.compileSdkVersion\n buildToolsVer"
},
{
"path": "test/src/main/AndroidManifest.xml",
"chars": 993,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.example.test\"\n xmlns:android=\"http://schemas.andr"
},
{
"path": "test/src/main/java/com/theartofdev/edmodo/cropper/test/MainActivity.java",
"chars": 1525,
"preview": "package com.theartofdev.edmodo.cropper.test;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport androidx.a"
},
{
"path": "test/src/main/res/layout/activity_main.xml",
"chars": 705,
"preview": "<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:layout_width=\"match_parent\"\n "
},
{
"path": "test/src/main/res/values/colors.xml",
"chars": 208,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#3F51B5</color>\n <color name=\"color"
},
{
"path": "test/src/main/res/values/strings.xml",
"chars": 67,
"preview": "<resources>\n <string name=\"app_name\">Test</string>\n</resources>\n"
},
{
"path": "test/src/main/res/values/styles.xml",
"chars": 369,
"preview": "<resources>\n\n <!-- Base application theme. -->\n <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light\">\n <!-"
}
]
// ... and 2 more files (download for full content)
About this extraction
This page contains the full source code of the ArthurHub/Android-Image-Cropper GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 99 files (367.5 KB), approximately 92.5k tokens, and a symbol index with 407 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.