Showing preview only (3,843K chars total). Download the full file or copy to clipboard to get everything.
Repository: Yalantis/uCrop
Branch: develop
Commit: f788b534b48c
Files: 125
Total size: 3.6 MB
Directory structure:
gitextract_w2fudyhs/
├── .gitattributes
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── README.md
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── mavenpush.gradle
├── sample/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── yalantis/
│ │ └── ucrop/
│ │ └── sample/
│ │ ├── BaseActivity.java
│ │ ├── ResultActivity.java
│ │ ├── SampleActivity.java
│ │ └── SampleApp.java
│ └── res/
│ ├── drawable/
│ │ ├── bg_rounded_rectangle.xml
│ │ ├── ic_done.xml
│ │ └── ic_file_download.xml
│ ├── layout/
│ │ ├── activity_result.xml
│ │ ├── activity_sample.xml
│ │ └── include_settings.xml
│ ├── menu/
│ │ └── menu_result.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── xml/
│ └── file_provider_paths.xml
├── settings.gradle
└── ucrop/
├── .gitignore
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── yalantis/
│ └── ucrop/
│ ├── UCrop.java
│ ├── UCropActivity.java
│ ├── UCropFragment.java
│ ├── UCropFragmentCallback.java
│ ├── UCropHttpClientStore.java
│ ├── callback/
│ │ ├── BitmapCropCallback.java
│ │ ├── BitmapLoadCallback.java
│ │ ├── CropBoundsChangeListener.java
│ │ └── OverlayViewChangeListener.java
│ ├── model/
│ │ ├── AspectRatio.java
│ │ ├── CropParameters.java
│ │ ├── ExifInfo.java
│ │ └── ImageState.java
│ ├── task/
│ │ ├── BitmapCropTask.java
│ │ └── BitmapLoadTask.java
│ ├── util/
│ │ ├── BitmapLoadUtils.java
│ │ ├── CubicEasing.java
│ │ ├── EglUtils.java
│ │ ├── FastBitmapDrawable.java
│ │ ├── FileUtils.java
│ │ ├── ImageHeaderParser.java
│ │ ├── RectUtils.java
│ │ ├── RotationGestureDetector.java
│ │ └── SelectedStateListDrawable.java
│ └── view/
│ ├── CropImageView.java
│ ├── GestureCropImageView.java
│ ├── OverlayView.java
│ ├── TransformImageView.java
│ ├── UCropView.java
│ └── widget/
│ ├── AspectRatioTextView.java
│ └── HorizontalProgressWheelView.java
├── jni/
│ ├── Android.mk
│ ├── Application.mk
│ ├── CImg.h
│ ├── com_yalantis_ucrop_task_BitmapCropTask.h
│ └── uCrop.cpp
└── res/
├── anim/
│ ├── ucrop_loader_circle_path.xml
│ └── ucrop_loader_circle_scale.xml
├── color/
│ └── ucrop_scale_text_view_selector.xml
├── drawable/
│ ├── ucrop_crop.xml
│ ├── ucrop_ic_crop.xml
│ ├── ucrop_ic_crop_unselected.xml
│ ├── ucrop_ic_cross.xml
│ ├── ucrop_ic_next.xml
│ ├── ucrop_ic_reset.xml
│ ├── ucrop_ic_rotate.xml
│ ├── ucrop_ic_rotate_unselected.xml
│ ├── ucrop_ic_scale.xml
│ ├── ucrop_ic_scale_unselected.xml
│ ├── ucrop_rotate.xml
│ ├── ucrop_scale.xml
│ ├── ucrop_shadow_upside.xml
│ ├── ucrop_vector_ic_crop.xml
│ ├── ucrop_vector_loader.xml
│ ├── ucrop_vector_loader_animated.xml
│ └── ucrop_wrapper_controls_shape.xml
├── layout/
│ ├── ucrop_activity_photobox.xml
│ ├── ucrop_aspect_ratio.xml
│ ├── ucrop_controls.xml
│ ├── ucrop_fragment_photobox.xml
│ ├── ucrop_layout_rotate_wheel.xml
│ ├── ucrop_layout_scale_wheel.xml
│ └── ucrop_view.xml
├── menu/
│ └── ucrop_menu_activity.xml
├── values/
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── public.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── values.xml
├── values-de/
│ └── strings.xml
├── values-es/
│ └── strings.xml
├── values-fa/
│ └── strings.xml
├── values-fi/
│ └── strings.xml
├── values-fr/
│ └── strings.xml
├── values-in/
│ └── strings.xml
├── values-it/
│ └── strings.xml
├── values-ja/
│ └── strings.xml
├── values-ko/
│ └── strings.xml
├── values-nl/
│ └── strings.xml
├── values-pt/
│ └── strings.xml
├── values-sk/
│ └── strings.xml
├── values-th/
│ └── strings.xml
├── values-tr/
│ └── strings.xml
├── values-zh/
│ └── strings.xml
└── values-zh-rTW/
└── strings.xml
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
ucrop/src/main/jni/CImg.h linguist-vendored
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
**Do you want to request a *feature* or report a *bug*?**
**What is the current behavior?**
**What is the expected behavior?**
**If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.**
**Please attach any *image files*, *URL* and `stack trace` that can be used to reproduce the *bug*.**
**Which versions of uCrop, and which Android API versions are affected by this issue? Did this work in previous versions of uCrop?**
================================================
FILE: .gitignore
================================================
# svn
*.svn*
# built application files
*.apk
*.ap_
# files for the dex VM
*.dex
# Java class files
*.class
# generated GUI files
*/R.java
# generated folder
bin
gen
# local
local.properties
proguard_logs/
# log files
log*.txt
# archives
*.gz
*.tar
*.zip
# eclipse
*.metadata
*.settings
*.prefs
#idea
*.idea
*.iml
out/
build/
captures/
.gradle/
.DS_Store
================================================
FILE: README.md
================================================
# uCrop - Image Cropping Library for Android
#### This project aims to provide an ultimate and flexible image cropping experience. Made in [Yalantis](https://yalantis.com/?utm_source=github)
#### [How We Created uCrop](https://yalantis.com/blog/how-we-created-ucrop-our-own-image-cropping-library-for-android/)
#### Check this [project on Dribbble](https://dribbble.com/shots/2484752-uCrop-Image-Cropping-Library)
<img src="preview.gif" width="800" height="600">
# Usage
*For a working implementation, please have a look at the Sample Project - sample*
<a href="https://play.google.com/store/apps/details?id=com.yalantis.ucrop.sample&utm_source=global_co&utm_medium=prtnr&utm_content=Mar2515&utm_campaign=PartBadge&pcampaignid=MKT-AC-global-none-all-co-pr-py-PartBadges-Oct1515-1"><img alt="Get it on Google Play" src="https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png" width="185" height="70"/></a>
1. Include the library as a local library project.
```
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
```
``` implementation 'com.github.yalantis:ucrop:2.2.11' ``` - lightweight general solution
``` implementation 'com.github.yalantis:ucrop:2.2.11-native' ``` - get power of the native code to preserve image quality (+ about 1.5 MB to an apk size)
2. Add UCropActivity into your AndroidManifest.xml
```
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
```
3. Register a callback for the uCrop result.
```java
private ActivityResultLauncher<Intent> activityResultLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
final Uri resultUri = UCrop.getOutput(result.getData());
} else if (result.getResultCode() == UCrop.RESULT_ERROR) {
final Throwable cropError = UCrop.getError(result.getData());
}
});
```
4. Create the uCrop configuration using the builder pattern.
```java
UCrop.of(sourceUri, destinationUri)
.withAspectRatio(16, 9)
.withMaxResultSize(maxWidth, maxHeight)
.start(context, activityResultLauncher);
```
5. You may want to add this to your PROGUARD config:
```
-dontwarn com.yalantis.ucrop**
-keep class com.yalantis.ucrop** { *; }
-keep interface com.yalantis.ucrop** { *; }
```
# Customization
If you want to let your users choose crop ratio dynamically, just do not call `withAspectRatio(x, y)`.
uCrop builder class has method `withOptions(UCrop.Options options)` which extends library configurations.
Currently, you can change:
* image compression format (e.g. PNG, JPEG), compression
* image compression quality [0 - 100]. PNG which is lossless, will ignore the quality setting.
* whether all gestures are enabled simultaneously
* maximum size for Bitmap that is decoded from source Uri and used within crop view. If you want to override the default behaviour.
* toggle whether to show crop frame/guidelines
* setup color/width/count of crop frame/rows/columns
* choose whether you want rectangle or oval(`options.setCircleDimmedLayer(true)`) crop area
* the UI colors (Toolbar, StatusBar, active widget state)
* and more...
# Compatibility
* Library - Android Lollipop 5.0+ (API 21) (Android ICS 4.0+ (API 14) for versions < 2.2.11)
* Sample - Android Lollipop 5.0+ (API 21)
* CPU - armeabi-v7a x86 x86_64 arm64-v8a (for versions >= 2.2.11)
# Changelog
### Version: 2.2.11
* Updated compileSdk and targetSdk to 36
* Added support for 16 KB page sizes
* Enabled edge-to-edge UI support
* And other improvements
### Version: 2.2.10
* Fixed [#926](https://github.com/Yalantis/uCrop/issues/926)
### Version: 2.2.9
* Update compileSdk and targetSdk versions up to 33
* Fixed [#867](https://github.com/Yalantis/uCrop/issues/867)
* Fixed [#873](https://github.com/Yalantis/uCrop/issues/873)
* And other improvements
### Version: 2.2.8
* Merged pending pull requests with improvements and bugfixes
* Update compileSdk and targetSdk versions up to 31
* Add localizations
* Fixed [#609](https://github.com/Yalantis/uCrop/issues/609)
* Fixed [#794](https://github.com/Yalantis/uCrop/issues/794)
### Version: 2.2.5
* Fixed [#584](https://github.com/Yalantis/uCrop/issues/584)
* Fixed [#598](https://github.com/Yalantis/uCrop/issues/598)
* Fixed [#543](https://github.com/Yalantis/uCrop/issues/543)
* Fixed [#602](https://github.com/Yalantis/uCrop/issues/602)
* And other improvements
### Version: 2.2.4
* **AndroidX migration**
* Redesign
* Several fixes including [#550](https://github.com/Yalantis/uCrop/issues/550)
### Version: 2.2.3
* Several fixes including [#445](https://github.com/Yalantis/uCrop/issues/445), [#465](https://github.com/Yalantis/uCrop/issues/465) and more!
* Material design support
* uCrop fragment as child fragment
* Added the Italian language
### Version: 2.2.2
* uCrop fragment added
* bugfix
### Version: 2.2.1
* Fix including [#285](https://github.com/Yalantis/uCrop/issues/285)
### Version: 2.2
* Several fixes including [#121](https://github.com/Yalantis/uCrop/issues/121), [#173](https://github.com/Yalantis/uCrop/issues/173), [#184](https://github.com/Yalantis/uCrop/issues/184) and more!
* New APIs introduced [#149](https://github.com/Yalantis/uCrop/issues/149), [#186](https://github.com/Yalantis/uCrop/issues/186) and [#156](https://github.com/Yalantis/uCrop/issues/156)
### Version: 2.1
* Fixes issue with EXIF data (images taken on front camera with Samsung devices mostly) [#130](https://github.com/Yalantis/uCrop/issues/130) [#111](https://github.com/Yalantis/uCrop/issues/111)
* Added API to set custom set of aspect ratio options for the user. [#131](https://github.com/Yalantis/uCrop/issues/131)
* Added API to set all configs via UCrop.Options class. [#126](https://github.com/Yalantis/uCrop/issues/126)
* Added ABI x86_64 support. [#105](https://github.com/Yalantis/uCrop/issues/105)
### Version: 2.0
* Native image crop (able to crop high-resolution images, e.g. 16MP & 32MP images on Nexus 5X).
* WebP compression format is not supported at the moment (choose JPEG or PNG).
* Now library copies EXIF data to cropped image (size and orientation are updated).
### Version: 1.5
* Introduced "Freestyle" crop (you can resize crop rectangle by dragging it corners) [#32](https://github.com/Yalantis/uCrop/issues/32)
* Now image & crop view paddings are not associated [#68](https://github.com/Yalantis/uCrop/issues/68)
* Updated API
### Version: 1.4
* Introduced HTTP(s) Uri support!
* Image is cropped in a background thread.
* Showing loader while Bitmap is processed (both loading and cropping).
* Several bug fixes.
* Couple new things to configure.
* Updated minSdkVersion to Android ICS 4.0 (no reason to support couple percents of old phones).
### Version: 1.3
* Image is loaded in a background thread. Better error-handling for image decoding.
* Improved EXIF data support (rotation and mirror).
* Small UI updates.
* Couple new things to configure.
* Sample updated with the possibility to choose custom aspect ratio.
### Version: 1.2
* Updated core logic so an image corrects its position smoothly and obviously.
### Version: 1.1
* UCrop builder was updated and now UCrop.Options class has even more values to setup.
### Version: 1.0
* Initial Build
### Let us know!
We’d be really happy if you sent us links to your projects where you use our component. Just send an email to github@yalantis.com And do let us know if you have any questions or suggestion regarding the library.
#### Apps using uCrop
- [Thirty](https://play.google.com/store/apps/details?id=com.twominds.thirty).
- [Light Smart HD](https://play.google.com/store/apps/details?id=com.SmartCamera.simple).
- [BCReader](https://play.google.com/store/apps/details?id=com.iac.bcreader).
- [Xprezia: Share Your Passion](https://play.google.com/store/apps/details?id=com.xprezzia.cnj).
## License
Copyright 2017, Yalantis
Software doesn't collect, store or transfer data to Yalantis or third parties.
Emplacement of this Software is carried out locally at device.
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: build.gradle
================================================
buildscript {
ext {
androidx_activity_version = "1.10.1"
androidx_appcompat_version = "1.7.1"
androidx_core_version = "1.16.0"
androidx_exifinterface_version = "1.4.1"
androidx_transition_version = "1.6.0"
constraintlayout_version = "2.2.1"
okhttp_version = "5.1.0"
}
repositories {
mavenCentral()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:8.11.1'
}
}
def isReleaseBuild() {
return version.contains("SNAPSHOT") == false
}
allprojects {
version = VERSION_NAME
group = GROUP
repositories {
mavenCentral()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Jul 21 21:21:40 EEST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
VERSION_NAME=2.2.11-native
VERSION_CODE=29
GROUP=com.yalantis
POM_DESCRIPTION=Android Library for cropping images
POM_URL=https://github.com/Yalantis/uCrop
POM_SCM_URL=https://github.com/Yalantis/uCrop
POM_SCM_CONNECTION=scm:git@github.com/Yalantis/uCrop.git
POM_SCM_DEV_CONNECTION=scm:git@github.com/Yalantis/uCrop.git
POM_LICENCE_NAME=The Apache Software License, Version 2.0
POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=yalantis
POM_DEVELOPER_NAME=Yalantis
android.useAndroidX=true
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: jitpack.yml
================================================
# configuration file for building snapshots and releases with jitpack.io
jdk:
- openjdk11
before_install:
- sdk install java 11.0.10-open
- sdk use java 11.0.10-open
================================================
FILE: mavenpush.gradle
================================================
apply plugin: 'maven-publish'
apply plugin: 'signing'
def sonatypeRepositoryUrl
if (isReleaseBuild()) {
println 'RELEASE BUILD'
sonatypeRepositoryUrl = hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
: "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
} else {
println 'DEBUG BUILD'
sonatypeRepositoryUrl = hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
: "https://oss.sonatype.org/content/repositories/snapshots/"
}
def getRepositoryUsername() {
return hasProperty('nexusUsername') ? nexusUsername : ""
}
def getRepositoryPassword() {
return hasProperty('nexusPassword') ? nexusPassword : ""
}
tasks.register('androidJavadocs', Javadoc) {
source = android.sourceSets.main.java.sourceFiles
classpath += project.files(android.getBootClasspath())
}
tasks.register('androidJavadocsJar', Jar) {
archiveClassifier.set("javadoc")
//basename = artifact_id
from tasks.named('androidJavadocs').get().destinationDir
}
tasks.register('androidSourcesJar', Jar) {
archiveClassifier.set("sources")
//basename = artifact_id
from android.sourceSets.main.java.sourceFiles
}
publishing {
repositories {
maven {
url = sonatypeRepositoryUrl
credentials {
username = getRepositoryUsername()
password = getRepositoryPassword()
}
}
}
publications {
maven(MavenPublication) {
afterEvaluate { project ->
def releaseComponent = components.findByName('release')
if (releaseComponent) {
from releaseComponent
}
artifact tasks.named("bundleReleaseAar")
artifact tasks.named('androidSourcesJar').get()
artifact tasks.named('androidJavadocsJar').get()
version = project.version
}
pom {
name = POM_NAME
packaging = POM_PACKAGING
description = POM_DESCRIPTION
url = POM_URL
scm {
url = POM_SCM_URL
connection = POM_SCM_CONNECTION
developerConnection = POM_SCM_DEV_CONNECTION
}
licenses {
license {
name = POM_LICENCE_NAME
url = POM_LICENCE_URL
distribution = POM_LICENCE_DIST
}
}
developers {
developer {
id = POM_DEVELOPER_ID
name = POM_DEVELOPER_NAME
}
}
}
}
}
}
signing {
required { isReleaseBuild() && gradle.taskGraph.hasTask("publishing") }
sign publishing.publications.maven
}
================================================
FILE: sample/.gitignore
================================================
/build
================================================
FILE: sample/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdk 36
namespace = "com.yalantis.ucrop.sample"
defaultConfig {
applicationId "com.yalantis.ucrop.sample"
minSdkVersion 21
targetSdkVersion 36
versionCode 13
versionName "1.2.4"
}
flavorDimensions "default"
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
buildConfig true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
productFlavors {
activity {
buildConfigField("int","RequestMode", "1")
}
fragment {
buildConfigField("int","RequestMode", "2")
}
}
lint {
abortOnError false
}
}
dependencies {
implementation "androidx.activity:activity:${androidx_activity_version}"
implementation "androidx.appcompat:appcompat:${androidx_appcompat_version}"
implementation "androidx.core:core:${androidx_core_version}"
implementation "androidx.constraintlayout:constraintlayout:${constraintlayout_version}"
implementation "com.squareup.okhttp3:okhttp:${okhttp_version}"
implementation project(':ucrop')
}
================================================
FILE: sample/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/oleksii/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: sample/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yalantis.ucrop.sample">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".SampleApp"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="@string/file_provider_authorities"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
<activity
android:name=".SampleActivity"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ResultActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
</application>
</manifest>
================================================
FILE: sample/src/main/java/com/yalantis/ucrop/sample/BaseActivity.java
================================================
package com.yalantis.ucrop.sample;
import android.content.DialogInterface;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
/**
* Created by Oleksii Shliama (https://github.com/shliama).
*/
public class BaseActivity extends AppCompatActivity {
protected static final int REQUEST_STORAGE_READ_ACCESS_PERMISSION = 101;
protected static final int REQUEST_STORAGE_WRITE_ACCESS_PERMISSION = 102;
private AlertDialog mAlertDialog;
/**
* Hide alert dialog if any.
*/
@Override
protected void onStop() {
super.onStop();
if (mAlertDialog != null && mAlertDialog.isShowing()) {
mAlertDialog.dismiss();
}
}
/**
* Requests given permission.
* If the permission has been denied previously, a Dialog will prompt the user to grant the
* permission, otherwise it is requested directly.
*/
protected void requestPermission(final String permission, String rationale, final int requestCode) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
showAlertDialog(getString(R.string.permission_title_rationale), rationale,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(BaseActivity.this,
new String[]{permission}, requestCode);
}
}, getString(R.string.label_ok), null, getString(R.string.label_cancel));
} else {
ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
}
}
/**
* This method shows dialog with given title & message.
* Also there is an option to pass onClickListener for positive & negative button.
*
* @param title - dialog title
* @param message - dialog message
* @param onPositiveButtonClickListener - listener for positive button
* @param positiveText - positive button text
* @param onNegativeButtonClickListener - listener for negative button
* @param negativeText - negative button text
*/
protected void showAlertDialog(@Nullable String title, @Nullable String message,
@Nullable DialogInterface.OnClickListener onPositiveButtonClickListener,
@NonNull String positiveText,
@Nullable DialogInterface.OnClickListener onNegativeButtonClickListener,
@NonNull String negativeText) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton(positiveText, onPositiveButtonClickListener);
builder.setNegativeButton(negativeText, onNegativeButtonClickListener);
mAlertDialog = builder.show();
}
}
================================================
FILE: sample/src/main/java/com/yalantis/ucrop/sample/ResultActivity.java
================================================
package com.yalantis.ucrop.sample;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.yalantis.ucrop.view.UCropView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.util.Calendar;
import java.util.List;
import androidx.activity.EdgeToEdge;
import androidx.activity.SystemBarStyle;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.content.FileProvider;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
/**
* Created by Oleksii Shliama (https://github.com/shliama).
*/
public class ResultActivity extends BaseActivity {
private static final String TAG = "ResultActivity";
private static final String CHANNEL_ID = "3000";
private static final int DOWNLOAD_NOTIFICATION_ID_DONE = 911;
public static void startWithUri(@NonNull Context context, @NonNull Uri uri) {
Intent intent = new Intent(context, ResultActivity.class);
intent.setData(uri);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this, SystemBarStyle.dark(Color.TRANSPARENT), SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT));
setContentView(R.layout.activity_result);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.toolbar), (view, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
view.setPaddingRelative(insets.left, insets.top, insets.right, 0);
return windowInsets;
});
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.ucrop), (view, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
view.setPaddingRelative(insets.left, 0, insets.right, insets.bottom);
return windowInsets;
});
Uri uri = getIntent().getData();
if (uri != null) {
try {
UCropView uCropView = findViewById(R.id.ucrop);
uCropView.getCropImageView().setImageUri(uri, null);
uCropView.getOverlayView().setShowCropFrame(false);
uCropView.getOverlayView().setShowCropGrid(false);
uCropView.getOverlayView().setDimmedColor(Color.TRANSPARENT);
} catch (Exception e) {
Log.e(TAG, "setImageUri", e);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
}
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(new File(getIntent().getData().getPath()).getAbsolutePath(), options);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(getString(R.string.format_crop_result_d_d, options.outWidth, options.outHeight));
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_result, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_download) {
saveCroppedImage();
} else if (item.getItemId() == android.R.id.home) {
onBackPressed();
}
return super.onOptionsItemSelected(item);
}
/**
* Callback received when a permissions request has been completed.
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_STORAGE_WRITE_ACCESS_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
saveCroppedImage();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
private void saveCroppedImage() {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
getString(R.string.permission_write_storage_rationale),
REQUEST_STORAGE_WRITE_ACCESS_PERMISSION);
} else {
Uri imageUri = getIntent().getData();
if (imageUri != null && imageUri.getScheme().equals("file")) {
try {
copyFileToDownloads(getIntent().getData());
} catch (Exception e) {
Toast.makeText(ResultActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, imageUri.toString(), e);
}
} else {
Toast.makeText(ResultActivity.this, getString(R.string.toast_unexpected_error), Toast.LENGTH_SHORT).show();
}
}
}
private void copyFileToDownloads(Uri croppedFileUri) throws Exception {
String downloadsDirectoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
String filename = String.format("%d_%s", Calendar.getInstance().getTimeInMillis(), croppedFileUri.getLastPathSegment());
File saveFile = new File(downloadsDirectoryPath, filename);
FileInputStream inStream = new FileInputStream(new File(croppedFileUri.getPath()));
FileOutputStream outStream = new FileOutputStream(saveFile);
FileChannel inChannel = inStream.getChannel();
FileChannel outChannel = outStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inStream.close();
outStream.close();
showNotification(saveFile);
Toast.makeText(this, R.string.notification_image_saved, Toast.LENGTH_SHORT).show();
finish();
}
private void showNotification(@NonNull File file) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Uri fileUri = FileProvider.getUriForFile(
this,
getString(R.string.file_provider_authorities),
file);
intent.setDataAndType(fileUri, "image/*");
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(
intent,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo info : resInfoList) {
grantUriPermission(
info.activityInfo.packageName,
fileUri, FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_READ_URI_PERMISSION);
}
NotificationCompat.Builder notificationBuilder;
NotificationManager notificationManager = (NotificationManager) this
.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (notificationManager != null) {
notificationManager.createNotificationChannel(createChannel());
}
notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
} else {
notificationBuilder = new NotificationCompat.Builder(this);
}
notificationBuilder
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.notification_image_saved_click_to_preview))
.setTicker(getString(R.string.notification_image_saved))
.setSmallIcon(R.drawable.ic_done)
.setOngoing(false)
.setContentIntent(PendingIntent.getActivity(this, 0, intent, 0))
.setAutoCancel(true);
if (notificationManager != null) {
notificationManager.notify(DOWNLOAD_NOTIFICATION_ID_DONE, notificationBuilder.build());
}
}
@TargetApi(Build.VERSION_CODES.O)
public NotificationChannel createChannel() {
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, getString(R.string.channel_name), importance);
channel.setDescription(getString(R.string.channel_description));
channel.enableLights(true);
channel.setLightColor(Color.YELLOW);
return channel;
}
}
================================================
FILE: sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java
================================================
package com.yalantis.ucrop.sample;
import android.annotation.TargetApi;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.ScrollView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.activity.SystemBarStyle;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.yalantis.ucrop.UCrop;
import com.yalantis.ucrop.UCropActivity;
import com.yalantis.ucrop.UCropFragment;
import com.yalantis.ucrop.UCropFragmentCallback;
import java.io.File;
import java.util.Locale;
import java.util.Random;
/**
* Created by Oleksii Shliama (https://github.com/shliama).
*/
public class SampleActivity extends BaseActivity implements UCropFragmentCallback {
private static final String TAG = "SampleActivity";
private static final int REQUEST_SELECT_PICTURE = 0x01;
private static final int REQUEST_SELECT_PICTURE_FOR_FRAGMENT = 0x02;
private static final String SAMPLE_CROPPED_IMAGE_NAME = "SampleCropImage";
private RadioGroup mRadioGroupAspectRatio, mRadioGroupCompressionSettings;
private EditText mEditTextMaxWidth, mEditTextMaxHeight;
private EditText mEditTextRatioX, mEditTextRatioY;
private CheckBox mCheckBoxMaxSize;
private SeekBar mSeekBarQuality;
private TextView mTextViewQuality;
private CheckBox mCheckBoxHideBottomControls;
private CheckBox mCheckBoxFreeStyleCrop;
private Toolbar toolbar;
private ScrollView settingsView;
private int requestMode = BuildConfig.RequestMode;
private UCropFragment fragment;
private boolean mShowLoader;
private String mToolbarTitle;
@DrawableRes
private int mToolbarCancelDrawable;
@DrawableRes
private int mToolbarCropDrawable;
// Enables dynamic coloring
private int mToolbarColor;
private int mToolbarWidgetColor;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this, SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT), SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT));
setContentView(R.layout.activity_sample);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.settings), (view, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
view.setPaddingRelative(insets.left, insets.top, insets.right, insets.bottom);
return windowInsets;
});
setupUI();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == requestMode) {
final Uri selectedUri = data.getData();
if (selectedUri != null) {
startCrop(selectedUri);
} else {
Toast.makeText(SampleActivity.this, R.string.toast_cannot_retrieve_selected_image, Toast.LENGTH_SHORT).show();
}
} else if (requestCode == UCrop.REQUEST_CROP) {
handleCropResult(data);
}
}
if (resultCode == UCrop.RESULT_ERROR) {
handleCropError(data);
}
}
private TextWatcher mAspectRatioTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
mRadioGroupAspectRatio.clearCheck();
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
}
};
private TextWatcher mMaxSizeTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s != null && !s.toString().trim().isEmpty()) {
if (Integer.valueOf(s.toString()) < UCrop.MIN_SIZE) {
Toast.makeText(SampleActivity.this, String.format(getString(R.string.format_max_cropped_image_size), UCrop.MIN_SIZE), Toast.LENGTH_SHORT).show();
}
}
}
};
@SuppressWarnings("ConstantConditions")
private void setupUI() {
findViewById(R.id.button_crop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
pickFromGallery();
}
});
findViewById(R.id.button_random_image).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Random random = new Random();
int minSizePixels = 800;
int maxSizePixels = 2400;
Uri uri = Uri.parse(String.format(Locale.getDefault(), "https://unsplash.it/%d/%d/?random",
minSizePixels + random.nextInt(maxSizePixels - minSizePixels),
minSizePixels + random.nextInt(maxSizePixels - minSizePixels)));
startCrop(uri);
}
});
settingsView = findViewById(R.id.settings);
mRadioGroupAspectRatio = findViewById(R.id.radio_group_aspect_ratio);
mRadioGroupCompressionSettings = findViewById(R.id.radio_group_compression_settings);
mCheckBoxMaxSize = findViewById(R.id.checkbox_max_size);
mEditTextRatioX = findViewById(R.id.edit_text_ratio_x);
mEditTextRatioY = findViewById(R.id.edit_text_ratio_y);
mEditTextMaxWidth = findViewById(R.id.edit_text_max_width);
mEditTextMaxHeight = findViewById(R.id.edit_text_max_height);
mSeekBarQuality = findViewById(R.id.seekbar_quality);
mTextViewQuality = findViewById(R.id.text_view_quality);
mCheckBoxHideBottomControls = findViewById(R.id.checkbox_hide_bottom_controls);
mCheckBoxFreeStyleCrop = findViewById(R.id.checkbox_freestyle_crop);
mRadioGroupAspectRatio.check(R.id.radio_dynamic);
mEditTextRatioX.addTextChangedListener(mAspectRatioTextWatcher);
mEditTextRatioY.addTextChangedListener(mAspectRatioTextWatcher);
mRadioGroupCompressionSettings.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
mSeekBarQuality.setEnabled(checkedId == R.id.radio_jpeg);
}
});
mRadioGroupCompressionSettings.check(R.id.radio_jpeg);
mSeekBarQuality.setProgress(UCropActivity.DEFAULT_COMPRESS_QUALITY);
mTextViewQuality.setText(String.format(getString(R.string.format_quality_d), mSeekBarQuality.getProgress()));
mSeekBarQuality.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
mTextViewQuality.setText(String.format(getString(R.string.format_quality_d), progress));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mEditTextMaxHeight.addTextChangedListener(mMaxSizeTextWatcher);
mEditTextMaxWidth.addTextChangedListener(mMaxSizeTextWatcher);
}
private void pickFromGallery() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
.setType("image/*")
.addCategory(Intent.CATEGORY_OPENABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
String[] mimeTypes = {"image/jpeg", "image/png"};
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
}
startActivityForResult(Intent.createChooser(intent, getString(R.string.label_select_picture)), requestMode);
}
private void startCrop(@NonNull Uri uri) {
String destinationFileName = SAMPLE_CROPPED_IMAGE_NAME;
int checkedId = mRadioGroupCompressionSettings.getCheckedRadioButtonId();
if (checkedId == R.id.radio_png) {
destinationFileName += ".png";
} else if (checkedId == R.id.radio_jpeg) {
destinationFileName += ".jpg";
}
UCrop uCrop = UCrop.of(uri, Uri.fromFile(new File(getCacheDir(), destinationFileName)));
uCrop = basisConfig(uCrop);
uCrop = advancedConfig(uCrop);
if (requestMode == REQUEST_SELECT_PICTURE_FOR_FRAGMENT) { //if build variant = fragment
setupFragment(uCrop);
} else { // else start uCrop Activity
uCrop.start(SampleActivity.this);
}
}
/**
* In most cases you need only to set crop aspect ration and max size for resulting image.
*
* @param uCrop - ucrop builder instance
* @return - ucrop builder instance
*/
private UCrop basisConfig(@NonNull UCrop uCrop) {
int checkedId = mRadioGroupAspectRatio.getCheckedRadioButtonId();
if (checkedId == R.id.radio_origin) {
uCrop = uCrop.useSourceImageAspectRatio();
} else if (checkedId == R.id.radio_square) {
uCrop = uCrop.withAspectRatio(1, 1);
} else if (checkedId == R.id.radio_dynamic) {
// do nothing
} else {
try {
float ratioX = Float.valueOf(mEditTextRatioX.getText().toString().trim());
float ratioY = Float.valueOf(mEditTextRatioY.getText().toString().trim());
if (ratioX > 0 && ratioY > 0) {
uCrop = uCrop.withAspectRatio(ratioX, ratioY);
}
} catch (NumberFormatException e) {
Log.i(TAG, String.format("Number please: %s", e.getMessage()));
}
}
if (mCheckBoxMaxSize.isChecked()) {
try {
int maxWidth = Integer.valueOf(mEditTextMaxWidth.getText().toString().trim());
int maxHeight = Integer.valueOf(mEditTextMaxHeight.getText().toString().trim());
if (maxWidth > UCrop.MIN_SIZE && maxHeight > UCrop.MIN_SIZE) {
uCrop = uCrop.withMaxResultSize(maxWidth, maxHeight);
}
} catch (NumberFormatException e) {
Log.e(TAG, "Number please", e);
}
}
return uCrop;
}
/**
* Sometimes you want to adjust more options, it's done via {@link com.yalantis.ucrop.UCrop.Options} class.
*
* @param uCrop - ucrop builder instance
* @return - ucrop builder instance
*/
private UCrop advancedConfig(@NonNull UCrop uCrop) {
UCrop.Options options = new UCrop.Options();
int checkedId = mRadioGroupCompressionSettings.getCheckedRadioButtonId();
if (checkedId == R.id.radio_png) {
options.setCompressionFormat(Bitmap.CompressFormat.PNG);
} else {
options.setCompressionFormat(Bitmap.CompressFormat.JPEG);
}
options.setCompressionQuality(mSeekBarQuality.getProgress());
options.setHideBottomControls(mCheckBoxHideBottomControls.isChecked());
options.setFreeStyleCropEnabled(mCheckBoxFreeStyleCrop.isChecked());
/*
If you want to configure how gestures work for all UCropActivity tabs
options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL);
* */
/*
This sets max size for bitmap that will be decoded from source Uri.
More size - more memory allocation, default implementation uses screen diagonal.
options.setMaxBitmapSize(640);
* */
/*
Tune everything (ノ◕ヮ◕)ノ*:・゚✧
options.setMaxScaleMultiplier(5);
options.setImageToCropBoundsAnimDuration(666);
options.setDimmedLayerColor(Color.CYAN);
options.setCircleDimmedLayer(true);
options.setShowCropFrame(false);
options.setCropGridStrokeWidth(20);
options.setCropGridColor(Color.GREEN);
options.setCropGridColumnCount(2);
options.setCropGridRowCount(1);
options.setToolbarCropDrawable(R.drawable.your_crop_icon);
options.setToolbarCancelDrawable(R.drawable.your_cancel_icon);
// System bars appearance
options.setStatusBarLight(true);
options.setNavigationBarLight(false);
// Color palette
options.setToolbarColor(ContextCompat.getColor(this, R.color.your_color_res));
options.setToolbarWidgetColor(ContextCompat.getColor(this, R.color.your_color_res));
options.setRootViewBackgroundColor(ContextCompat.getColor(this, R.color.your_color_res));
options.setActiveControlsWidgetColor(ContextCompat.getColor(this, R.color.your_color_res));
// Aspect ratio options
options.setAspectRatioOptions(2,
new AspectRatio("WOW", 1, 2),
new AspectRatio("MUCH", 3, 4),
new AspectRatio("RATIO", CropImageView.DEFAULT_ASPECT_RATIO, CropImageView.DEFAULT_ASPECT_RATIO),
new AspectRatio("SO", 16, 9),
new AspectRatio("ASPECT", 1, 1));
options.withAspectRatio(CropImageView.DEFAULT_ASPECT_RATIO, CropImageView.DEFAULT_ASPECT_RATIO);
options.useSourceImageAspectRatio();
*/
return uCrop.withOptions(options);
}
private void handleCropResult(@NonNull Intent result) {
final Uri resultUri = UCrop.getOutput(result);
if (resultUri != null) {
ResultActivity.startWithUri(SampleActivity.this, resultUri);
} else {
Toast.makeText(SampleActivity.this, R.string.toast_cannot_retrieve_cropped_image, Toast.LENGTH_SHORT).show();
}
}
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
private void handleCropError(@NonNull Intent result) {
final Throwable cropError = UCrop.getError(result);
if (cropError != null) {
Log.e(TAG, "handleCropError: ", cropError);
Toast.makeText(SampleActivity.this, cropError.getMessage(), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(SampleActivity.this, R.string.toast_unexpected_error, Toast.LENGTH_SHORT).show();
}
}
@Override
public void loadingProgress(boolean showLoader) {
mShowLoader = showLoader;
supportInvalidateOptionsMenu();
}
@Override
public void onCropFinish(UCropFragment.UCropResult result) {
switch (result.mResultCode) {
case RESULT_OK:
handleCropResult(result.mResultData);
break;
case UCrop.RESULT_ERROR:
handleCropError(result.mResultData);
break;
}
removeFragmentFromScreen();
}
public void removeFragmentFromScreen() {
getSupportFragmentManager().beginTransaction()
.remove(fragment)
.commit();
toolbar.setVisibility(View.GONE);
settingsView.setVisibility(View.VISIBLE);
}
public void setupFragment(UCrop uCrop) {
fragment = uCrop.getFragment(uCrop.getIntent(this).getExtras());
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment, UCropFragment.TAG)
.commitAllowingStateLoss();
setupViews(uCrop.getIntent(this).getExtras());
}
public void setupViews(Bundle args) {
settingsView.setVisibility(View.GONE);
mToolbarColor = args.getInt(UCrop.Options.EXTRA_TOOL_BAR_COLOR, ContextCompat.getColor(this, com.yalantis.ucrop.R.color.ucrop_color_toolbar));
mToolbarCancelDrawable = args.getInt(UCrop.Options.EXTRA_UCROP_WIDGET_CANCEL_DRAWABLE, com.yalantis.ucrop.R.drawable.ucrop_ic_cross);
mToolbarCropDrawable = args.getInt(UCrop.Options.EXTRA_UCROP_WIDGET_CROP_DRAWABLE, com.yalantis.ucrop.R.drawable.ucrop_ic_done);
mToolbarWidgetColor = args.getInt(UCrop.Options.EXTRA_UCROP_WIDGET_COLOR_TOOLBAR, ContextCompat.getColor(this, com.yalantis.ucrop.R.color.ucrop_color_toolbar_widget));
mToolbarTitle = args.getString(UCrop.Options.EXTRA_UCROP_TITLE_TEXT_TOOLBAR);
mToolbarTitle = mToolbarTitle != null ? mToolbarTitle : getResources().getString(com.yalantis.ucrop.R.string.ucrop_label_edit_photo);
setupAppBar();
}
/**
* Configures and styles both status bar and toolbar.
*/
private void setupAppBar() {
toolbar = findViewById(R.id.toolbar);
// Set all of the Toolbar coloring
toolbar.setBackgroundColor(mToolbarColor);
toolbar.setTitleTextColor(mToolbarWidgetColor);
toolbar.setVisibility(View.VISIBLE);
final TextView toolbarTitle = toolbar.findViewById(R.id.toolbar_title);
toolbarTitle.setTextColor(mToolbarWidgetColor);
toolbarTitle.setText(mToolbarTitle);
// Color buttons inside the Toolbar
Drawable stateButtonDrawable = ContextCompat.getDrawable(getBaseContext(), mToolbarCancelDrawable);
if (stateButtonDrawable != null) {
stateButtonDrawable.mutate();
stateButtonDrawable.setColorFilter(mToolbarWidgetColor, PorterDuff.Mode.SRC_ATOP);
toolbar.setNavigationIcon(stateButtonDrawable);
}
setSupportActionBar(toolbar);
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
}
/**
* Sets status-bar color for L devices.
*
* @param color - status-bar color
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void setStatusBarColor(@ColorInt int color) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
final Window window = getWindow();
if (window != null) {
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(color);
}
}
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(com.yalantis.ucrop.R.menu.ucrop_menu_activity, menu);
// Change crop & loader menu icons color to match the rest of the UI colors
MenuItem menuItemLoader = menu.findItem(com.yalantis.ucrop.R.id.menu_loader);
Drawable menuItemLoaderIcon = menuItemLoader.getIcon();
if (menuItemLoaderIcon != null) {
try {
menuItemLoaderIcon.mutate();
menuItemLoaderIcon.setColorFilter(mToolbarWidgetColor, PorterDuff.Mode.SRC_ATOP);
menuItemLoader.setIcon(menuItemLoaderIcon);
} catch (IllegalStateException e) {
Log.i(this.getClass().getName(), String.format("%s - %s", e.getMessage(), getString(com.yalantis.ucrop.R.string.ucrop_mutate_exception_hint)));
}
((Animatable) menuItemLoader.getIcon()).start();
}
MenuItem menuItemCrop = menu.findItem(com.yalantis.ucrop.R.id.menu_crop);
Drawable menuItemCropIcon = ContextCompat.getDrawable(this, mToolbarCropDrawable == 0 ? com.yalantis.ucrop.R.drawable.ucrop_ic_done : mToolbarCropDrawable);
if (menuItemCropIcon != null) {
menuItemCropIcon.mutate();
menuItemCropIcon.setColorFilter(mToolbarWidgetColor, PorterDuff.Mode.SRC_ATOP);
menuItemCrop.setIcon(menuItemCropIcon);
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(com.yalantis.ucrop.R.id.menu_crop).setVisible(!mShowLoader);
menu.findItem(com.yalantis.ucrop.R.id.menu_loader).setVisible(mShowLoader);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == com.yalantis.ucrop.R.id.menu_crop) {
if (fragment != null && fragment.isAdded())
fragment.cropAndSaveImage();
} else if (item.getItemId() == android.R.id.home) {
removeFragmentFromScreen();
}
return super.onOptionsItemSelected(item);
}
}
================================================
FILE: sample/src/main/java/com/yalantis/ucrop/sample/SampleApp.java
================================================
package com.yalantis.ucrop.sample;
import android.app.Application;
import com.yalantis.ucrop.UCropHttpClientStore;
import java.util.Collections;
import okhttp3.ConnectionSpec;
import okhttp3.OkHttpClient;
public class SampleApp extends Application {
@Override
public void onCreate() {
super.onCreate();
setUcropHttpClient();
}
private void setUcropHttpClient() {
ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.allEnabledCipherSuites()
.allEnabledTlsVersions()
.build();
OkHttpClient client = new OkHttpClient.Builder()
.connectionSpecs(Collections.singletonList(cs))
.build();
UCropHttpClientStore.INSTANCE.setClient(client);
}
}
================================================
FILE: sample/src/main/res/drawable/bg_rounded_rectangle.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners
android:bottomLeftRadius="3dp"
android:bottomRightRadius="3dp"
android:radius="1dp"
android:topLeftRadius="3dp"
android:topRightRadius="3dp"/>
<stroke
android:width="1dp"
android:color="@color/colorAccent"/>
<solid android:color="@android:color/transparent"/>
</shape>
================================================
FILE: sample/src/main/res/drawable/ic_done.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector>
================================================
FILE: sample/src/main/res/drawable/ic_file_download.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#fff"
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/>
</vector>
================================================
FILE: sample/src/main/res/layout/activity_result.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?colorAccent"
android:minHeight="?attr/actionBarSize"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:title="@string/format_crop_result_d_d"
app:titleTextColor="@android:color/white" />
<com.yalantis.ucrop.view.UCropView
android:id="@+id/ucrop"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
================================================
FILE: sample/src/main/res/layout/activity_sample.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/toolbar_title"
style="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</androidx.appcompat.widget.Toolbar>
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" />
<include
android:id="@+id/settings"
layout="@layout/include_settings" />
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: sample/src/main/res/layout/include_settings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:fillViewport="true">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true">
<LinearLayout
android:id="@+id/wrapper_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:background="@drawable/bg_rounded_rectangle"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/app_name"
android:textColor="@color/colorAccent"
android:textSize="42sp"
android:textStyle="bold" />
<Button
android:id="@+id/button_crop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/button_pick_amp_crop"
android:textAllCaps="true"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="@android:color/white"
android:textStyle="bold" />
<Button
android:id="@+id/button_random_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/button_crop_random_image"
android:textAllCaps="true"
android:textAppearance="?android:textAppearanceMedium"
android:textColor="@android:color/white"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="5dp"
android:background="@color/colorAccent" />
<RadioGroup
android:id="@+id/radio_group_aspect_ratio"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/label_aspect_ratio"
android:textAppearance="?android:textAppearanceSmall" />
<CheckBox
android:id="@+id/checkbox_freestyle_crop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_freestyle_crop"
android:textAppearance="?android:textAppearanceMedium" />
<RadioButton
android:id="@+id/radio_dynamic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_dynamic"
android:textAppearance="?android:textAppearanceMedium"
tools:checked="true" />
<RadioButton
android:id="@+id/radio_origin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_image_source"
android:textAppearance="?android:textAppearanceMedium" />
<RadioButton
android:id="@+id/radio_square"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_square"
android:textAppearance="?android:textAppearanceMedium" />
<LinearLayout
android:layout_width="140dp"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/edit_text_ratio_x"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:hint="x"
android:inputType="numberDecimal" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="to"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/edit_text_ratio_y"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:hint="y"
android:inputType="numberDecimal" />
</LinearLayout>
</RadioGroup>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="5dp"
android:background="@color/colorAccent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/label_max_cropped_image_size"
android:textAppearance="?android:textAppearanceSmall" />
<CheckBox
android:id="@+id/checkbox_max_size"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_resize_image_to_max_size"
android:textAppearance="?android:textAppearanceMedium" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/edit_text_max_width"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLength="9"
android:gravity="center"
android:hint="@string/label_width"
android:inputType="number" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="x"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/edit_text_max_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLength="9"
android:gravity="center"
android:hint="@string/label_height"
android:inputType="number" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="5dp"
android:background="@color/colorAccent" />
<RadioGroup
android:id="@+id/radio_group_compression_settings"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/label_compression_settings"
android:textAppearance="?android:textAppearanceSmall" />
<RadioButton
android:id="@+id/radio_jpeg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="JPEG"
android:textAppearance="?android:textAppearanceMedium"
tools:checked="true"
tools:ignore="HardcodedText" />
<RadioButton
android:id="@+id/radio_png"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="PNG"
android:textAppearance="?android:textAppearanceMedium"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/text_view_quality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:textAppearanceSmall"
tools:text="Quality: 90" />
<SeekBar
android:id="@+id/seekbar_quality"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:progress="90" />
</RadioGroup>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="5dp"
android:background="@color/colorAccent" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/label_ui"
android:textAppearance="?android:textAppearanceSmall" />
<CheckBox
android:id="@+id/checkbox_hide_bottom_controls"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/label_hide_bottom_ui_controls"
android:textAppearance="?android:textAppearanceMedium" />
</LinearLayout>
</FrameLayout>
</ScrollView>
================================================
FILE: sample/src/main/res/menu/menu_result.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_download"
app:showAsAction="always"
android:icon="@drawable/ic_file_download"
android:title="@string/menu_download"/>
</menu>
================================================
FILE: sample/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FF6E40</color>
<color name="colorPrimaryDark">#CC5833</color>
<color name="colorAccent">#FF6E40</color>
<color name="your_color_res">#03A9F4</color>
</resources>
================================================
FILE: sample/src/main/res/values/dimens.xml
================================================
<resources>
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
================================================
FILE: sample/src/main/res/values/strings.xml
================================================
<resources>
<string name="app_name">uCrop</string>
<string name="label_sample">sample</string>
<string name="label_aspect_ratio">Aspect ratio</string>
<string name="label_dynamic">Dynamic</string>
<string name="label_image_source">Image source</string>
<string name="label_square">Square</string>
<string name="label_max_cropped_image_size">Max cropped image size</string>
<string name="label_compression_settings">Compression settings</string>
<string name="label_ui">UI</string>
<string name="label_width">Width</string>
<string name="label_height">Height</string>
<string name="label_resize_image_to_max_size">Resize image to max size</string>
<string name="label_hide_bottom_ui_controls">Hide bottom UI controls</string>
<string name="label_freestyle_crop">Freestyle crop</string>
<string name="label_select_picture">Select Picture</string>
<string name="label_cancel">Cancel</string>
<string name="label_ok">OK</string>
<string name="notification_image_saved">Image saved</string>
<string name="notification_image_saved_click_to_preview">Image saved. Click to preview.</string>
<string name="menu_download">Download</string>
<string name="button_pick_amp_crop"><![CDATA[Pick & Crop]]></string>
<string name="button_crop_random_image">Crop random image</string>
<string name="description_cropped_image">Cropped image</string>
<string name="format_crop_result_d_d">Crop Result (%1$dx%2$d)</string>
<string name="format_quality_d">Quality: %d</string>
<string name="format_d_d_px">%1$dx%2$d px</string>
<string name="format_max_cropped_image_size">Max cropped image size cannot be less then %d</string>
<string name="format_max_cropped_image_size_resolution">Too big resolution</string>
<string name="permission_title_rationale">Permission needed</string>
<string name="permission_read_storage_rationale">Storage read permission is needed to pick files.</string>
<string name="permission_write_storage_rationale">Storage write permission is needed to save the image.</string>
<string name="toast_cannot_retrieve_selected_image">Cannot retrieve selected image</string>
<string name="toast_cannot_retrieve_cropped_image">Cannot retrieve cropped image</string>
<string name="toast_unexpected_error">Unexpected error</string>
<string name="file_provider_authorities">com.yalantis.ucrop.provider</string>
<string name="channel_name">ucrop_chanel</string>
<string name="channel_description">ucrop result image</string>
</resources>
================================================
FILE: sample/src/main/res/values/themes.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorButtonNormal">@color/colorAccent</item>
</style>
</resources>
================================================
FILE: sample/src/main/res/xml/file_provider_paths.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path name="Pictures" path="."/>
</paths>
================================================
FILE: settings.gradle
================================================
include ':ucrop', ':sample'
================================================
FILE: ucrop/.gitignore
================================================
/build
================================================
FILE: ucrop/build.gradle
================================================
apply plugin: 'com.android.library'
apply from: '../mavenpush.gradle'
android {
compileSdk 36
namespace = "com.yalantis.ucrop"
defaultConfig {
minSdkVersion 21
targetSdkVersion 36
versionCode 27
versionName "2.2.11-native"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
buildConfig true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
resourcePrefix 'ucrop_'
sourceSets.main {
jni.srcDirs = []
}
lint {
abortOnError false
}
}
dependencies {
implementation "androidx.appcompat:appcompat:${androidx_appcompat_version}"
implementation "androidx.exifinterface:exifinterface:${androidx_exifinterface_version}"
implementation "androidx.transition:transition:${androidx_transition_version}"
implementation "com.squareup.okhttp3:okhttp:${okhttp_version}"
}
================================================
FILE: ucrop/gradle.properties
================================================
POM_NAME=uCrop
POM_ARTIFACT_ID=ucrop
POM_PACKAGING=aar
================================================
FILE: ucrop/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/oleksii/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: ucrop/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.yalantis.ucrop"/>
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/UCrop.java
================================================
package com.yalantis.ucrop;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.yalantis.ucrop.model.AspectRatio;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
/**
* Created by Oleksii Shliama (https://github.com/shliama).
* <p/>
* Builder class to ease Intent setup.
*/
public class UCrop {
public static final int REQUEST_CROP = 69;
public static final int RESULT_ERROR = 96;
public static final int MIN_SIZE = 10;
private static final String EXTRA_PREFIX = BuildConfig.LIBRARY_PACKAGE_NAME;
public static final String EXTRA_INPUT_URI = EXTRA_PREFIX + ".InputUri";
public static final String EXTRA_OUTPUT_URI = EXTRA_PREFIX + ".OutputUri";
public static final String EXTRA_OUTPUT_CROP_ASPECT_RATIO = EXTRA_PREFIX + ".CropAspectRatio";
public static final String EXTRA_OUTPUT_IMAGE_WIDTH = EXTRA_PREFIX + ".ImageWidth";
public static final String EXTRA_OUTPUT_IMAGE_HEIGHT = EXTRA_PREFIX + ".ImageHeight";
public static final String EXTRA_OUTPUT_OFFSET_X = EXTRA_PREFIX + ".OffsetX";
public static final String EXTRA_OUTPUT_OFFSET_Y = EXTRA_PREFIX + ".OffsetY";
public static final String EXTRA_ERROR = EXTRA_PREFIX + ".Error";
public static final String EXTRA_ASPECT_RATIO_X = EXTRA_PREFIX + ".AspectRatioX";
public static final String EXTRA_ASPECT_RATIO_Y = EXTRA_PREFIX + ".AspectRatioY";
public static final String EXTRA_MAX_SIZE_X = EXTRA_PREFIX + ".MaxSizeX";
public static final String EXTRA_MAX_SIZE_Y = EXTRA_PREFIX + ".MaxSizeY";
private Intent mCropIntent;
private Bundle mCropOptionsBundle;
/**
* This method creates new Intent builder and sets both source and destination image URIs.
*
* @param source Uri for image to crop
* @param destination Uri for saving the cropped image
*/
public static UCrop of(@NonNull Uri source, @NonNull Uri destination) {
return new UCrop(source, destination);
}
private UCrop(@NonNull Uri source, @NonNull Uri destination) {
mCropIntent = new Intent();
mCropOptionsBundle = new Bundle();
mCropOptionsBundle.putParcelable(EXTRA_INPUT_URI, source);
mCropOptionsBundle.putParcelable(EXTRA_OUTPUT_URI, destination);
}
/**
* Set an aspect ratio for crop bounds.
* User won't see the menu with other ratios options.
*
* @param x aspect ratio X
* @param y aspect ratio Y
*/
public UCrop withAspectRatio(float x, float y) {
mCropOptionsBundle.putFloat(EXTRA_ASPECT_RATIO_X, x);
mCropOptionsBundle.putFloat(EXTRA_ASPECT_RATIO_Y, y);
return this;
}
/**
* Set an aspect ratio for crop bounds that is evaluated from source image width and height.
* User won't see the menu with other ratios options.
*/
public UCrop useSourceImageAspectRatio() {
mCropOptionsBundle.putFloat(EXTRA_ASPECT_RATIO_X, 0);
mCropOptionsBundle.putFloat(EXTRA_ASPECT_RATIO_Y, 0);
return this;
}
/**
* Set maximum size for result cropped image. Maximum size cannot be less then {@value MIN_SIZE}
*
* @param width max cropped image width
* @param height max cropped image height
*/
public UCrop withMaxResultSize(@IntRange(from = MIN_SIZE) int width, @IntRange(from = MIN_SIZE) int height) {
if (width < MIN_SIZE) {
width = MIN_SIZE;
}
if (height < MIN_SIZE) {
height = MIN_SIZE;
}
mCropOptionsBundle.putInt(EXTRA_MAX_SIZE_X, width);
mCropOptionsBundle.putInt(EXTRA_MAX_SIZE_Y, height);
return this;
}
public UCrop withOptions(@NonNull Options options) {
mCropOptionsBundle.putAll(options.getOptionBundle());
return this;
}
/**
* Send the crop Intent from an Activity
*
* @param activity Activity to receive result
*/
public void start(@NonNull Activity activity) {
start(activity, REQUEST_CROP);
}
/**
* Send the crop Intent from an Activity with a custom request code
*
* @param activity Activity to receive result
* @param requestCode requestCode for result
*/
public void start(@NonNull Activity activity, int requestCode) {
activity.startActivityForResult(getIntent(activity), requestCode);
}
/**
* Send the crop Intent from a Fragment
*
* @param fragment Fragment to receive result
*/
public void start(@NonNull Context context, @NonNull Fragment fragment) {
start(context, fragment, REQUEST_CROP);
}
/**
* Send the crop Intent from a support library Fragment
*
* @param fragment Fragment to receive result
*/
public void start(@NonNull Context context, @NonNull androidx.fragment.app.Fragment fragment) {
start(context, fragment, REQUEST_CROP);
}
/**
* Send the crop Intent with a custom request code
*
* @param fragment Fragment to receive result
* @param requestCode requestCode for result
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void start(@NonNull Context context, @NonNull Fragment fragment, int requestCode) {
fragment.startActivityForResult(getIntent(context), requestCode);
}
/**
* Send the crop Intent with a custom request code
*
* @param fragment Fragment to receive result
* @param requestCode requestCode for result
*/
public void start(@NonNull Context context, @NonNull androidx.fragment.app.Fragment fragment, int requestCode) {
fragment.startActivityForResult(getIntent(context), requestCode);
}
/**
* Send the crop Intent
*
* @param activityResultLauncher used to launch {@link UCropActivity} and receive a result
*/
public void start(@NonNull Context context,
@NonNull ActivityResultLauncher<Intent> activityResultLauncher) {
activityResultLauncher.launch(getIntent(context));
}
/**
* Get Intent to start {@link UCropActivity}
*
* @return Intent for {@link UCropActivity}
*/
public Intent getIntent(@NonNull Context context) {
mCropIntent.setClass(context, UCropActivity.class);
mCropIntent.putExtras(mCropOptionsBundle);
return mCropIntent;
}
/**
* Get Fragment {@link UCropFragment}
*
* @return Fragment of {@link UCropFragment}
*/
public UCropFragment getFragment() {
return UCropFragment.newInstance(mCropOptionsBundle);
}
public UCropFragment getFragment(Bundle bundle) {
mCropOptionsBundle = bundle;
return getFragment();
}
/**
* Retrieve cropped image Uri from the result Intent
*
* @param intent crop result intent
*/
@Nullable
public static Uri getOutput(@NonNull Intent intent) {
return intent.getParcelableExtra(EXTRA_OUTPUT_URI);
}
/**
* Retrieve the width of the cropped image
*
* @param intent crop result intent
*/
public static int getOutputImageWidth(@NonNull Intent intent) {
return intent.getIntExtra(EXTRA_OUTPUT_IMAGE_WIDTH, -1);
}
/**
* Retrieve the height of the cropped image
*
* @param intent crop result intent
*/
public static int getOutputImageHeight(@NonNull Intent intent) {
return intent.getIntExtra(EXTRA_OUTPUT_IMAGE_HEIGHT, -1);
}
/**
* Retrieve cropped image aspect ratio from the result Intent
*
* @param intent crop result intent
* @return aspect ratio as a floating point value (x:y) - so it will be 1 for 1:1 or 4/3 for 4:3
*/
public static float getOutputCropAspectRatio(@NonNull Intent intent) {
return intent.getFloatExtra(EXTRA_OUTPUT_CROP_ASPECT_RATIO, 0f);
}
/**
* Method retrieves error from the result intent.
*
* @param result crop result Intent
* @return Throwable that could happen while image processing
*/
@Nullable
public static Throwable getError(@NonNull Intent result) {
return (Throwable) result.getSerializableExtra(EXTRA_ERROR);
}
/**
* Class that helps to setup advanced configs that are not commonly used.
* Use it with method {@link #withOptions(Options)}
*/
public static class Options {
public static final String EXTRA_COMPRESSION_FORMAT_NAME = EXTRA_PREFIX + ".CompressionFormatName";
public static final String EXTRA_COMPRESSION_QUALITY = EXTRA_PREFIX + ".CompressionQuality";
public static final String EXTRA_ALLOWED_GESTURES = EXTRA_PREFIX + ".AllowedGestures";
public static final String EXTRA_MAX_BITMAP_SIZE = EXTRA_PREFIX + ".MaxBitmapSize";
public static final String EXTRA_MAX_SCALE_MULTIPLIER = EXTRA_PREFIX + ".MaxScaleMultiplier";
public static final String EXTRA_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION = EXTRA_PREFIX + ".ImageToCropBoundsAnimDuration";
public static final String EXTRA_DIMMED_LAYER_COLOR = EXTRA_PREFIX + ".DimmedLayerColor";
public static final String EXTRA_CIRCLE_DIMMED_LAYER = EXTRA_PREFIX + ".CircleDimmedLayer";
public static final String EXTRA_SHOW_CROP_FRAME = EXTRA_PREFIX + ".ShowCropFrame";
public static final String EXTRA_CROP_FRAME_COLOR = EXTRA_PREFIX + ".CropFrameColor";
public static final String EXTRA_CROP_FRAME_STROKE_WIDTH = EXTRA_PREFIX + ".CropFrameStrokeWidth";
public static final String EXTRA_SHOW_CROP_GRID = EXTRA_PREFIX + ".ShowCropGrid";
public static final String EXTRA_CROP_GRID_ROW_COUNT = EXTRA_PREFIX + ".CropGridRowCount";
public static final String EXTRA_CROP_GRID_COLUMN_COUNT = EXTRA_PREFIX + ".CropGridColumnCount";
public static final String EXTRA_CROP_GRID_COLOR = EXTRA_PREFIX + ".CropGridColor";
public static final String EXTRA_CROP_GRID_CORNER_COLOR = EXTRA_PREFIX + ".CropGridCornerColor";
public static final String EXTRA_CROP_GRID_STROKE_WIDTH = EXTRA_PREFIX + ".CropGridStrokeWidth";
public static final String EXTRA_TOOL_BAR_COLOR = EXTRA_PREFIX + ".ToolbarColor";
public static final String EXTRA_STATUS_BAR_LIGHT = EXTRA_PREFIX + ".StatusBarLight";
public static final String EXTRA_NAVIGATION_BAR_LIGHT = EXTRA_PREFIX + ".NavigationBarLight";
public static final String EXTRA_UCROP_COLOR_CONTROLS_WIDGET_ACTIVE = EXTRA_PREFIX + ".UcropColorControlsWidgetActive";
public static final String EXTRA_UCROP_WIDGET_COLOR_TOOLBAR = EXTRA_PREFIX + ".UcropToolbarWidgetColor";
public static final String EXTRA_UCROP_TITLE_TEXT_TOOLBAR = EXTRA_PREFIX + ".UcropToolbarTitleText";
public static final String EXTRA_UCROP_WIDGET_CANCEL_DRAWABLE = EXTRA_PREFIX + ".UcropToolbarCancelDrawable";
public static final String EXTRA_UCROP_WIDGET_CROP_DRAWABLE = EXTRA_PREFIX + ".UcropToolbarCropDrawable";
public static final String EXTRA_UCROP_LOGO_COLOR = EXTRA_PREFIX + ".UcropLogoColor";
public static final String EXTRA_HIDE_BOTTOM_CONTROLS = EXTRA_PREFIX + ".HideBottomControls";
public static final String EXTRA_FREE_STYLE_CROP = EXTRA_PREFIX + ".FreeStyleCrop";
public static final String EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT = EXTRA_PREFIX + ".AspectRatioSelectedByDefault";
public static final String EXTRA_ASPECT_RATIO_OPTIONS = EXTRA_PREFIX + ".AspectRatioOptions";
public static final String EXTRA_UCROP_ROOT_VIEW_BACKGROUND_COLOR = EXTRA_PREFIX + ".UcropRootViewBackgroundColor";
private final Bundle mOptionBundle;
public Options() {
mOptionBundle = new Bundle();
}
@NonNull
public Bundle getOptionBundle() {
return mOptionBundle;
}
/**
* Set one of {@link android.graphics.Bitmap.CompressFormat} that will be used to save resulting Bitmap.
*/
public void setCompressionFormat(@NonNull Bitmap.CompressFormat format) {
mOptionBundle.putString(EXTRA_COMPRESSION_FORMAT_NAME, format.name());
}
/**
* Set compression quality [0-100] that will be used to save resulting Bitmap.
*/
public void setCompressionQuality(@IntRange(from = 0) int compressQuality) {
mOptionBundle.putInt(EXTRA_COMPRESSION_QUALITY, compressQuality);
}
/**
* Choose what set of gestures will be enabled on each tab - if any.
*/
public void setAllowedGestures(@UCropActivity.GestureTypes int tabScale,
@UCropActivity.GestureTypes int tabRotate,
@UCropActivity.GestureTypes int tabAspectRatio) {
mOptionBundle.putIntArray(EXTRA_ALLOWED_GESTURES, new int[]{tabScale, tabRotate, tabAspectRatio});
}
/**
* This method sets multiplier that is used to calculate max image scale from min image scale.
*
* @param maxScaleMultiplier - (minScale * maxScaleMultiplier) = maxScale
*/
public void setMaxScaleMultiplier(@FloatRange(from = 1.0, fromInclusive = false) float maxScaleMultiplier) {
mOptionBundle.putFloat(EXTRA_MAX_SCALE_MULTIPLIER, maxScaleMultiplier);
}
/**
* This method sets animation duration for image to wrap the crop bounds
*
* @param durationMillis - duration in milliseconds
*/
public void setImageToCropBoundsAnimDuration(@IntRange(from = MIN_SIZE) int durationMillis) {
mOptionBundle.putInt(EXTRA_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION, durationMillis);
}
/**
* Setter for max size for both width and height of bitmap that will be decoded from an input Uri and used in the view.
*
* @param maxBitmapSize - size in pixels
*/
public void setMaxBitmapSize(@IntRange(from = MIN_SIZE) int maxBitmapSize) {
mOptionBundle.putInt(EXTRA_MAX_BITMAP_SIZE, maxBitmapSize);
}
/**
* @param color - desired color of dimmed area around the crop bounds
*/
public void setDimmedLayerColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_DIMMED_LAYER_COLOR, color);
}
/**
* @param isCircle - set it to true if you want dimmed layer to have an circle inside
*/
public void setCircleDimmedLayer(boolean isCircle) {
mOptionBundle.putBoolean(EXTRA_CIRCLE_DIMMED_LAYER, isCircle);
}
/**
* @param show - set to true if you want to see a crop frame rectangle on top of an image
*/
public void setShowCropFrame(boolean show) {
mOptionBundle.putBoolean(EXTRA_SHOW_CROP_FRAME, show);
}
/**
* @param color - desired color of crop frame
*/
public void setCropFrameColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_CROP_FRAME_COLOR, color);
}
/**
* @param width - desired width of crop frame line in pixels
*/
public void setCropFrameStrokeWidth(@IntRange(from = 0) int width) {
mOptionBundle.putInt(EXTRA_CROP_FRAME_STROKE_WIDTH, width);
}
/**
* @param show - set to true if you want to see a crop grid/guidelines on top of an image
*/
public void setShowCropGrid(boolean show) {
mOptionBundle.putBoolean(EXTRA_SHOW_CROP_GRID, show);
}
/**
* @param count - crop grid rows count.
*/
public void setCropGridRowCount(@IntRange(from = 0) int count) {
mOptionBundle.putInt(EXTRA_CROP_GRID_ROW_COUNT, count);
}
/**
* @param count - crop grid columns count.
*/
public void setCropGridColumnCount(@IntRange(from = 0) int count) {
mOptionBundle.putInt(EXTRA_CROP_GRID_COLUMN_COUNT, count);
}
/**
* @param color - desired color of crop grid/guidelines
*/
public void setCropGridColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_CROP_GRID_COLOR, color);
}
/**
* @param color - desired color of crop grid/guidelines corner
*/
public void setCropGridCornerColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_CROP_GRID_CORNER_COLOR, color);
}
/**
* @param width - desired width of crop grid lines in pixels
*/
public void setCropGridStrokeWidth(@IntRange(from = 0) int width) {
mOptionBundle.putInt(EXTRA_CROP_GRID_STROKE_WIDTH, width);
}
/**
* @param color - desired resolved color of the toolbar
*/
public void setToolbarColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_TOOL_BAR_COLOR, color);
}
/**
* @param light true for light status bar (dark icons), false for dark status bar (light icons)
*/
public void setStatusBarLight(boolean light) {
mOptionBundle.putBoolean(EXTRA_STATUS_BAR_LIGHT, light);
}
/**
* @param light true for light navigation bar (dark icons), false for dark navigation bar (light icons)
*/
public void setNavigationBarLight(boolean light) {
mOptionBundle.putBoolean(EXTRA_NAVIGATION_BAR_LIGHT, light);
}
/**
* @param color - desired resolved color of the active and selected widget and progress wheel middle line (default is white)
*/
public void setActiveControlsWidgetColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_UCROP_COLOR_CONTROLS_WIDGET_ACTIVE, color);
}
/**
* @param color - desired resolved color of Toolbar text and buttons (default is darker orange)
*/
public void setToolbarWidgetColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_UCROP_WIDGET_COLOR_TOOLBAR, color);
}
/**
* @param text - desired text for Toolbar title
*/
public void setToolbarTitle(@Nullable String text) {
mOptionBundle.putString(EXTRA_UCROP_TITLE_TEXT_TOOLBAR, text);
}
/**
* @param drawable - desired drawable for the Toolbar left cancel icon
*/
public void setToolbarCancelDrawable(@DrawableRes int drawable) {
mOptionBundle.putInt(EXTRA_UCROP_WIDGET_CANCEL_DRAWABLE, drawable);
}
/**
* @param drawable - desired drawable for the Toolbar right crop icon
*/
public void setToolbarCropDrawable(@DrawableRes int drawable) {
mOptionBundle.putInt(EXTRA_UCROP_WIDGET_CROP_DRAWABLE, drawable);
}
/**
* @param color - desired resolved color of logo fill (default is darker grey)
*/
public void setLogoColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_UCROP_LOGO_COLOR, color);
}
/**
* @param hide - set to true to hide the bottom controls (shown by default)
*/
public void setHideBottomControls(boolean hide) {
mOptionBundle.putBoolean(EXTRA_HIDE_BOTTOM_CONTROLS, hide);
}
/**
* @param enabled - set to true to let user resize crop bounds (disabled by default)
*/
public void setFreeStyleCropEnabled(boolean enabled) {
mOptionBundle.putBoolean(EXTRA_FREE_STYLE_CROP, enabled);
}
/**
* Pass an ordered list of desired aspect ratios that should be available for a user.
*
* @param selectedByDefault - index of aspect ratio option that is selected by default (starts with 0).
* @param aspectRatio - list of aspect ratio options that are available to user
*/
public void setAspectRatioOptions(int selectedByDefault, AspectRatio... aspectRatio) {
if (selectedByDefault >= aspectRatio.length) {
throw new IllegalArgumentException(String.format(Locale.US,
"Index [selectedByDefault = %d] (0-based) cannot be higher or equal than aspect ratio options count [count = %d].",
selectedByDefault, aspectRatio.length));
}
mOptionBundle.putInt(EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, selectedByDefault);
mOptionBundle.putParcelableArrayList(EXTRA_ASPECT_RATIO_OPTIONS, new ArrayList<Parcelable>(Arrays.asList(aspectRatio)));
}
/**
* @param color - desired background color that should be applied to the root view
*/
public void setRootViewBackgroundColor(@ColorInt int color) {
mOptionBundle.putInt(EXTRA_UCROP_ROOT_VIEW_BACKGROUND_COLOR, color);
}
/**
* Set an aspect ratio for crop bounds.
* User won't see the menu with other ratios options.
*
* @param x aspect ratio X
* @param y aspect ratio Y
*/
public void withAspectRatio(float x, float y) {
mOptionBundle.putFloat(EXTRA_ASPECT_RATIO_X, x);
mOptionBundle.putFloat(EXTRA_ASPECT_RATIO_Y, y);
}
/**
* Set an aspect ratio for crop bounds that is evaluated from source image width and height.
* User won't see the menu with other ratios options.
*/
public void useSourceImageAspectRatio() {
mOptionBundle.putFloat(EXTRA_ASPECT_RATIO_X, 0);
mOptionBundle.putFloat(EXTRA_ASPECT_RATIO_Y, 0);
}
/**
* Set maximum size for result cropped image.
*
* @param width max cropped image width
* @param height max cropped image height
*/
public void withMaxResultSize(@IntRange(from = MIN_SIZE) int width, @IntRange(from = MIN_SIZE) int height) {
mOptionBundle.putInt(EXTRA_MAX_SIZE_X, width);
mOptionBundle.putInt(EXTRA_MAX_SIZE_Y, height);
}
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java
================================================
package com.yalantis.ucrop;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.activity.SystemBarStyle;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.transition.AutoTransition;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import com.yalantis.ucrop.callback.BitmapCropCallback;
import com.yalantis.ucrop.model.AspectRatio;
import com.yalantis.ucrop.util.SelectedStateListDrawable;
import com.yalantis.ucrop.view.CropImageView;
import com.yalantis.ucrop.view.GestureCropImageView;
import com.yalantis.ucrop.view.OverlayView;
import com.yalantis.ucrop.view.TransformImageView;
import com.yalantis.ucrop.view.UCropView;
import com.yalantis.ucrop.view.widget.AspectRatioTextView;
import com.yalantis.ucrop.view.widget.HorizontalProgressWheelView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* Created by Oleksii Shliama (https://github.com/shliama).
*/
@SuppressWarnings("ConstantConditions")
public class UCropActivity extends AppCompatActivity {
public static final int DEFAULT_COMPRESS_QUALITY = 90;
public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.JPEG;
public static final int NONE = 0;
public static final int SCALE = 1;
public static final int ROTATE = 2;
public static final int ALL = 3;
@IntDef({NONE, SCALE, ROTATE, ALL})
@Retention(RetentionPolicy.SOURCE)
public @interface GestureTypes {
}
private static final String TAG = "UCropActivity";
private static final long CONTROLS_ANIMATION_DURATION = 50;
private static final int TABS_COUNT = 3;
private static final int SCALE_WIDGET_SENSITIVITY_COEFFICIENT = 15000;
private static final int ROTATE_WIDGET_SENSITIVITY_COEFFICIENT = 42;
private String mToolbarTitle;
// Enables dynamic coloring
private int mToolbarColor;
private int mActiveControlsWidgetColor;
private int mToolbarWidgetColor;
@ColorInt
private int mRootViewBackgroundColor;
@DrawableRes
private int mToolbarCancelDrawable;
@DrawableRes
private int mToolbarCropDrawable;
private int mLogoColor;
private boolean mShowBottomControls;
private boolean mShowLoader = true;
private UCropView mUCropView;
private GestureCropImageView mGestureCropImageView;
private OverlayView mOverlayView;
private ViewGroup mWrapperStateAspectRatio, mWrapperStateRotate, mWrapperStateScale;
private ViewGroup mLayoutAspectRatio, mLayoutRotate, mLayoutScale;
private List<ViewGroup> mCropAspectRatioViews = new ArrayList<>();
private TextView mTextViewRotateAngle, mTextViewScalePercent;
private View mBlockingView;
private Transition mControlsTransition;
private Bitmap.CompressFormat mCompressFormat = DEFAULT_COMPRESS_FORMAT;
private int mCompressQuality = DEFAULT_COMPRESS_QUALITY;
private int[] mAllowedGestures = new int[]{SCALE, ROTATE, ALL};
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Intent intent = getIntent();
setupSystemBars(intent);
setContentView(R.layout.ucrop_activity_photobox);
setupViews(intent);
setImageData(intent);
setInitialState();
addBlockingView();
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.ucrop_menu_activity, menu);
// Change crop & loader menu icons color to match the rest of the UI colors
MenuItem menuItemLoader = menu.findItem(R.id.menu_loader);
Drawable menuItemLoaderIcon = menuItemLoader.getIcon();
if (menuItemLoaderIcon != null) {
try {
menuItemLoaderIcon.mutate();
menuItemLoaderIcon.setColorFilter(mToolbarWidgetColor, PorterDuff.Mode.SRC_ATOP);
menuItemLoader.setIcon(menuItemLoaderIcon);
} catch (IllegalStateException e) {
Log.i(TAG, String.format("%s - %s", e.getMessage(), getString(R.string.ucrop_mutate_exception_hint)));
}
((Animatable) menuItemLoader.getIcon()).start();
}
MenuItem menuItemCrop = menu.findItem(R.id.menu_crop);
Drawable menuItemCropIcon = ContextCompat.getDrawable(this, mToolbarCropDrawable);
if (menuItemCropIcon != null) {
menuItemCropIcon.mutate();
menuItemCropIcon.setColorFilter(mToolbarWidgetColor, PorterDuff.Mode.SRC_ATOP);
menuItemCrop.setIcon(menuItemCropIcon);
}
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(R.id.menu_crop).setVisible(!mShowLoader);
menu.findItem(R.id.menu_loader).setVisible(mShowLoader);
return super.onPrepareOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_crop) {
cropAndSaveImage();
return true;
} else if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onStop() {
super.onStop();
if (mGestureCropImageView != null) {
mGestureCropImageView.cancelAllAnimations();
}
}
/**
* This method extracts all data from the incoming intent and setups views properly.
*/
private void setImageData(@NonNull Intent intent) {
Uri inputUri = intent.getParcelableExtra(UCrop.EXTRA_INPUT_URI);
Uri outputUri = intent.getParcelableExtra(UCrop.EXTRA_OUTPUT_URI);
processOptions(intent);
if (inputUri != null && outputUri != null) {
try {
mGestureCropImageView.setImageUri(inputUri, outputUri);
} catch (Exception e) {
setResultError(e);
finish();
}
} else {
setResultError(new NullPointerException(getString(R.string.ucrop_error_input_data_is_absent)));
finish();
}
}
/**
* This method extracts {@link com.yalantis.ucrop.UCrop.Options #optionsBundle} from incoming intent
* and setups Activity, {@link OverlayView} and {@link CropImageView} properly.
*/
@SuppressWarnings("deprecation")
private void processOptions(@NonNull Intent intent) {
// Bitmap compression options
String compressionFormatName = intent.getStringExtra(UCrop.Options.EXTRA_COMPRESSION_FORMAT_NAME);
Bitmap.CompressFormat compressFormat = null;
if (!TextUtils.isEmpty(compressionFormatName)) {
compressFormat = Bitmap.CompressFormat.valueOf(compressionFormatName);
}
mCompressFormat = (compressFormat == null) ? DEFAULT_COMPRESS_FORMAT : compressFormat;
mCompressQuality = intent.getIntExtra(UCrop.Options.EXTRA_COMPRESSION_QUALITY, UCropActivity.DEFAULT_COMPRESS_QUALITY);
// Gestures options
int[] allowedGestures = intent.getIntArrayExtra(UCrop.Options.EXTRA_ALLOWED_GESTURES);
if (allowedGestures != null && allowedGestures.length == TABS_COUNT) {
mAllowedGestures = allowedGestures;
}
// Crop image view options
mGestureCropImageView.setMaxBitmapSize(intent.getIntExtra(UCrop.Options.EXTRA_MAX_BITMAP_SIZE, CropImageView.DEFAULT_MAX_BITMAP_SIZE));
mGestureCropImageView.setMaxScaleMultiplier(intent.getFloatExtra(UCrop.Options.EXTRA_MAX_SCALE_MULTIPLIER, CropImageView.DEFAULT_MAX_SCALE_MULTIPLIER));
mGestureCropImageView.setImageToWrapCropBoundsAnimDuration(intent.getIntExtra(UCrop.Options.EXTRA_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION, CropImageView.DEFAULT_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION));
// Overlay view options
mOverlayView.setFreestyleCropEnabled(intent.getBooleanExtra(UCrop.Options.EXTRA_FREE_STYLE_CROP, OverlayView.DEFAULT_FREESTYLE_CROP_MODE != OverlayView.FREESTYLE_CROP_MODE_DISABLE));
mOverlayView.setDimmedColor(intent.getIntExtra(UCrop.Options.EXTRA_DIMMED_LAYER_COLOR, getResources().getColor(R.color.ucrop_color_default_dimmed)));
mOverlayView.setCircleDimmedLayer(intent.getBooleanExtra(UCrop.Options.EXTRA_CIRCLE_DIMMED_LAYER, OverlayView.DEFAULT_CIRCLE_DIMMED_LAYER));
mOverlayView.setShowCropFrame(intent.getBooleanExtra(UCrop.Options.EXTRA_SHOW_CROP_FRAME, OverlayView.DEFAULT_SHOW_CROP_FRAME));
mOverlayView.setCropFrameColor(intent.getIntExtra(UCrop.Options.EXTRA_CROP_FRAME_COLOR, getResources().getColor(R.color.ucrop_color_default_crop_frame)));
mOverlayView.setCropFrameStrokeWidth(intent.getIntExtra(UCrop.Options.EXTRA_CROP_FRAME_STROKE_WIDTH, getResources().getDimensionPixelSize(R.dimen.ucrop_default_crop_frame_stoke_width)));
mOverlayView.setShowCropGrid(intent.getBooleanExtra(UCrop.Options.EXTRA_SHOW_CROP_GRID, OverlayView.DEFAULT_SHOW_CROP_GRID));
mOverlayView.setCropGridRowCount(intent.getIntExtra(UCrop.Options.EXTRA_CROP_GRID_ROW_COUNT, OverlayView.DEFAULT_CROP_GRID_ROW_COUNT));
mOverlayView.setCropGridColumnCount(intent.getIntExtra(UCrop.Options.EXTRA_CROP_GRID_COLUMN_COUNT, OverlayView.DEFAULT_CROP_GRID_COLUMN_COUNT));
mOverlayView.setCropGridColor(intent.getIntExtra(UCrop.Options.EXTRA_CROP_GRID_COLOR, getResources().getColor(R.color.ucrop_color_default_crop_grid)));
mOverlayView.setCropGridCornerColor(intent.getIntExtra(UCrop.Options.EXTRA_CROP_GRID_CORNER_COLOR, getResources().getColor(R.color.ucrop_color_default_crop_grid)));
mOverlayView.setCropGridStrokeWidth(intent.getIntExtra(UCrop.Options.EXTRA_CROP_GRID_STROKE_WIDTH, getResources().getDimensionPixelSize(R.dimen.ucrop_default_crop_grid_stoke_width)));
// Aspect ratio options
float aspectRatioX = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_X, -1);
float aspectRatioY = intent.getFloatExtra(UCrop.EXTRA_ASPECT_RATIO_Y, -1);
int aspectRationSelectedByDefault = intent.getIntExtra(UCrop.Options.EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, 0);
ArrayList<AspectRatio> aspectRatioList = intent.getParcelableArrayListExtra(UCrop.Options.EXTRA_ASPECT_RATIO_OPTIONS);
if (aspectRatioX >= 0 && aspectRatioY >= 0) {
if (mWrapperStateAspectRatio != null) {
mWrapperStateAspectRatio.setVisibility(View.GONE);
}
float targetAspectRatio = aspectRatioX / aspectRatioY;
mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else if (aspectRatioList != null && aspectRationSelectedByDefault < aspectRatioList.size()) {
float targetAspectRatio = aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioX() / aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioY();
mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else {
mGestureCropImageView.setTargetAspectRatio(CropImageView.SOURCE_IMAGE_ASPECT_RATIO);
}
// Result bitmap max size options
int maxSizeX = intent.getIntExtra(UCrop.EXTRA_MAX_SIZE_X, 0);
int maxSizeY = intent.getIntExtra(UCrop.EXTRA_MAX_SIZE_Y, 0);
if (maxSizeX > 0 && maxSizeY > 0) {
mGestureCropImageView.setMaxResultImageSizeX(maxSizeX);
mGestureCropImageView.setMaxResultImageSizeY(maxSizeY);
}
}
private void setupSystemBars(@NonNull Intent intent) {
boolean statusBarLight = intent.getBooleanExtra(UCrop.Options.EXTRA_STATUS_BAR_LIGHT, true);
boolean navigationBarLight = intent.getBooleanExtra(UCrop.Options.EXTRA_NAVIGATION_BAR_LIGHT, false);
SystemBarStyle statusBarStyle = statusBarLight
? SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT)
: SystemBarStyle.dark(Color.TRANSPARENT);
SystemBarStyle navigationBarStyle = navigationBarLight
? SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT)
: SystemBarStyle.dark(Color.TRANSPARENT);
EdgeToEdge.enable(this, statusBarStyle, navigationBarStyle);
}
private void setupViews(@NonNull Intent intent) {
mToolbarColor = intent.getIntExtra(UCrop.Options.EXTRA_TOOL_BAR_COLOR, ContextCompat.getColor(this, R.color.ucrop_color_toolbar));
mActiveControlsWidgetColor = intent.getIntExtra(UCrop.Options.EXTRA_UCROP_COLOR_CONTROLS_WIDGET_ACTIVE, ContextCompat.getColor(this, R.color.ucrop_color_active_controls_color));
mToolbarWidgetColor = intent.getIntExtra(UCrop.Options.EXTRA_UCROP_WIDGET_COLOR_TOOLBAR, ContextCompat.getColor(this, R.color.ucrop_color_toolbar_widget));
mToolbarCancelDrawable = intent.getIntExtra(UCrop.Options.EXTRA_UCROP_WIDGET_CANCEL_DRAWABLE, R.drawable.ucrop_ic_cross);
mToolbarCropDrawable = intent.getIntExtra(UCrop.Options.EXTRA_UCROP_WIDGET_CROP_DRAWABLE, R.drawable.ucrop_ic_done);
mToolbarTitle = intent.getStringExtra(UCrop.Options.EXTRA_UCROP_TITLE_TEXT_TOOLBAR);
mToolbarTitle = mToolbarTitle != null ? mToolbarTitle : getResources().getString(R.string.ucrop_label_edit_photo);
mLogoColor = intent.getIntExtra(UCrop.Options.EXTRA_UCROP_LOGO_COLOR, ContextCompat.getColor(this, R.color.ucrop_color_default_logo));
mShowBottomControls = !intent.getBooleanExtra(UCrop.Options.EXTRA_HIDE_BOTTOM_CONTROLS, false);
mRootViewBackgroundColor = intent.getIntExtra(UCrop.Options.EXTRA_UCROP_ROOT_VIEW_BACKGROUND_COLOR, ContextCompat.getColor(this, R.color.ucrop_color_crop_background));
setupAppBar();
initiateRootViews();
if (mShowBottomControls) {
ViewGroup viewGroup = findViewById(R.id.ucrop_photobox);
ViewGroup wrapper = viewGroup.findViewById(R.id.controls_wrapper);
wrapper.setVisibility(View.VISIBLE);
LayoutInflater.from(this).inflate(R.layout.ucrop_controls, wrapper, true);
mControlsTransition = new AutoTransition();
mControlsTransition.setDuration(CONTROLS_ANIMATION_DURATION);
mWrapperStateAspectRatio = findViewById(R.id.state_aspect_ratio);
mWrapperStateAspectRatio.setOnClickListener(mStateClickListener);
mWrapperStateRotate = findViewById(R.id.state_rotate);
mWrapperStateRotate.setOnClickListener(mStateClickListener);
mWrapperStateScale = findViewById(R.id.state_scale);
mWrapperStateScale.setOnClickListener(mStateClickListener);
mLayoutAspectRatio = findViewById(R.id.layout_aspect_ratio);
mLayoutRotate = findViewById(R.id.layout_rotate_wheel);
mLayoutScale = findViewById(R.id.layout_scale_wheel);
View controlsWrapper = findViewById(R.id.controls_wrapper);
int wrapperStatesHeight = getResources().getDimensionPixelSize(R.dimen.ucrop_height_wrapper_states);
ViewCompat.setOnApplyWindowInsetsListener(controlsWrapper.findViewById(R.id.wrapper_states), (view, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
view.setPaddingRelative(insets.left, 0, insets.right, insets.bottom);
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
int newWrapperStatesHeight = wrapperStatesHeight + insets.bottom;
if (layoutParams.height != newWrapperStatesHeight) {
layoutParams.height = newWrapperStatesHeight;
view.setLayoutParams(layoutParams);
}
return windowInsets;
});
setupAspectRatioWidget(intent);
setupRotateWidget();
setupScaleWidget();
setupStatesWrapper();
}
}
/**
* Configures and styles both status bar and toolbar.
*/
private void setupAppBar() {
final Toolbar toolbar = findViewById(R.id.toolbar);
ViewCompat.setOnApplyWindowInsetsListener(toolbar, (view, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
view.setPaddingRelative(insets.left, insets.top, insets.right, 0);
return windowInsets;
});
// Set all of the Toolbar coloring
toolbar.setBackgroundColor(mToolbarColor);
toolbar.setTitleTextColor(mToolbarWidgetColor);
final TextView toolbarTitle = toolbar.findViewById(R.id.toolbar_title);
toolbarTitle.setTextColor(mToolbarWidgetColor);
toolbarTitle.setText(mToolbarTitle);
// Color buttons inside the Toolbar
Drawable stateButtonDrawable = ContextCompat.getDrawable(this, mToolbarCancelDrawable).mutate();
stateButtonDrawable.setColorFilter(mToolbarWidgetColor, PorterDuff.Mode.SRC_ATOP);
toolbar.setNavigationIcon(stateButtonDrawable);
setSupportActionBar(toolbar);
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowTitleEnabled(false);
}
}
private void initiateRootViews() {
mUCropView = findViewById(R.id.ucrop);
mGestureCropImageView = mUCropView.getCropImageView();
mOverlayView = mUCropView.getOverlayView();
mGestureCropImageView.setTransformImageListener(mImageListener);
((ImageView) findViewById(R.id.image_view_logo)).setColorFilter(mLogoColor, PorterDuff.Mode.SRC_ATOP);
findViewById(R.id.ucrop_frame).setBackgroundColor(mRootViewBackgroundColor);
if (!mShowBottomControls) {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) findViewById(R.id.ucrop_frame).getLayoutParams();
params.bottomMargin = 0;
findViewById(R.id.ucrop_frame).requestLayout();
}
}
private TransformImageView.TransformImageListener mImageListener = new TransformImageView.TransformImageListener() {
@Override
public void onRotate(float currentAngle) {
setAngleText(currentAngle);
}
@Override
public void onScale(float currentScale) {
setScaleText(currentScale);
}
@Override
public void onLoadComplete() {
mUCropView.animate().alpha(1).setDuration(300).setInterpolator(new AccelerateInterpolator());
mBlockingView.setClickable(false);
mShowLoader = false;
supportInvalidateOptionsMenu();
}
@Override
public void onLoadFailure(@NonNull Exception e) {
setResultError(e);
finish();
}
};
/**
* Use {@link #mActiveControlsWidgetColor} for color filter
*/
private void setupStatesWrapper() {
ImageView stateScaleImageView = findViewById(R.id.image_view_state_scale);
ImageView stateRotateImageView = findViewById(R.id.image_view_state_rotate);
ImageView stateAspectRatioImageView = findViewById(R.id.image_view_state_aspect_ratio);
stateScaleImageView.setImageDrawable(new SelectedStateListDrawable(stateScaleImageView.getDrawable(), mActiveControlsWidgetColor));
stateRotateImageView.setImageDrawable(new SelectedStateListDrawable(stateRotateImageView.getDrawable(), mActiveControlsWidgetColor));
stateAspectRatioImageView.setImageDrawable(new SelectedStateListDrawable(stateAspectRatioImageView.getDrawable(), mActiveControlsWidgetColor));
}
private void setupAspectRatioWidget(@NonNull Intent intent) {
int aspectRationSelectedByDefault = intent.getIntExtra(UCrop.Options.EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, 0);
ArrayList<AspectRatio> aspectRatioList = intent.getParcelableArrayListExtra(UCrop.Options.EXTRA_ASPECT_RATIO_OPTIONS);
if (aspectRatioList == null || aspectRatioList.isEmpty()) {
aspectRationSelectedByDefault = 2;
aspectRatioList = new ArrayList<>();
aspectRatioList.add(new AspectRatio(null, 1, 1));
aspectRatioList.add(new AspectRatio(null, 3, 4));
aspectRatioList.add(new AspectRatio(getString(R.string.ucrop_label_original).toUpperCase(),
CropImageView.SOURCE_IMAGE_ASPECT_RATIO, CropImageView.SOURCE_IMAGE_ASPECT_RATIO));
aspectRatioList.add(new AspectRatio(null, 3, 2));
aspectRatioList.add(new AspectRatio(null, 16, 9));
}
LinearLayout wrapperAspectRatioList = findViewById(R.id.layout_aspect_ratio);
FrameLayout wrapperAspectRatio;
AspectRatioTextView aspectRatioTextView;
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
lp.weight = 1;
for (AspectRatio aspectRatio : aspectRatioList) {
wrapperAspectRatio = (FrameLayout) getLayoutInflater().inflate(R.layout.ucrop_aspect_ratio, null);
wrapperAspectRatio.setLayoutParams(lp);
aspectRatioTextView = ((AspectRatioTextView) wrapperAspectRatio.getChildAt(0));
aspectRatioTextView.setActiveColor(mActiveControlsWidgetColor);
aspectRatioTextView.setAspectRatio(aspectRatio);
wrapperAspectRatioList.addView(wrapperAspectRatio);
mCropAspectRatioViews.add(wrapperAspectRatio);
}
mCropAspectRatioViews.get(aspectRationSelectedByDefault).setSelected(true);
for (ViewGroup cropAspectRatioView : mCropAspectRatioViews) {
cropAspectRatioView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mGestureCropImageView.setTargetAspectRatio(
((AspectRatioTextView) ((ViewGroup) v).getChildAt(0)).getAspectRatio(v.isSelected()));
mGestureCropImageView.setImageToWrapCropBounds();
if (!v.isSelected()) {
for (ViewGroup cropAspectRatioView : mCropAspectRatioViews) {
cropAspectRatioView.setSelected(cropAspectRatioView == v);
}
}
}
});
}
}
private void setupRotateWidget() {
mTextViewRotateAngle = findViewById(R.id.text_view_rotate);
((HorizontalProgressWheelView) findViewById(R.id.rotate_scroll_wheel))
.setScrollingListener(new HorizontalProgressWheelView.ScrollingListener() {
@Override
public void onScroll(float delta, float totalDistance) {
mGestureCropImageView.postRotate(delta / ROTATE_WIDGET_SENSITIVITY_COEFFICIENT);
}
@Override
public void onScrollEnd() {
mGestureCropImageView.setImageToWrapCropBounds();
}
@Override
public void onScrollStart() {
mGestureCropImageView.cancelAllAnimations();
}
});
((HorizontalProgressWheelView) findViewById(R.id.rotate_scroll_wheel)).setMiddleLineColor(mActiveControlsWidgetColor);
findViewById(R.id.wrapper_reset_rotate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resetRotation();
}
});
findViewById(R.id.wrapper_rotate_by_angle).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
rotateByAngle(90);
}
});
setAngleTextColor(mActiveControlsWidgetColor);
}
private void setupScaleWidget() {
mTextViewScalePercent = findViewById(R.id.text_view_scale);
((HorizontalProgressWheelView) findViewById(R.id.scale_scroll_wheel))
.setScrollingListener(new HorizontalProgressWheelView.ScrollingListener() {
@Override
public void onScroll(float delta, float totalDistance) {
if (delta > 0) {
mGestureCropImageView.zoomInImage(mGestureCropImageView.getCurrentScale()
+ delta * ((mGestureCropImageView.getMaxScale() - mGestureCropImageView.getMinScale()) / SCALE_WIDGET_SENSITIVITY_COEFFICIENT));
} else {
mGestureCropImageView.zoomOutImage(mGestureCropImageView.getCurrentScale()
+ delta * ((mGestureCropImageView.getMaxScale() - mGestureCropImageView.getMinScale()) / SCALE_WIDGET_SENSITIVITY_COEFFICIENT));
}
}
@Override
public void onScrollEnd() {
mGestureCropImageView.setImageToWrapCropBounds();
}
@Override
public void onScrollStart() {
mGestureCropImageView.cancelAllAnimations();
}
});
((HorizontalProgressWheelView) findViewById(R.id.scale_scroll_wheel)).setMiddleLineColor(mActiveControlsWidgetColor);
setScaleTextColor(mActiveControlsWidgetColor);
}
private void setAngleText(float angle) {
if (mTextViewRotateAngle != null) {
mTextViewRotateAngle.setText(String.format(Locale.getDefault(), "%.1f°", angle));
}
}
private void setAngleTextColor(int textColor) {
if (mTextViewRotateAngle != null) {
mTextViewRotateAngle.setTextColor(textColor);
}
}
private void setScaleText(float scale) {
if (mTextViewScalePercent != null) {
mTextViewScalePercent.setText(String.format(Locale.getDefault(), "%d%%", (int) (scale * 100)));
}
}
private void setScaleTextColor(int textColor) {
if (mTextViewScalePercent != null) {
mTextViewScalePercent.setTextColor(textColor);
}
}
private void resetRotation() {
mGestureCropImageView.postRotate(-mGestureCropImageView.getCurrentAngle());
mGestureCropImageView.setImageToWrapCropBounds();
}
private void rotateByAngle(int angle) {
mGestureCropImageView.postRotate(angle);
mGestureCropImageView.setImageToWrapCropBounds();
}
private final View.OnClickListener mStateClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!v.isSelected()) {
setWidgetState(v.getId());
}
}
};
private void setInitialState() {
if (mShowBottomControls) {
if (mWrapperStateAspectRatio.getVisibility() == View.VISIBLE) {
setWidgetState(R.id.state_aspect_ratio);
} else {
setWidgetState(R.id.state_scale);
}
} else {
setAllowedGestures(0);
}
}
private void setWidgetState(@IdRes int stateViewId) {
if (!mShowBottomControls) return;
mWrapperStateAspectRatio.setSelected(stateViewId == R.id.state_aspect_ratio);
mWrapperStateRotate.setSelected(stateViewId == R.id.state_rotate);
mWrapperStateScale.setSelected(stateViewId == R.id.state_scale);
mLayoutAspectRatio.setVisibility(stateViewId == R.id.state_aspect_ratio ? View.VISIBLE : View.GONE);
mLayoutRotate.setVisibility(stateViewId == R.id.state_rotate ? View.VISIBLE : View.GONE);
mLayoutScale.setVisibility(stateViewId == R.id.state_scale ? View.VISIBLE : View.GONE);
changeSelectedTab(stateViewId);
if (stateViewId == R.id.state_scale) {
setAllowedGestures(0);
} else if (stateViewId == R.id.state_rotate) {
setAllowedGestures(1);
} else {
setAllowedGestures(2);
}
}
private void changeSelectedTab(int stateViewId) {
TransitionManager.beginDelayedTransition((ViewGroup) findViewById(R.id.ucrop_photobox), mControlsTransition);
mWrapperStateScale.findViewById(R.id.text_view_scale).setVisibility(stateViewId == R.id.state_scale ? View.VISIBLE : View.GONE);
mWrapperStateAspectRatio.findViewById(R.id.text_view_crop).setVisibility(stateViewId == R.id.state_aspect_ratio ? View.VISIBLE : View.GONE);
mWrapperStateRotate.findViewById(R.id.text_view_rotate).setVisibility(stateViewId == R.id.state_rotate ? View.VISIBLE : View.GONE);
}
private void setAllowedGestures(int tab) {
mGestureCropImageView.setScaleEnabled(mAllowedGestures[tab] == ALL || mAllowedGestures[tab] == SCALE);
mGestureCropImageView.setRotateEnabled(mAllowedGestures[tab] == ALL || mAllowedGestures[tab] == ROTATE);
}
/**
* Adds view that covers everything below the Toolbar.
* When it's clickable - user won't be able to click/touch anything below the Toolbar.
* Need to block user input while loading and cropping an image.
*/
private void addBlockingView() {
if (mBlockingView == null) {
mBlockingView = new View(this);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
lp.addRule(RelativeLayout.BELOW, R.id.toolbar);
mBlockingView.setLayoutParams(lp);
mBlockingView.setClickable(true);
}
((RelativeLayout) findViewById(R.id.ucrop_photobox)).addView(mBlockingView);
}
protected void cropAndSaveImage() {
mBlockingView.setClickable(true);
mShowLoader = true;
supportInvalidateOptionsMenu();
mGestureCropImageView.cropAndSaveImage(mCompressFormat, mCompressQuality, new BitmapCropCallback() {
@Override
public void onBitmapCropped(@NonNull Uri resultUri, int offsetX, int offsetY, int imageWidth, int imageHeight) {
setResultUri(resultUri, mGestureCropImageView.getTargetAspectRatio(), offsetX, offsetY, imageWidth, imageHeight);
finish();
}
@Override
public void onCropFailure(@NonNull Throwable t) {
setResultError(t);
finish();
}
});
}
protected void setResultUri(Uri uri, float resultAspectRatio, int offsetX, int offsetY, int imageWidth, int imageHeight) {
setResult(RESULT_OK, new Intent()
.putExtra(UCrop.EXTRA_OUTPUT_URI, uri)
.putExtra(UCrop.EXTRA_OUTPUT_CROP_ASPECT_RATIO, resultAspectRatio)
.putExtra(UCrop.EXTRA_OUTPUT_IMAGE_WIDTH, imageWidth)
.putExtra(UCrop.EXTRA_OUTPUT_IMAGE_HEIGHT, imageHeight)
.putExtra(UCrop.EXTRA_OUTPUT_OFFSET_X, offsetX)
.putExtra(UCrop.EXTRA_OUTPUT_OFFSET_Y, offsetY)
);
}
protected void setResultError(Throwable throwable) {
setResult(UCrop.RESULT_ERROR, new Intent().putExtra(UCrop.EXTRA_ERROR, throwable));
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java
================================================
package com.yalantis.ucrop;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.yalantis.ucrop.callback.BitmapCropCallback;
import com.yalantis.ucrop.model.AspectRatio;
import com.yalantis.ucrop.util.SelectedStateListDrawable;
import com.yalantis.ucrop.view.CropImageView;
import com.yalantis.ucrop.view.GestureCropImageView;
import com.yalantis.ucrop.view.OverlayView;
import com.yalantis.ucrop.view.TransformImageView;
import com.yalantis.ucrop.view.UCropView;
import com.yalantis.ucrop.view.widget.AspectRatioTextView;
import com.yalantis.ucrop.view.widget.HorizontalProgressWheelView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import androidx.annotation.ColorInt;
import androidx.annotation.IdRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.transition.AutoTransition;
import androidx.transition.Transition;
import androidx.transition.TransitionManager;
import static android.app.Activity.RESULT_OK;
@SuppressWarnings("ConstantConditions")
public class UCropFragment extends Fragment {
public static final int DEFAULT_COMPRESS_QUALITY = 90;
public static final Bitmap.CompressFormat DEFAULT_COMPRESS_FORMAT = Bitmap.CompressFormat.JPEG;
public static final int NONE = 0;
public static final int SCALE = 1;
public static final int ROTATE = 2;
public static final int ALL = 3;
@IntDef({NONE, SCALE, ROTATE, ALL})
@Retention(RetentionPolicy.SOURCE)
public @interface GestureTypes {
}
public static final String TAG = "UCropFragment";
private static final long CONTROLS_ANIMATION_DURATION = 50;
private static final int TABS_COUNT = 3;
private static final int SCALE_WIDGET_SENSITIVITY_COEFFICIENT = 15000;
private static final int ROTATE_WIDGET_SENSITIVITY_COEFFICIENT = 42;
private UCropFragmentCallback callback;
private int mActiveControlsWidgetColor;
@ColorInt
private int mRootViewBackgroundColor;
private int mLogoColor;
private boolean mShowBottomControls;
private Transition mControlsTransition;
private UCropView mUCropView;
private GestureCropImageView mGestureCropImageView;
private OverlayView mOverlayView;
private ViewGroup mWrapperStateAspectRatio, mWrapperStateRotate, mWrapperStateScale;
private ViewGroup mLayoutAspectRatio, mLayoutRotate, mLayoutScale;
private List<ViewGroup> mCropAspectRatioViews = new ArrayList<>();
private TextView mTextViewRotateAngle, mTextViewScalePercent;
private View mBlockingView;
private Bitmap.CompressFormat mCompressFormat = DEFAULT_COMPRESS_FORMAT;
private int mCompressQuality = DEFAULT_COMPRESS_QUALITY;
private int[] mAllowedGestures = new int[]{SCALE, ROTATE, ALL};
static {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
public static UCropFragment newInstance(Bundle uCrop) {
UCropFragment fragment = new UCropFragment();
fragment.setArguments(uCrop);
return fragment;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (getParentFragment() instanceof UCropFragmentCallback)
callback = (UCropFragmentCallback) getParentFragment();
else if (context instanceof UCropFragmentCallback)
callback = (UCropFragmentCallback) context;
else
throw new IllegalArgumentException(context.toString()
+ " must implement UCropFragmentCallback");
}
public void setCallback(UCropFragmentCallback callback) {
this.callback = callback;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.ucrop_fragment_photobox, container, false);
Bundle args = getArguments();
setupViews(rootView, args);
setImageData(args);
setInitialState();
addBlockingView(rootView);
return rootView;
}
public void setupViews(View view, Bundle args) {
mActiveControlsWidgetColor = args.getInt(UCrop.Options.EXTRA_UCROP_COLOR_CONTROLS_WIDGET_ACTIVE, ContextCompat.getColor(getContext(), R.color.ucrop_color_widget_active));
mLogoColor = args.getInt(UCrop.Options.EXTRA_UCROP_LOGO_COLOR, ContextCompat.getColor(getContext(), R.color.ucrop_color_default_logo));
mShowBottomControls = !args.getBoolean(UCrop.Options.EXTRA_HIDE_BOTTOM_CONTROLS, false);
mRootViewBackgroundColor = args.getInt(UCrop.Options.EXTRA_UCROP_ROOT_VIEW_BACKGROUND_COLOR, ContextCompat.getColor(getContext(), R.color.ucrop_color_crop_background));
initiateRootViews(view);
callback.loadingProgress(true);
if (mShowBottomControls) {
ViewGroup wrapper = view.findViewById(R.id.controls_wrapper);
wrapper.setVisibility(View.VISIBLE);
LayoutInflater.from(getContext()).inflate(R.layout.ucrop_controls, wrapper, true);
mControlsTransition = new AutoTransition();
mControlsTransition.setDuration(CONTROLS_ANIMATION_DURATION);
mWrapperStateAspectRatio = view.findViewById(R.id.state_aspect_ratio);
mWrapperStateAspectRatio.setOnClickListener(mStateClickListener);
mWrapperStateRotate = view.findViewById(R.id.state_rotate);
mWrapperStateRotate.setOnClickListener(mStateClickListener);
mWrapperStateScale = view.findViewById(R.id.state_scale);
mWrapperStateScale.setOnClickListener(mStateClickListener);
mLayoutAspectRatio = view.findViewById(R.id.layout_aspect_ratio);
mLayoutRotate = view.findViewById(R.id.layout_rotate_wheel);
mLayoutScale = view.findViewById(R.id.layout_scale_wheel);
setupAspectRatioWidget(args, view);
setupRotateWidget(view);
setupScaleWidget(view);
setupStatesWrapper(view);
} else {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view.findViewById(R.id.ucrop_frame).getLayoutParams();
params.bottomMargin = 0;
view.findViewById(R.id.ucrop_frame).requestLayout();
}
}
private void setImageData(@NonNull Bundle bundle) {
Uri inputUri = bundle.getParcelable(UCrop.EXTRA_INPUT_URI);
Uri outputUri = bundle.getParcelable(UCrop.EXTRA_OUTPUT_URI);
processOptions(bundle);
if (inputUri != null && outputUri != null) {
try {
mGestureCropImageView.setImageUri(inputUri, outputUri);
} catch (Exception e) {
callback.onCropFinish(getError(e));
}
} else {
callback.onCropFinish(getError(new NullPointerException(getString(R.string.ucrop_error_input_data_is_absent))));
}
}
/**
* This method extracts {@link com.yalantis.ucrop.UCrop.Options #optionsBundle} from incoming bundle
* and setups fragment, {@link OverlayView} and {@link CropImageView} properly.
*/
@SuppressWarnings("deprecation")
private void processOptions(@NonNull Bundle bundle) {
// Bitmap compression options
String compressionFormatName = bundle.getString(UCrop.Options.EXTRA_COMPRESSION_FORMAT_NAME);
Bitmap.CompressFormat compressFormat = null;
if (!TextUtils.isEmpty(compressionFormatName)) {
compressFormat = Bitmap.CompressFormat.valueOf(compressionFormatName);
}
mCompressFormat = (compressFormat == null) ? DEFAULT_COMPRESS_FORMAT : compressFormat;
mCompressQuality = bundle.getInt(UCrop.Options.EXTRA_COMPRESSION_QUALITY, UCropActivity.DEFAULT_COMPRESS_QUALITY);
// Gestures options
int[] allowedGestures = bundle.getIntArray(UCrop.Options.EXTRA_ALLOWED_GESTURES);
if (allowedGestures != null && allowedGestures.length == TABS_COUNT) {
mAllowedGestures = allowedGestures;
}
// Crop image view options
mGestureCropImageView.setMaxBitmapSize(bundle.getInt(UCrop.Options.EXTRA_MAX_BITMAP_SIZE, CropImageView.DEFAULT_MAX_BITMAP_SIZE));
mGestureCropImageView.setMaxScaleMultiplier(bundle.getFloat(UCrop.Options.EXTRA_MAX_SCALE_MULTIPLIER, CropImageView.DEFAULT_MAX_SCALE_MULTIPLIER));
mGestureCropImageView.setImageToWrapCropBoundsAnimDuration(bundle.getInt(UCrop.Options.EXTRA_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION, CropImageView.DEFAULT_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION));
// Overlay view options
mOverlayView.setFreestyleCropEnabled(bundle.getBoolean(UCrop.Options.EXTRA_FREE_STYLE_CROP, OverlayView.DEFAULT_FREESTYLE_CROP_MODE != OverlayView.FREESTYLE_CROP_MODE_DISABLE));
mOverlayView.setDimmedColor(bundle.getInt(UCrop.Options.EXTRA_DIMMED_LAYER_COLOR, getResources().getColor(R.color.ucrop_color_default_dimmed)));
mOverlayView.setCircleDimmedLayer(bundle.getBoolean(UCrop.Options.EXTRA_CIRCLE_DIMMED_LAYER, OverlayView.DEFAULT_CIRCLE_DIMMED_LAYER));
mOverlayView.setShowCropFrame(bundle.getBoolean(UCrop.Options.EXTRA_SHOW_CROP_FRAME, OverlayView.DEFAULT_SHOW_CROP_FRAME));
mOverlayView.setCropFrameColor(bundle.getInt(UCrop.Options.EXTRA_CROP_FRAME_COLOR, getResources().getColor(R.color.ucrop_color_default_crop_frame)));
mOverlayView.setCropFrameStrokeWidth(bundle.getInt(UCrop.Options.EXTRA_CROP_FRAME_STROKE_WIDTH, getResources().getDimensionPixelSize(R.dimen.ucrop_default_crop_frame_stoke_width)));
mOverlayView.setShowCropGrid(bundle.getBoolean(UCrop.Options.EXTRA_SHOW_CROP_GRID, OverlayView.DEFAULT_SHOW_CROP_GRID));
mOverlayView.setCropGridRowCount(bundle.getInt(UCrop.Options.EXTRA_CROP_GRID_ROW_COUNT, OverlayView.DEFAULT_CROP_GRID_ROW_COUNT));
mOverlayView.setCropGridColumnCount(bundle.getInt(UCrop.Options.EXTRA_CROP_GRID_COLUMN_COUNT, OverlayView.DEFAULT_CROP_GRID_COLUMN_COUNT));
mOverlayView.setCropGridColor(bundle.getInt(UCrop.Options.EXTRA_CROP_GRID_COLOR, getResources().getColor(R.color.ucrop_color_default_crop_grid)));
mOverlayView.setCropGridStrokeWidth(bundle.getInt(UCrop.Options.EXTRA_CROP_GRID_STROKE_WIDTH, getResources().getDimensionPixelSize(R.dimen.ucrop_default_crop_grid_stoke_width)));
// Aspect ratio options
float aspectRatioX = bundle.getFloat(UCrop.EXTRA_ASPECT_RATIO_X, -1);
float aspectRatioY = bundle.getFloat(UCrop.EXTRA_ASPECT_RATIO_Y, -1);
int aspectRationSelectedByDefault = bundle.getInt(UCrop.Options.EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, 0);
ArrayList<AspectRatio> aspectRatioList = bundle.getParcelableArrayList(UCrop.Options.EXTRA_ASPECT_RATIO_OPTIONS);
if (aspectRatioX >= 0 && aspectRatioY >= 0) {
if (mWrapperStateAspectRatio != null) {
mWrapperStateAspectRatio.setVisibility(View.GONE);
}
float targetAspectRatio = aspectRatioX / aspectRatioY;
mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else if (aspectRatioList != null && aspectRationSelectedByDefault < aspectRatioList.size()) {
float targetAspectRatio = aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioX() / aspectRatioList.get(aspectRationSelectedByDefault).getAspectRatioY();
mGestureCropImageView.setTargetAspectRatio(Float.isNaN(targetAspectRatio) ? CropImageView.SOURCE_IMAGE_ASPECT_RATIO : targetAspectRatio);
} else {
mGestureCropImageView.setTargetAspectRatio(CropImageView.SOURCE_IMAGE_ASPECT_RATIO);
}
// Result bitmap max size options
int maxSizeX = bundle.getInt(UCrop.EXTRA_MAX_SIZE_X, 0);
int maxSizeY = bundle.getInt(UCrop.EXTRA_MAX_SIZE_Y, 0);
if (maxSizeX > 0 && maxSizeY > 0) {
mGestureCropImageView.setMaxResultImageSizeX(maxSizeX);
mGestureCropImageView.setMaxResultImageSizeY(maxSizeY);
}
}
private void initiateRootViews(View view) {
mUCropView = view.findViewById(R.id.ucrop);
mGestureCropImageView = mUCropView.getCropImageView();
mOverlayView = mUCropView.getOverlayView();
mGestureCropImageView.setTransformImageListener(mImageListener);
((ImageView) view.findViewById(R.id.image_view_logo)).setColorFilter(mLogoColor, PorterDuff.Mode.SRC_ATOP);
view.findViewById(R.id.ucrop_frame).setBackgroundColor(mRootViewBackgroundColor);
}
private TransformImageView.TransformImageListener mImageListener = new TransformImageView.TransformImageListener() {
@Override
public void onRotate(float currentAngle) {
setAngleText(currentAngle);
}
@Override
public void onScale(float currentScale) {
setScaleText(currentScale);
}
@Override
public void onLoadComplete() {
mUCropView.animate().alpha(1).setDuration(300).setInterpolator(new AccelerateInterpolator());
mBlockingView.setClickable(false);
callback.loadingProgress(false);
}
@Override
public void onLoadFailure(@NonNull Exception e) {
callback.onCropFinish(getError(e));
}
};
private void setupStatesWrapper(View view) {
ImageView stateScaleImageView = view.findViewById(R.id.image_view_state_scale);
ImageView stateRotateImageView = view.findViewById(R.id.image_view_state_rotate);
ImageView stateAspectRatioImageView = view.findViewById(R.id.image_view_state_aspect_ratio);
stateScaleImageView.setImageDrawable(new SelectedStateListDrawable(stateScaleImageView.getDrawable(), mActiveControlsWidgetColor));
stateRotateImageView.setImageDrawable(new SelectedStateListDrawable(stateRotateImageView.getDrawable(), mActiveControlsWidgetColor));
stateAspectRatioImageView.setImageDrawable(new SelectedStateListDrawable(stateAspectRatioImageView.getDrawable(), mActiveControlsWidgetColor));
}
private void setupAspectRatioWidget(@NonNull Bundle bundle, View view) {
int aspectRationSelectedByDefault = bundle.getInt(UCrop.Options.EXTRA_ASPECT_RATIO_SELECTED_BY_DEFAULT, 0);
ArrayList<AspectRatio> aspectRatioList = bundle.getParcelableArrayList(UCrop.Options.EXTRA_ASPECT_RATIO_OPTIONS);
if (aspectRatioList == null || aspectRatioList.isEmpty()) {
aspectRationSelectedByDefault = 2;
aspectRatioList = new ArrayList<>();
aspectRatioList.add(new AspectRatio(null, 1, 1));
aspectRatioList.add(new AspectRatio(null, 3, 4));
aspectRatioList.add(new AspectRatio(getString(R.string.ucrop_label_original).toUpperCase(),
CropImageView.SOURCE_IMAGE_ASPECT_RATIO, CropImageView.SOURCE_IMAGE_ASPECT_RATIO));
aspectRatioList.add(new AspectRatio(null, 3, 2));
aspectRatioList.add(new AspectRatio(null, 16, 9));
}
LinearLayout wrapperAspectRatioList = view.findViewById(R.id.layout_aspect_ratio);
FrameLayout wrapperAspectRatio;
AspectRatioTextView aspectRatioTextView;
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT);
lp.weight = 1;
for (AspectRatio aspectRatio : aspectRatioList) {
wrapperAspectRatio = (FrameLayout) getLayoutInflater().inflate(R.layout.ucrop_aspect_ratio, null);
wrapperAspectRatio.setLayoutParams(lp);
aspectRatioTextView = ((AspectRatioTextView) wrapperAspectRatio.getChildAt(0));
aspectRatioTextView.setActiveColor(mActiveControlsWidgetColor);
aspectRatioTextView.setAspectRatio(aspectRatio);
wrapperAspectRatioList.addView(wrapperAspectRatio);
mCropAspectRatioViews.add(wrapperAspectRatio);
}
mCropAspectRatioViews.get(aspectRationSelectedByDefault).setSelected(true);
for (ViewGroup cropAspectRatioView : mCropAspectRatioViews) {
cropAspectRatioView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mGestureCropImageView.setTargetAspectRatio(
((AspectRatioTextView) ((ViewGroup) v).getChildAt(0)).getAspectRatio(v.isSelected()));
mGestureCropImageView.setImageToWrapCropBounds();
if (!v.isSelected()) {
for (ViewGroup cropAspectRatioView : mCropAspectRatioViews) {
cropAspectRatioView.setSelected(cropAspectRatioView == v);
}
}
}
});
}
}
private void setupRotateWidget(View view) {
mTextViewRotateAngle = view.findViewById(R.id.text_view_rotate);
((HorizontalProgressWheelView) view.findViewById(R.id.rotate_scroll_wheel))
.setScrollingListener(new HorizontalProgressWheelView.ScrollingListener() {
@Override
public void onScroll(float delta, float totalDistance) {
mGestureCropImageView.postRotate(delta / ROTATE_WIDGET_SENSITIVITY_COEFFICIENT);
}
@Override
public void onScrollEnd() {
mGestureCropImageView.setImageToWrapCropBounds();
}
@Override
public void onScrollStart() {
mGestureCropImageView.cancelAllAnimations();
}
});
((HorizontalProgressWheelView) view.findViewById(R.id.rotate_scroll_wheel)).setMiddleLineColor(mActiveControlsWidgetColor);
view.findViewById(R.id.wrapper_reset_rotate).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
resetRotation();
}
});
view.findViewById(R.id.wrapper_rotate_by_angle).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
rotateByAngle(90);
}
});
setAngleTextColor(mActiveControlsWidgetColor);
}
private void setupScaleWidget(View view) {
mTextViewScalePercent = view.findViewById(R.id.text_view_scale);
((HorizontalProgressWheelView) view.findViewById(R.id.scale_scroll_wheel))
.setScrollingListener(new HorizontalProgressWheelView.ScrollingListener() {
@Override
public void onScroll(float delta, float totalDistance) {
if (delta > 0) {
mGestureCropImageView.zoomInImage(mGestureCropImageView.getCurrentScale()
+ delta * ((mGestureCropImageView.getMaxScale() - mGestureCropImageView.getMinScale()) / SCALE_WIDGET_SENSITIVITY_COEFFICIENT));
} else {
mGestureCropImageView.zoomOutImage(mGestureCropImageView.getCurrentScale()
+ delta * ((mGestureCropImageView.getMaxScale() - mGestureCropImageView.getMinScale()) / SCALE_WIDGET_SENSITIVITY_COEFFICIENT));
}
}
@Override
public void onScrollEnd() {
mGestureCropImageView.setImageToWrapCropBounds();
}
@Override
public void onScrollStart() {
mGestureCropImageView.cancelAllAnimations();
}
});
((HorizontalProgressWheelView) view.findViewById(R.id.scale_scroll_wheel)).setMiddleLineColor(mActiveControlsWidgetColor);
setScaleTextColor(mActiveControlsWidgetColor);
}
private void setAngleText(float angle) {
if (mTextViewRotateAngle != null) {
mTextViewRotateAngle.setText(String.format(Locale.getDefault(), "%.1f°", angle));
}
}
private void setAngleTextColor(int textColor) {
if (mTextViewRotateAngle != null) {
mTextViewRotateAngle.setTextColor(textColor);
}
}
private void setScaleText(float scale) {
if (mTextViewScalePercent != null) {
mTextViewScalePercent.setText(String.format(Locale.getDefault(), "%d%%", (int) (scale * 100)));
}
}
private void setScaleTextColor(int textColor) {
if (mTextViewScalePercent != null) {
mTextViewScalePercent.setTextColor(textColor);
}
}
private void resetRotation() {
mGestureCropImageView.postRotate(-mGestureCropImageView.getCurrentAngle());
mGestureCropImageView.setImageToWrapCropBounds();
}
private void rotateByAngle(int angle) {
mGestureCropImageView.postRotate(angle);
mGestureCropImageView.setImageToWrapCropBounds();
}
private final View.OnClickListener mStateClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!v.isSelected()) {
setWidgetState(v.getId());
}
}
};
private void setInitialState() {
if (mShowBottomControls) {
if (mWrapperStateAspectRatio.getVisibility() == View.VISIBLE) {
setWidgetState(R.id.state_aspect_ratio);
} else {
setWidgetState(R.id.state_scale);
}
} else {
setAllowedGestures(0);
}
}
private void setWidgetState(@IdRes int stateViewId) {
if (!mShowBottomControls) return;
mWrapperStateAspectRatio.setSelected(stateViewId == R.id.state_aspect_ratio);
mWrapperStateRotate.setSelected(stateViewId == R.id.state_rotate);
mWrapperStateScale.setSelected(stateViewId == R.id.state_scale);
mLayoutAspectRatio.setVisibility(stateViewId == R.id.state_aspect_ratio ? View.VISIBLE : View.GONE);
mLayoutRotate.setVisibility(stateViewId == R.id.state_rotate ? View.VISIBLE : View.GONE);
mLayoutScale.setVisibility(stateViewId == R.id.state_scale ? View.VISIBLE : View.GONE);
changeSelectedTab(stateViewId);
if (stateViewId == R.id.state_scale) {
setAllowedGestures(0);
} else if (stateViewId == R.id.state_rotate) {
setAllowedGestures(1);
} else {
setAllowedGestures(2);
}
}
private void changeSelectedTab(int stateViewId) {
if (getView() != null) {
TransitionManager.beginDelayedTransition((ViewGroup) getView().findViewById(R.id.ucrop_photobox), mControlsTransition);
}
mWrapperStateScale.findViewById(R.id.text_view_scale).setVisibility(stateViewId == R.id.state_scale ? View.VISIBLE : View.GONE);
mWrapperStateAspectRatio.findViewById(R.id.text_view_crop).setVisibility(stateViewId == R.id.state_aspect_ratio ? View.VISIBLE : View.GONE);
mWrapperStateRotate.findViewById(R.id.text_view_rotate).setVisibility(stateViewId == R.id.state_rotate ? View.VISIBLE : View.GONE);
}
private void setAllowedGestures(int tab) {
mGestureCropImageView.setScaleEnabled(mAllowedGestures[tab] == ALL || mAllowedGestures[tab] == SCALE);
mGestureCropImageView.setRotateEnabled(mAllowedGestures[tab] == ALL || mAllowedGestures[tab] == ROTATE);
}
/**
* Adds view that covers everything below the Toolbar.
* When it's clickable - user won't be able to click/touch anything below the Toolbar.
* Need to block user input while loading and cropping an image.
*/
private void addBlockingView(View view) {
if (mBlockingView == null) {
mBlockingView = new View(getContext());
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mBlockingView.setLayoutParams(lp);
mBlockingView.setClickable(true);
}
((RelativeLayout) view.findViewById(R.id.ucrop_photobox)).addView(mBlockingView);
}
public void cropAndSaveImage() {
mBlockingView.setClickable(true);
callback.loadingProgress(true);
mGestureCropImageView.cropAndSaveImage(mCompressFormat, mCompressQuality, new BitmapCropCallback() {
@Override
public void onBitmapCropped(@NonNull Uri resultUri, int offsetX, int offsetY, int imageWidth, int imageHeight) {
callback.onCropFinish(getResult(resultUri, mGestureCropImageView.getTargetAspectRatio(), offsetX, offsetY, imageWidth, imageHeight));
callback.loadingProgress(false);
}
@Override
public void onCropFailure(@NonNull Throwable t) {
callback.onCropFinish(getError(t));
}
});
}
protected UCropResult getResult(Uri uri, float resultAspectRatio, int offsetX, int offsetY, int imageWidth, int imageHeight) {
return new UCropResult(RESULT_OK, new Intent()
.putExtra(UCrop.EXTRA_OUTPUT_URI, uri)
.putExtra(UCrop.EXTRA_OUTPUT_CROP_ASPECT_RATIO, resultAspectRatio)
.putExtra(UCrop.EXTRA_OUTPUT_IMAGE_WIDTH, imageWidth)
.putExtra(UCrop.EXTRA_OUTPUT_IMAGE_HEIGHT, imageHeight)
.putExtra(UCrop.EXTRA_OUTPUT_OFFSET_X, offsetX)
.putExtra(UCrop.EXTRA_OUTPUT_OFFSET_Y, offsetY)
);
}
protected UCropResult getError(Throwable throwable) {
return new UCropResult(UCrop.RESULT_ERROR, new Intent().putExtra(UCrop.EXTRA_ERROR, throwable));
}
public class UCropResult {
public int mResultCode;
public Intent mResultData;
public UCropResult(int resultCode, Intent data) {
mResultCode = resultCode;
mResultData = data;
}
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/UCropFragmentCallback.java
================================================
package com.yalantis.ucrop;
public interface UCropFragmentCallback {
/**
* Return loader status
* @param showLoader
*/
void loadingProgress(boolean showLoader);
/**
* Return cropping result or error
* @param result
*/
void onCropFinish(UCropFragment.UCropResult result);
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/UCropHttpClientStore.java
================================================
package com.yalantis.ucrop;
import androidx.annotation.NonNull;
import okhttp3.OkHttpClient;
public class UCropHttpClientStore {
private UCropHttpClientStore() {}
public final static UCropHttpClientStore INSTANCE = new UCropHttpClientStore();
private OkHttpClient client;
@NonNull
public OkHttpClient getClient() {
if (client == null) {
client = new OkHttpClient();
}
return client;
}
public void setClient(@NonNull OkHttpClient client) {
this.client = client;
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapCropCallback.java
================================================
package com.yalantis.ucrop.callback;
import android.net.Uri;
import androidx.annotation.NonNull;
public interface BitmapCropCallback {
void onBitmapCropped(@NonNull Uri resultUri, int offsetX, int offsetY, int imageWidth, int imageHeight);
void onCropFailure(@NonNull Throwable t);
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapLoadCallback.java
================================================
package com.yalantis.ucrop.callback;
import android.graphics.Bitmap;
import com.yalantis.ucrop.model.ExifInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public interface BitmapLoadCallback {
void onBitmapLoaded(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo, @NonNull String imageInputPath, @Nullable String imageOutputPath);
void onFailure(@NonNull Exception bitmapWorkerException);
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/callback/CropBoundsChangeListener.java
================================================
package com.yalantis.ucrop.callback;
/**
* Interface for crop bound change notifying.
*/
public interface CropBoundsChangeListener {
void onCropAspectRatioChanged(float cropRatio);
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/callback/OverlayViewChangeListener.java
================================================
package com.yalantis.ucrop.callback;
import android.graphics.RectF;
/**
* Created by Oleksii Shliama.
*/
public interface OverlayViewChangeListener {
void onCropRectUpdated(RectF cropRect);
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/model/AspectRatio.java
================================================
package com.yalantis.ucrop.model;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
/**
* Created by Oleksii Shliama [https://github.com/shliama] on 6/24/16.
*/
public class AspectRatio implements Parcelable {
@Nullable
private final String mAspectRatioTitle;
private final float mAspectRatioX;
private final float mAspectRatioY;
public AspectRatio(@Nullable String aspectRatioTitle, float aspectRatioX, float aspectRatioY) {
mAspectRatioTitle = aspectRatioTitle;
mAspectRatioX = aspectRatioX;
mAspectRatioY = aspectRatioY;
}
protected AspectRatio(Parcel in) {
mAspectRatioTitle = in.readString();
mAspectRatioX = in.readFloat();
mAspectRatioY = in.readFloat();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mAspectRatioTitle);
dest.writeFloat(mAspectRatioX);
dest.writeFloat(mAspectRatioY);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<AspectRatio> CREATOR = new Creator<AspectRatio>() {
@Override
public AspectRatio createFromParcel(Parcel in) {
return new AspectRatio(in);
}
@Override
public AspectRatio[] newArray(int size) {
return new AspectRatio[size];
}
};
@Nullable
public String getAspectRatioTitle() {
return mAspectRatioTitle;
}
public float getAspectRatioX() {
return mAspectRatioX;
}
public float getAspectRatioY() {
return mAspectRatioY;
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java
================================================
package com.yalantis.ucrop.model;
import android.graphics.Bitmap;
/**
* Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.
*/
public class CropParameters {
private int mMaxResultImageSizeX, mMaxResultImageSizeY;
private Bitmap.CompressFormat mCompressFormat;
private int mCompressQuality;
private String mImageInputPath, mImageOutputPath;
private ExifInfo mExifInfo;
public CropParameters(int maxResultImageSizeX, int maxResultImageSizeY,
Bitmap.CompressFormat compressFormat, int compressQuality,
String imageInputPath, String imageOutputPath, ExifInfo exifInfo) {
mMaxResultImageSizeX = maxResultImageSizeX;
mMaxResultImageSizeY = maxResultImageSizeY;
mCompressFormat = compressFormat;
mCompressQuality = compressQuality;
mImageInputPath = imageInputPath;
mImageOutputPath = imageOutputPath;
mExifInfo = exifInfo;
}
public int getMaxResultImageSizeX() {
return mMaxResultImageSizeX;
}
public int getMaxResultImageSizeY() {
return mMaxResultImageSizeY;
}
public Bitmap.CompressFormat getCompressFormat() {
return mCompressFormat;
}
public int getCompressQuality() {
return mCompressQuality;
}
public String getImageInputPath() {
return mImageInputPath;
}
public String getImageOutputPath() {
return mImageOutputPath;
}
public ExifInfo getExifInfo() {
return mExifInfo;
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/model/ExifInfo.java
================================================
package com.yalantis.ucrop.model;
/**
* Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.
*/
public class ExifInfo {
private int mExifOrientation;
private int mExifDegrees;
private int mExifTranslation;
public ExifInfo(int exifOrientation, int exifDegrees, int exifTranslation) {
mExifOrientation = exifOrientation;
mExifDegrees = exifDegrees;
mExifTranslation = exifTranslation;
}
public int getExifOrientation() {
return mExifOrientation;
}
public int getExifDegrees() {
return mExifDegrees;
}
public int getExifTranslation() {
return mExifTranslation;
}
public void setExifOrientation(int exifOrientation) {
mExifOrientation = exifOrientation;
}
public void setExifDegrees(int exifDegrees) {
mExifDegrees = exifDegrees;
}
public void setExifTranslation(int exifTranslation) {
mExifTranslation = exifTranslation;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExifInfo exifInfo = (ExifInfo) o;
if (mExifOrientation != exifInfo.mExifOrientation) return false;
if (mExifDegrees != exifInfo.mExifDegrees) return false;
return mExifTranslation == exifInfo.mExifTranslation;
}
@Override
public int hashCode() {
int result = mExifOrientation;
result = 31 * result + mExifDegrees;
result = 31 * result + mExifTranslation;
return result;
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/model/ImageState.java
================================================
package com.yalantis.ucrop.model;
import android.graphics.RectF;
/**
* Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.
*/
public class ImageState {
private RectF mCropRect;
private RectF mCurrentImageRect;
private float mCurrentScale, mCurrentAngle;
public ImageState(RectF cropRect, RectF currentImageRect, float currentScale, float currentAngle) {
mCropRect = cropRect;
mCurrentImageRect = currentImageRect;
mCurrentScale = currentScale;
mCurrentAngle = currentAngle;
}
public RectF getCropRect() {
return mCropRect;
}
public RectF getCurrentImageRect() {
return mCurrentImageRect;
}
public float getCurrentScale() {
return mCurrentScale;
}
public float getCurrentAngle() {
return mCurrentAngle;
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java
================================================
package com.yalantis.ucrop.task;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.RectF;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface;
import com.yalantis.ucrop.callback.BitmapCropCallback;
import com.yalantis.ucrop.model.CropParameters;
import com.yalantis.ucrop.model.ExifInfo;
import com.yalantis.ucrop.model.ImageState;
import com.yalantis.ucrop.util.FileUtils;
import com.yalantis.ucrop.util.ImageHeaderParser;
import java.io.File;
import java.io.IOException;
/**
* Crops part of image that fills the crop bounds.
* <p/>
* First image is downscaled if max size was set and if resulting image is larger that max size.
* Then image is rotated accordingly.
* Finally new Bitmap object is created and saved to file.
*/
public class BitmapCropTask extends AsyncTask<Void, Void, Throwable> {
private static final String TAG = "BitmapCropTask";
static {
System.loadLibrary("ucrop");
}
private Bitmap mViewBitmap;
private final RectF mCropRect;
private final RectF mCurrentImageRect;
private float mCurrentScale, mCurrentAngle;
private final int mMaxResultImageSizeX, mMaxResultImageSizeY;
private final Bitmap.CompressFormat mCompressFormat;
private final int mCompressQuality;
private final String mImageInputPath, mImageOutputPath;
private final ExifInfo mExifInfo;
private final BitmapCropCallback mCropCallback;
private int mCroppedImageWidth, mCroppedImageHeight;
private int cropOffsetX, cropOffsetY;
public BitmapCropTask(@Nullable Bitmap viewBitmap, @NonNull ImageState imageState, @NonNull CropParameters cropParameters,
@Nullable BitmapCropCallback cropCallback) {
mViewBitmap = viewBitmap;
mCropRect = imageState.getCropRect();
mCurrentImageRect = imageState.getCurrentImageRect();
mCurrentScale = imageState.getCurrentScale();
mCurrentAngle = imageState.getCurrentAngle();
mMaxResultImageSizeX = cropParameters.getMaxResultImageSizeX();
mMaxResultImageSizeY = cropParameters.getMaxResultImageSizeY();
mCompressFormat = cropParameters.getCompressFormat();
mCompressQuality = cropParameters.getCompressQuality();
mImageInputPath = cropParameters.getImageInputPath();
mImageOutputPath = cropParameters.getImageOutputPath();
mExifInfo = cropParameters.getExifInfo();
mCropCallback = cropCallback;
}
@Override
@Nullable
protected Throwable doInBackground(Void... params) {
if (mViewBitmap == null) {
return new NullPointerException("ViewBitmap is null");
} else if (mViewBitmap.isRecycled()) {
return new NullPointerException("ViewBitmap is recycled");
} else if (mCurrentImageRect.isEmpty()) {
return new NullPointerException("CurrentImageRect is empty");
}
float resizeScale = resize();
try {
crop(resizeScale);
mViewBitmap = null;
} catch (Throwable throwable) {
return throwable;
}
return null;
}
private float resize() {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mImageInputPath, options);
boolean swapSides = mExifInfo.getExifDegrees() == 90 || mExifInfo.getExifDegrees() == 270;
float scaleX = (swapSides ? options.outHeight : options.outWidth) / (float) mViewBitmap.getWidth();
float scaleY = (swapSides ? options.outWidth : options.outHeight) / (float) mViewBitmap.getHeight();
float resizeScale = Math.min(scaleX, scaleY);
mCurrentScale /= resizeScale;
resizeScale = 1;
if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) {
float cropWidth = mCropRect.width() / mCurrentScale;
float cropHeight = mCropRect.height() / mCurrentScale;
if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) {
scaleX = mMaxResultImageSizeX / cropWidth;
scaleY = mMaxResultImageSizeY / cropHeight;
resizeScale = Math.min(scaleX, scaleY);
mCurrentScale /= resizeScale;
}
}
return resizeScale;
}
private boolean crop(float resizeScale) throws IOException {
ExifInterface originalExif = new ExifInterface(mImageInputPath);
cropOffsetX = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale);
cropOffsetY = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale);
mCroppedImageWidth = Math.round(mCropRect.width() / mCurrentScale);
mCroppedImageHeight = Math.round(mCropRect.height() / mCurrentScale);
boolean shouldCrop = shouldCrop(mCroppedImageWidth, mCroppedImageHeight);
Log.i(TAG, "Should crop: " + shouldCrop);
if (shouldCrop) {
boolean cropped = cropCImg(mImageInputPath, mImageOutputPath,
cropOffsetX, cropOffsetY, mCroppedImageWidth, mCroppedImageHeight,
mCurrentAngle, resizeScale, mCompressFormat.ordinal(), mCompressQuality,
mExifInfo.getExifDegrees(), mExifInfo.getExifTranslation());
if (cropped && mCompressFormat.equals(Bitmap.CompressFormat.JPEG)) {
ImageHeaderParser.copyExif(originalExif, mCroppedImageWidth, mCroppedImageHeight, mImageOutputPath);
}
return cropped;
} else {
FileUtils.copyFile(mImageInputPath, mImageOutputPath);
return false;
}
}
/**
* Check whether an image should be cropped at all or just file can be copied to the destination path.
* For each 1000 pixels there is one pixel of error due to matrix calculations etc.
*
* @param width - crop area width
* @param height - crop area height
* @return - true if image must be cropped, false - if original image fits requirements
*/
private boolean shouldCrop(int width, int height) {
int pixelError = 1;
pixelError += Math.round(Math.max(width, height) / 1000f);
return (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0)
|| Math.abs(mCropRect.left - mCurrentImageRect.left) > pixelError
|| Math.abs(mCropRect.top - mCurrentImageRect.top) > pixelError
|| Math.abs(mCropRect.bottom - mCurrentImageRect.bottom) > pixelError
|| Math.abs(mCropRect.right - mCurrentImageRect.right) > pixelError
|| mCurrentAngle != 0;
}
native public static boolean cropCImg(String inputPath, String outputPath,
int left, int top, int width, int height,
float angle, float resizeScale,
int format, int quality,
int exifDegrees, int exifTranslation) throws IOException, OutOfMemoryError;
@Override
protected void onPostExecute(@Nullable Throwable t) {
if (mCropCallback != null) {
if (t == null) {
Uri uri = Uri.fromFile(new File(mImageOutputPath));
mCropCallback.onBitmapCropped(uri, cropOffsetX, cropOffsetY, mCroppedImageWidth, mCroppedImageHeight);
} else {
mCropCallback.onCropFailure(t);
}
}
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java
================================================
package com.yalantis.ucrop.task;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.yalantis.ucrop.UCropHttpClientStore;
import com.yalantis.ucrop.callback.BitmapLoadCallback;
import com.yalantis.ucrop.model.ExifInfo;
import com.yalantis.ucrop.util.BitmapLoadUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okio.BufferedSource;
import okio.Okio;
import okio.Sink;
/**
* Creates and returns a Bitmap for a given Uri(String url).
* inSampleSize is calculated based on requiredWidth property. However can be adjusted if OOM occurs.
* If any EXIF config is found - bitmap is transformed properly.
*/
public class BitmapLoadTask extends AsyncTask<Void, Void, BitmapLoadTask.BitmapWorkerResult> {
private static final String TAG = "BitmapWorkerTask";
private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
private final Context mContext;
private Uri mInputUri;
private Uri mOutputUri;
private final int mRequiredWidth;
private final int mRequiredHeight;
private final BitmapLoadCallback mBitmapLoadCallback;
public static class BitmapWorkerResult {
Bitmap mBitmapResult;
ExifInfo mExifInfo;
Exception mBitmapWorkerException;
public BitmapWorkerResult(@NonNull Bitmap bitmapResult, @NonNull ExifInfo exifInfo) {
mBitmapResult = bitmapResult;
mExifInfo = exifInfo;
}
public BitmapWorkerResult(@NonNull Exception bitmapWorkerException) {
mBitmapWorkerException = bitmapWorkerException;
}
}
public BitmapLoadTask(@NonNull Context context,
@NonNull Uri inputUri, @Nullable Uri outputUri,
int requiredWidth, int requiredHeight,
BitmapLoadCallback loadCallback) {
mContext = context;
mInputUri = inputUri;
mOutputUri = outputUri;
mRequiredWidth = requiredWidth;
mRequiredHeight = requiredHeight;
mBitmapLoadCallback = loadCallback;
}
@Override
@NonNull
protected BitmapWorkerResult doInBackground(Void... params) {
if (mInputUri == null) {
return new BitmapWorkerResult(new NullPointerException("Input Uri cannot be null"));
}
try {
processInputUri();
} catch (NullPointerException | IOException e) {
return new BitmapWorkerResult(e);
}
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
options.inSampleSize = BitmapLoadUtils.calculateInSampleSize(options, mRequiredWidth, mRequiredHeight);
options.inJustDecodeBounds = false;
Bitmap decodeSampledBitmap = null;
boolean decodeAttemptSuccess = false;
while (!decodeAttemptSuccess) {
try {
InputStream stream = mContext.getContentResolver().openInputStream(mInputUri);
try {
decodeSampledBitmap = BitmapFactory.decodeStream(stream, null, options);
if (options.outWidth == -1 || options.outHeight == -1) {
return new BitmapWorkerResult(new IllegalArgumentException("Bounds for bitmap could not be retrieved from the Uri: [" + mInputUri + "]"));
}
} finally {
BitmapLoadUtils.close(stream);
}
if (checkSize(decodeSampledBitmap, options)) continue;
decodeAttemptSuccess = true;
} catch (OutOfMemoryError error) {
Log.e(TAG, "doInBackground: BitmapFactory.decodeFileDescriptor: ", error);
options.inSampleSize *= 2;
} catch (IOException e) {
Log.e(TAG, "doInBackground: ImageDecoder.createSource: ", e);
return new BitmapWorkerResult(new IllegalArgumentException("Bitmap could not be decoded from the Uri: [" + mInputUri + "]", e));
}
}
if (decodeSampledBitmap == null) {
return new BitmapWorkerResult(new IllegalArgumentException("Bitmap could not be decoded from the Uri: [" + mInputUri + "]"));
}
int exifOrientation = BitmapLoadUtils.getExifOrientation(mContext, mInputUri);
int exifDegrees = BitmapLoadUtils.exifToDegrees(exifOrientation);
int exifTranslation = BitmapLoadUtils.exifToTranslation(exifOrientation);
ExifInfo exifInfo = new ExifInfo(exifOrientation, exifDegrees, exifTranslation);
Matrix matrix = new Matrix();
if (exifDegrees != 0) {
matrix.preRotate(exifDegrees);
}
if (exifTranslation != 1) {
matrix.postScale(exifTranslation, 1);
}
if (!matrix.isIdentity()) {
return new BitmapWorkerResult(BitmapLoadUtils.transformBitmap(decodeSampledBitmap, matrix), exifInfo);
}
return new BitmapWorkerResult(decodeSampledBitmap, exifInfo);
}
private void processInputUri() throws NullPointerException, IOException {
Log.d(TAG, "Uri scheme: " + mInputUri.getScheme());
if (isDownloadUri(mInputUri)) {
try {
downloadFile(mInputUri, mOutputUri);
} catch (NullPointerException | IOException e) {
Log.e(TAG, "Downloading failed", e);
throw e;
}
} else if (isContentUri(mInputUri)) {
try {
copyFile(mInputUri, mOutputUri);
} catch (NullPointerException | IOException e) {
Log.e(TAG, "Copying failed", e);
throw e;
}
} else if (!isFileUri(mInputUri)) {
String inputUriScheme = mInputUri.getScheme();
Log.e(TAG, "Invalid Uri scheme " + inputUriScheme);
throw new IllegalArgumentException("Invalid Uri scheme" + inputUriScheme);
}
}
private void copyFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException {
Log.d(TAG, "copyFile");
if (outputUri == null) {
throw new NullPointerException("Output Uri is null - cannot copy image");
}
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = mContext.getContentResolver().openInputStream(inputUri);
if (inputStream == null) {
throw new NullPointerException("InputStream for given input Uri is null");
}
if (isContentUri(outputUri)) {
outputStream = mContext.getContentResolver().openOutputStream(outputUri);
} else {
outputStream = new FileOutputStream(new File(outputUri.getPath()));
}
byte buffer[] = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
} finally {
BitmapLoadUtils.close(outputStream);
BitmapLoadUtils.close(inputStream);
// swap uris, because input image was copied to the output destination
// (cropped image will override it later)
mInputUri = mOutputUri;
}
}
private void downloadFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException {
Log.d(TAG, "downloadFile");
if (outputUri == null) {
throw new NullPointerException("Output Uri is null - cannot download image");
}
OkHttpClient client = UCropHttpClientStore.INSTANCE.getClient();
BufferedSource source = null;
Sink sink = null;
Response response = null;
try {
Request request = new Request.Builder()
.url(inputUri.toString())
.build();
response = client.newCall(request).execute();
source = response.body().source();
OutputStream outputStream;
if (isContentUri(mOutputUri)) {
outputStream = mContext.getContentResolver().openOutputStream(outputUri);
} else {
outputStream = new FileOutputStream(new File(outputUri.getPath()));
}
if (outputStream != null) {
sink = Okio.sink(outputStream);
source.readAll(sink);
} else {
throw new NullPointerException("OutputStream for given output Uri is null");
}
} finally {
BitmapLoadUtils.close(source);
BitmapLoadUtils.close(sink);
if (response != null) {
BitmapLoadUtils.close(response.body());
}
client.dispatcher().cancelAll();
// swap uris, because input image was downloaded to the output destination
// (cropped image will override it later)
mInputUri = mOutputUri;
}
}
@Override
protected void onPostExecute(@NonNull BitmapWorkerResult result) {
if (result.mBitmapWorkerException == null) {
mBitmapLoadCallback.onBitmapLoaded(result.mBitmapResult, result.mExifInfo, mInputUri.getPath(), (mOutputUri == null) ? null : mOutputUri.getPath());
} else {
mBitmapLoadCallback.onFailure(result.mBitmapWorkerException);
}
}
private boolean checkSize(Bitmap bitmap, BitmapFactory.Options options) {
int bitmapSize = bitmap != null ? bitmap.getByteCount() : 0;
if (bitmapSize > MAX_BITMAP_SIZE) {
options.inSampleSize *= 2;
return true;
}
return false;
}
private boolean isDownloadUri(Uri uri) {
final String schema = uri.getScheme();
return schema.equals("http") || schema.equals("https");
}
private boolean isContentUri(Uri uri) {
final String schema = uri.getScheme();
return schema.equals("content");
}
private boolean isFileUri(Uri uri) {
final String schema = uri.getScheme();
return schema.equals("file");
}
}
================================================
FILE: ucrop/src/main/java/com/yalantis/ucrop/util/BitmapLoadUtils.java
================================================
package com.yalantis.ucrop.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Point;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import com.yalantis.ucrop.callback.BitmapLoadCallback;
import com.yalantis.ucrop.task.BitmapLoadTask;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface;
/**
* Created by Oleksii Shliama (https://github.com/shliama).
*/
public class BitmapLoadUtils {
private static final String TAG = "BitmapLoadUtils";
public static void decodeBitmapInBackground(@NonNull Context context,
@NonNull Uri uri, @Nullable Uri outputUri,
int requiredWidth, int requiredHeight,
BitmapLoadCallback loadCallback) {
new BitmapLoadTask(context, uri, outputUri, requiredWidth, requiredHeight, loadCallback)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static Bitmap transformBitmap(@NonNull Bitmap bitmap, @NonNull Matrix transformMatrix) {
try {
Bitmap converted = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), transformMatrix, true);
if (!bitmap.sameAs(converted)) {
bit
gitextract_w2fudyhs/
├── .gitattributes
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── README.md
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── mavenpush.gradle
├── sample/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── yalantis/
│ │ └── ucrop/
│ │ └── sample/
│ │ ├── BaseActivity.java
│ │ ├── ResultActivity.java
│ │ ├── SampleActivity.java
│ │ └── SampleApp.java
│ └── res/
│ ├── drawable/
│ │ ├── bg_rounded_rectangle.xml
│ │ ├── ic_done.xml
│ │ └── ic_file_download.xml
│ ├── layout/
│ │ ├── activity_result.xml
│ │ ├── activity_sample.xml
│ │ └── include_settings.xml
│ ├── menu/
│ │ └── menu_result.xml
│ ├── values/
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── xml/
│ └── file_provider_paths.xml
├── settings.gradle
└── ucrop/
├── .gitignore
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src/
└── main/
├── AndroidManifest.xml
├── java/
│ └── com/
│ └── yalantis/
│ └── ucrop/
│ ├── UCrop.java
│ ├── UCropActivity.java
│ ├── UCropFragment.java
│ ├── UCropFragmentCallback.java
│ ├── UCropHttpClientStore.java
│ ├── callback/
│ │ ├── BitmapCropCallback.java
│ │ ├── BitmapLoadCallback.java
│ │ ├── CropBoundsChangeListener.java
│ │ └── OverlayViewChangeListener.java
│ ├── model/
│ │ ├── AspectRatio.java
│ │ ├── CropParameters.java
│ │ ├── ExifInfo.java
│ │ └── ImageState.java
│ ├── task/
│ │ ├── BitmapCropTask.java
│ │ └── BitmapLoadTask.java
│ ├── util/
│ │ ├── BitmapLoadUtils.java
│ │ ├── CubicEasing.java
│ │ ├── EglUtils.java
│ │ ├── FastBitmapDrawable.java
│ │ ├── FileUtils.java
│ │ ├── ImageHeaderParser.java
│ │ ├── RectUtils.java
│ │ ├── RotationGestureDetector.java
│ │ └── SelectedStateListDrawable.java
│ └── view/
│ ├── CropImageView.java
│ ├── GestureCropImageView.java
│ ├── OverlayView.java
│ ├── TransformImageView.java
│ ├── UCropView.java
│ └── widget/
│ ├── AspectRatioTextView.java
│ └── HorizontalProgressWheelView.java
├── jni/
│ ├── Android.mk
│ ├── Application.mk
│ ├── CImg.h
│ ├── com_yalantis_ucrop_task_BitmapCropTask.h
│ └── uCrop.cpp
└── res/
├── anim/
│ ├── ucrop_loader_circle_path.xml
│ └── ucrop_loader_circle_scale.xml
├── color/
│ └── ucrop_scale_text_view_selector.xml
├── drawable/
│ ├── ucrop_crop.xml
│ ├── ucrop_ic_crop.xml
│ ├── ucrop_ic_crop_unselected.xml
│ ├── ucrop_ic_cross.xml
│ ├── ucrop_ic_next.xml
│ ├── ucrop_ic_reset.xml
│ ├── ucrop_ic_rotate.xml
│ ├── ucrop_ic_rotate_unselected.xml
│ ├── ucrop_ic_scale.xml
│ ├── ucrop_ic_scale_unselected.xml
│ ├── ucrop_rotate.xml
│ ├── ucrop_scale.xml
│ ├── ucrop_shadow_upside.xml
│ ├── ucrop_vector_ic_crop.xml
│ ├── ucrop_vector_loader.xml
│ ├── ucrop_vector_loader_animated.xml
│ └── ucrop_wrapper_controls_shape.xml
├── layout/
│ ├── ucrop_activity_photobox.xml
│ ├── ucrop_aspect_ratio.xml
│ ├── ucrop_controls.xml
│ ├── ucrop_fragment_photobox.xml
│ ├── ucrop_layout_rotate_wheel.xml
│ ├── ucrop_layout_scale_wheel.xml
│ └── ucrop_view.xml
├── menu/
│ └── ucrop_menu_activity.xml
├── values/
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── public.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── values.xml
├── values-de/
│ └── strings.xml
├── values-es/
│ └── strings.xml
├── values-fa/
│ └── strings.xml
├── values-fi/
│ └── strings.xml
├── values-fr/
│ └── strings.xml
├── values-in/
│ └── strings.xml
├── values-it/
│ └── strings.xml
├── values-ja/
│ └── strings.xml
├── values-ko/
│ └── strings.xml
├── values-nl/
│ └── strings.xml
├── values-pt/
│ └── strings.xml
├── values-sk/
│ └── strings.xml
├── values-th/
│ └── strings.xml
├── values-tr/
│ └── strings.xml
├── values-zh/
│ └── strings.xml
└── values-zh-rTW/
└── strings.xml
Condensed preview — 125 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,932K chars).
[
{
"path": ".gitattributes",
"chars": 44,
"preview": "ucrop/src/main/jni/CImg.h linguist-vendored\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 489,
"preview": "**Do you want to request a *feature* or report a *bug*?**\n\n**What is the current behavior?**\n\n**What is the expected beh"
},
{
"path": ".gitignore",
"chars": 367,
"preview": "# svn\n*.svn*\n\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# generat"
},
{
"path": "README.md",
"chars": 9058,
"preview": "# uCrop - Image Cropping Library for Android\n\n#### This project aims to provide an ultimate and flexible image cropping "
},
{
"path": "build.gradle",
"chars": 906,
"preview": "buildscript {\n ext {\n androidx_activity_version = \"1.10.1\"\n androidx_appcompat_version = \"1.7.1\"\n "
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 232,
"preview": "#Mon Jul 21 21:21:40 EEST 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\:/"
},
{
"path": "gradle.properties",
"chars": 1396,
"preview": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will o"
},
{
"path": "gradlew",
"chars": 5080,
"preview": "#!/usr/bin/env bash\n\n##############################################################################\n##\n## Gradle start "
},
{
"path": "gradlew.bat",
"chars": 2314,
"preview": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem "
},
{
"path": "jitpack.yml",
"chars": 171,
"preview": "# configuration file for building snapshots and releases with jitpack.io\njdk:\n - openjdk11\nbefore_install:\n - sdk inst"
},
{
"path": "mavenpush.gradle",
"chars": 2916,
"preview": "apply plugin: 'maven-publish'\napply plugin: 'signing'\n\ndef sonatypeRepositoryUrl\nif (isReleaseBuild()) {\n println 'RE"
},
{
"path": "sample/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "sample/build.gradle",
"chars": 1361,
"preview": "apply plugin: 'com.android.application'\n\nandroid {\n compileSdk 36\n namespace = \"com.yalantis.ucrop.sample\"\n def"
},
{
"path": "sample/proguard-rules.pro",
"chars": 665,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "sample/src/main/AndroidManifest.xml",
"chars": 1767,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package="
},
{
"path": "sample/src/main/java/com/yalantis/ucrop/sample/BaseActivity.java",
"chars": 3247,
"preview": "package com.yalantis.ucrop.sample;\n\nimport android.content.DialogInterface;\n\nimport androidx.annotation.NonNull;\nimport "
},
{
"path": "sample/src/main/java/com/yalantis/ucrop/sample/ResultActivity.java",
"chars": 9582,
"preview": "package com.yalantis.ucrop.sample;\n\nimport android.Manifest;\nimport android.annotation.TargetApi;\nimport android.app.Not"
},
{
"path": "sample/src/main/java/com/yalantis/ucrop/sample/SampleActivity.java",
"chars": 21479,
"preview": "package com.yalantis.ucrop.sample;\n\nimport android.annotation.TargetApi;\nimport android.content.Intent;\nimport android.g"
},
{
"path": "sample/src/main/java/com/yalantis/ucrop/sample/SampleApp.java",
"chars": 787,
"preview": "package com.yalantis.ucrop.sample;\n\nimport android.app.Application;\n\nimport com.yalantis.ucrop.UCropHttpClientStore;\n\nim"
},
{
"path": "sample/src/main/res/drawable/bg_rounded_rectangle.xml",
"chars": 452,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <corners\n "
},
{
"path": "sample/src/main/res/drawable/ic_done.xml",
"chars": 341,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "sample/src/main/res/drawable/ic_file_download.xml",
"chars": 327,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "sample/src/main/res/layout/activity_result.xml",
"chars": 891,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "sample/src/main/res/layout/activity_sample.xml",
"chars": 1462,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "sample/src/main/res/layout/include_settings.xml",
"chars": 11133,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:"
},
{
"path": "sample/src/main/res/menu/menu_result.xml",
"chars": 352,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "sample/src/main/res/values/colors.xml",
"chars": 258,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"colorPrimary\">#FF6E40</color>\n <color name=\"color"
},
{
"path": "sample/src/main/res/values/dimens.xml",
"chars": 139,
"preview": "<resources>\n <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n <dimen name=\"activity_vertical_margin\">16dp</d"
},
{
"path": "sample/src/main/res/values/strings.xml",
"chars": 2586,
"preview": "<resources>\n\n <string name=\"app_name\">uCrop</string>\n\n <string name=\"label_sample\">sample</string>\n <string nam"
},
{
"path": "sample/src/main/res/values/themes.xml",
"chars": 403,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light.NoActionBar"
},
{
"path": "sample/src/main/res/xml/file_provider_paths.xml",
"chars": 101,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n <external-path name=\"Pictures\" path=\".\"/>\n</paths>"
},
{
"path": "settings.gradle",
"chars": 27,
"preview": "include ':ucrop', ':sample'"
},
{
"path": "ucrop/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "ucrop/build.gradle",
"chars": 1154,
"preview": "apply plugin: 'com.android.library'\napply from: '../mavenpush.gradle'\n\nandroid {\n compileSdk 36\n namespace = \"com."
},
{
"path": "ucrop/gradle.properties",
"chars": 54,
"preview": "POM_NAME=uCrop\nPOM_ARTIFACT_ID=ucrop\nPOM_PACKAGING=aar"
},
{
"path": "ucrop/proguard-rules.pro",
"chars": 665,
"preview": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /U"
},
{
"path": "ucrop/src/main/AndroidManifest.xml",
"chars": 79,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.yalantis.ucrop\"/>"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/UCrop.java",
"chars": 22688,
"preview": "package com.yalantis.ucrop;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.Fragme"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/UCropActivity.java",
"chars": 32695,
"preview": "package com.yalantis.ucrop;\n\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.graphics.Colo"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/UCropFragment.java",
"chars": 26846,
"preview": "package com.yalantis.ucrop;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.graphics.Bitm"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/UCropFragmentCallback.java",
"chars": 323,
"preview": "package com.yalantis.ucrop;\n\npublic interface UCropFragmentCallback {\n\n /**\n * Return loader status\n * @param"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/UCropHttpClientStore.java",
"chars": 549,
"preview": "package com.yalantis.ucrop;\n\nimport androidx.annotation.NonNull;\n\nimport okhttp3.OkHttpClient;\n\npublic class UCropHttpCl"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapCropCallback.java",
"chars": 297,
"preview": "package com.yalantis.ucrop.callback;\n\nimport android.net.Uri;\n\nimport androidx.annotation.NonNull;\n\npublic interface Bit"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapLoadCallback.java",
"chars": 435,
"preview": "package com.yalantis.ucrop.callback;\n\nimport android.graphics.Bitmap;\n\nimport com.yalantis.ucrop.model.ExifInfo;\n\nimport"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/callback/CropBoundsChangeListener.java",
"chars": 191,
"preview": "package com.yalantis.ucrop.callback;\n\n/**\n * Interface for crop bound change notifying.\n */\npublic interface CropBoundsC"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/callback/OverlayViewChangeListener.java",
"chars": 201,
"preview": "package com.yalantis.ucrop.callback;\n\nimport android.graphics.RectF;\n\n/**\n * Created by Oleksii Shliama.\n */\npublic inte"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/model/AspectRatio.java",
"chars": 1658,
"preview": "package com.yalantis.ucrop.model;\n\nimport android.os.Parcel;\nimport android.os.Parcelable;\n\nimport androidx.annotation.N"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java",
"chars": 1555,
"preview": "package com.yalantis.ucrop.model;\n\nimport android.graphics.Bitmap;\n\n/**\n * Created by Oleksii Shliama [https://github.co"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/model/ExifInfo.java",
"chars": 1601,
"preview": "package com.yalantis.ucrop.model;\n\n/**\n * Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.\n */\npublic"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/model/ImageState.java",
"chars": 849,
"preview": "package com.yalantis.ucrop.model;\n\nimport android.graphics.RectF;\n\n/**\n * Created by Oleksii Shliama [https://github.com"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java",
"chars": 7696,
"preview": "package com.yalantis.ucrop.task;\n\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android."
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java",
"chars": 10545,
"preview": "package com.yalantis.ucrop.task;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphic"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/BitmapLoadUtils.java",
"chars": 6285,
"preview": "package com.yalantis.ucrop.util;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphic"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/CubicEasing.java",
"chars": 650,
"preview": "package com.yalantis.ucrop.util;\n\npublic final class CubicEasing {\n\n public static float easeOut(float time, float st"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/EglUtils.java",
"chars": 4630,
"preview": "package com.yalantis.ucrop.util;\n\nimport android.annotation.TargetApi;\nimport android.opengl.EGL14;\nimport android.openg"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/FastBitmapDrawable.java",
"chars": 2464,
"preview": "/*\n * Copyright (C) 2008 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/FileUtils.java",
"chars": 8286,
"preview": "/*\n * Copyright (C) 2007-2008 OpenIntents.org\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * y"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/ImageHeaderParser.java",
"chars": 15930,
"preview": "/*\n * Copyright 2015 Google, Inc. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or "
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/RectUtils.java",
"chars": 2370,
"preview": "package com.yalantis.ucrop.util;\n\nimport android.graphics.RectF;\n\npublic class RectUtils {\n\n /**\n * Gets a float "
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/RotationGestureDetector.java",
"chars": 3703,
"preview": "package com.yalantis.ucrop.util;\n\nimport android.view.MotionEvent;\n\nimport androidx.annotation.NonNull;\n\npublic class Ro"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/util/SelectedStateListDrawable.java",
"chars": 1189,
"preview": "package com.yalantis.ucrop.util;\n\nimport android.graphics.PorterDuff;\nimport android.graphics.drawable.Drawable;\nimport "
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/view/CropImageView.java",
"chars": 24558,
"preview": "package com.yalantis.ucrop.view;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android."
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/view/GestureCropImageView.java",
"chars": 5011,
"preview": "package com.yalantis.ucrop.view;\n\nimport android.content.Context;\nimport android.util.AttributeSet;\nimport android.view."
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/view/OverlayView.java",
"chars": 21616,
"preview": "package com.yalantis.ucrop.view;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android."
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/view/TransformImageView.java",
"chars": 11274,
"preview": "package com.yalantis.ucrop.view;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphic"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/view/UCropView.java",
"chars": 2641,
"preview": "package com.yalantis.ucrop.view;\n\nimport android.content.Context;\nimport android.content.res.TypedArray;\nimport android."
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/view/widget/AspectRatioTextView.java",
"chars": 5378,
"preview": "package com.yalantis.ucrop.view.widget;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport and"
},
{
"path": "ucrop/src/main/java/com/yalantis/ucrop/view/widget/HorizontalProgressWheelView.java",
"chars": 5981,
"preview": "package com.yalantis.ucrop.view.widget;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport and"
},
{
"path": "ucrop/src/main/jni/Android.mk",
"chars": 356,
"preview": "LOCAL_PATH := $(call my-dir)\n\ninclude $(CLEAR_VARS)\n\nLOCAL_MODULE := ucrop\nLOCAL_SRC_FILES := uCrop.cpp\n\nLOCAL_LDLIBS"
},
{
"path": "ucrop/src/main/jni/Application.mk",
"chars": 189,
"preview": "APP_ABI := armeabi-v7a x86 x86_64 arm64-v8a\nAPP_CPPFLAGS += -frtti\nAPP_CPPFLAGS += -fexceptions\nAPP_CPPFLAGS += -DANDROI"
},
{
"path": "ucrop/src/main/jni/CImg.h",
"chars": 3463930,
"preview": "/*\n #\n # File : CImg.h\n # ( C++ header file )\n #\n # Description : C++ Template Image"
},
{
"path": "ucrop/src/main/jni/com_yalantis_ucrop_task_BitmapCropTask.h",
"chars": 655,
"preview": "/* DO NOT EDIT THIS FILE - it is machine generated */\n#include <jni.h>\n/* Header for class com_yalantis_ucrop_task_Bitma"
},
{
"path": "ucrop/src/main/jni/uCrop.cpp",
"chars": 3791,
"preview": "//\n// Created by Oleksii Shliama on 3/13/16.\n//\n\n#include <stdio.h>\n#include <jni.h>\n#include <vector>\n#include <android"
},
{
"path": "ucrop/src/main/res/anim/ucrop_loader_circle_path.xml",
"chars": 639,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <objectAnim"
},
{
"path": "ucrop/src/main/res/anim/ucrop_loader_circle_scale.xml",
"chars": 904,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <objectAnim"
},
{
"path": "ucrop/src/main/res/color/ucrop_scale_text_view_selector.xml",
"chars": 287,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "ucrop/src/main/res/drawable/ucrop_crop.xml",
"chars": 306,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_crop.xml",
"chars": 604,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_crop_unselected.xml",
"chars": 606,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_cross.xml",
"chars": 416,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_next.xml",
"chars": 366,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_reset.xml",
"chars": 569,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_rotate.xml",
"chars": 1035,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_rotate_unselected.xml",
"chars": 1037,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_scale.xml",
"chars": 512,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_ic_scale_unselected.xml",
"chars": 514,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_rotate.xml",
"chars": 310,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_scale.xml",
"chars": 308,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <item "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_shadow_upside.xml",
"chars": 245,
"preview": "<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:shape=\"rectangle\">\n <gradient\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_vector_ic_crop.xml",
"chars": 569,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\""
},
{
"path": "ucrop/src/main/res/drawable/ucrop_vector_loader.xml",
"chars": 895,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n andro"
},
{
"path": "ucrop/src/main/res/drawable/ucrop_vector_loader_animated.xml",
"chars": 417,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "ucrop/src/main/res/drawable/ucrop_wrapper_controls_shape.xml",
"chars": 288,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:sha"
},
{
"path": "ucrop/src/main/res/layout/ucrop_activity_photobox.xml",
"chars": 2222,
"preview": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk"
},
{
"path": "ucrop/src/main/res/layout/ucrop_aspect_ratio.xml",
"chars": 218,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout style=\"@style/ucrop_WrapperIconState\">\n\n <com.yalantis.ucrop.view"
},
{
"path": "ucrop/src/main/res/layout/ucrop_controls.xml",
"chars": 3609,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xm"
},
{
"path": "ucrop/src/main/res/layout/ucrop_fragment_photobox.xml",
"chars": 1529,
"preview": "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk"
},
{
"path": "ucrop/src/main/res/layout/ucrop_layout_rotate_wheel.xml",
"chars": 1959,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xm"
},
{
"path": "ucrop/src/main/res/layout/ucrop_layout_scale_wheel.xml",
"chars": 725,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xm"
},
{
"path": "ucrop/src/main/res/layout/ucrop_view.xml",
"chars": 728,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:"
},
{
"path": "ucrop/src/main/res/menu/ucrop_menu_activity.xml",
"chars": 553,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app="
},
{
"path": "ucrop/src/main/res/values/attrs.xml",
"chars": 1212,
"preview": "<resources>\n\n <declare-styleable name=\"ucrop_UCropView\">\n\n <!--Crop image view-->\n <attr name=\"ucrop_as"
},
{
"path": "ucrop/src/main/res/values/colors.xml",
"chars": 1830,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <!--Palette-->\n <color name=\"ucrop_color_black\">#000</color>\n"
},
{
"path": "ucrop/src/main/res/values/dimens.xml",
"chars": 1507,
"preview": "<resources>\n\n <!--uCrop Activity-->\n <dimen name=\"ucrop_padding_crop_frame\">16dp</dimen>\n <dimen name=\"ucrop_si"
},
{
"path": "ucrop/src/main/res/values/public.xml",
"chars": 120,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <!--Do not expose library resources-->\n <public/>\n</resources>"
},
{
"path": "ucrop/src/main/res/values/strings.xml",
"chars": 648,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">Original</string>\n <string name=\"ucrop_label_edit_photo\">Edit Ph"
},
{
"path": "ucrop/src/main/res/values/styles.xml",
"chars": 2776,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n <style name=\"ucrop_ImageViewWidgetIcon\">\n <item name=\"and"
},
{
"path": "ucrop/src/main/res/values/values.xml",
"chars": 131,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <integer name=\"ucrop_progress_loading_anim_time\">1500</integer>\n<"
},
{
"path": "ucrop/src/main/res/values-de/strings.xml",
"chars": 395,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"ucrop_label_edit_photo\">Foto editieren</string>\n "
},
{
"path": "ucrop/src/main/res/values-es/strings.xml",
"chars": 342,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">Original</string>\n <string name=\"ucrop_label_edit_photo\">Editar "
},
{
"path": "ucrop/src/main/res/values-fa/strings.xml",
"chars": 332,
"preview": " \n<resources>\n\n <string name=\"ucrop_label_original\">اصلی</string>\n <string name=\"ucrop_label_edit_photo\">ویرایش ع"
},
{
"path": "ucrop/src/main/res/values-fi/strings.xml",
"chars": 358,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"ucrop_label_edit_photo\">Muokkaa kuvaa</string>\n "
},
{
"path": "ucrop/src/main/res/values-fr/strings.xml",
"chars": 205,
"preview": "<resources>\n <string name=\"ucrop_label_original\">Original</string>\n <string name=\"ucrop_label_edit_photo\">Modifier"
},
{
"path": "ucrop/src/main/res/values-in/strings.xml",
"chars": 338,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">Asli</string>\n <string name=\"ucrop_label_edit_photo\">Edit Foto</"
},
{
"path": "ucrop/src/main/res/values-it/strings.xml",
"chars": 200,
"preview": "<resources>\n <string name=\"ucrop_label_original\">Originale</string>\n <string name=\"ucrop_label_edit_photo\">Modific"
},
{
"path": "ucrop/src/main/res/values-ja/strings.xml",
"chars": 354,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"ucrop_label_original\">オリジナル</string>\n <string na"
},
{
"path": "ucrop/src/main/res/values-ko/strings.xml",
"chars": 219,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"ucrop_label_edit_photo\">사진 편집</string>\n <string "
},
{
"path": "ucrop/src/main/res/values-nl/strings.xml",
"chars": 361,
"preview": "<resources>\r\n <string name=\"ucrop_label_original\">Origineel</string>\r\n <string name=\"ucrop_label_edit_photo\">Foto "
},
{
"path": "ucrop/src/main/res/values-pt/strings.xml",
"chars": 342,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">Original</string>\n <string name=\"ucrop_label_edit_photo\">Editar "
},
{
"path": "ucrop/src/main/res/values-sk/strings.xml",
"chars": 209,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">Pôvodná</string>\n <string name=\"ucrop_label_edit_photo\">Upraviť "
},
{
"path": "ucrop/src/main/res/values-th/strings.xml",
"chars": 339,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">เป็นต้นฉบับ</string>\n <string name=\"ucrop_label_edit_photo\">แก้ไ"
},
{
"path": "ucrop/src/main/res/values-tr/strings.xml",
"chars": 346,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">Orijinal</string>\n <string name=\"ucrop_label_edit_photo\">Fotoğra"
},
{
"path": "ucrop/src/main/res/values-zh/strings.xml",
"chars": 312,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">原始比例</string>\n <string name=\"ucrop_label_edit_photo\">裁剪</string>"
},
{
"path": "ucrop/src/main/res/values-zh-rTW/strings.xml",
"chars": 507,
"preview": "<resources>\n\n <string name=\"ucrop_label_original\">原始比例</string>\n <string name=\"ucrop_label_edit_photo\">裁切</string>"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the Yalantis/uCrop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 125 files (3.6 MB), approximately 960.8k tokens. 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.