├── .gitattributes
├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── mavenpush.gradle
├── preview.gif
├── preview.png
├── 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
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── 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
├── jniLibs
├── arm64-v8a
│ └── libucrop.so
├── armeabi-v7a
│ └── libucrop.so
├── armeabi
│ └── libucrop.so
├── x86
│ └── libucrop.so
└── x86_64
│ └── libucrop.so
└── res
├── anim
├── ucrop_loader_circle_path.xml
└── ucrop_loader_circle_scale.xml
├── color
└── ucrop_scale_text_view_selector.xml
├── drawable-hdpi
├── ucrop_ic_angle.png
└── ucrop_ic_done.png
├── drawable-ldpi
├── ucrop_ic_angle.png
└── ucrop_ic_done.png
├── drawable-mdpi
├── ucrop_ic_angle.png
└── ucrop_ic_done.png
├── drawable-xhdpi
├── ucrop_ic_angle.png
└── ucrop_ic_done.png
├── drawable-xxhdpi
├── ucrop_ic_angle.png
└── ucrop_ic_done.png
├── drawable-xxxhdpi
├── ucrop_ic_angle.png
└── ucrop_ic_done.png
├── 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-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-ru
└── strings.xml
├── values-sk
└── strings.xml
├── values-th
└── strings.xml
├── values-tr
└── strings.xml
├── values-zh-rTW
└── strings.xml
├── values-zh
└── strings.xml
└── values
├── attrs.xml
├── colors.xml
├── dimens.xml
├── public.xml
├── strings.xml
├── styles.xml
└── values.xml
/.gitattributes:
--------------------------------------------------------------------------------
1 | ucrop/src/main/jni/CImg.h linguist-vendored
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | **Do you want to request a *feature* or report a *bug*?**
2 |
3 | **What is the current behavior?**
4 |
5 | **What is the expected behavior?**
6 |
7 | **If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.**
8 |
9 | **Please attach any *image files*, *URL* and `stack trace` that can be used to reproduce the *bug*.**
10 |
11 | **Which versions of uCrop, and which Android API versions are affected by this issue? Did this work in previous versions of uCrop?**
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # svn
2 | *.svn*
3 |
4 | # built application files
5 | *.apk
6 | *.ap_
7 |
8 | # files for the dex VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # generated GUI files
15 | */R.java
16 |
17 | # generated folder
18 | bin
19 | gen
20 |
21 | # local
22 | local.properties
23 |
24 | proguard_logs/
25 |
26 | # log files
27 | log*.txt
28 |
29 | # archives
30 | *.gz
31 | *.tar
32 | *.zip
33 |
34 | # eclipse
35 | *.metadata
36 | *.settings
37 | *.prefs
38 |
39 | #idea
40 | *.idea
41 | *.iml
42 | out/
43 |
44 | build/
45 | captures/
46 | .gradle/
47 |
48 | .DS_Store
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # uCrop - Image Cropping Library for Android
2 |
3 | #### This project aims to provide an ultimate and flexible image cropping experience. Made in [Yalantis](https://yalantis.com/?utm_source=github)
4 |
5 | #### [How We Created uCrop](https://yalantis.com/blog/how-we-created-ucrop-our-own-image-cropping-library-for-android/)
6 | #### Check this [project on Dribbble](https://dribbble.com/shots/2484752-uCrop-Image-Cropping-Library)
7 |
8 |
9 |
10 | # Usage
11 |
12 | *For a working implementation, please have a look at the Sample Project - sample*
13 |
14 |
15 |
16 | 1. Include the library as a local library project.
17 |
18 | ```
19 | allprojects {
20 | repositories {
21 | jcenter()
22 | maven { url "https://jitpack.io" }
23 | }
24 | }
25 | ```
26 |
27 | ``` implementation 'com.github.yalantis:ucrop:2.2.10' ``` - lightweight general solution
28 |
29 | ``` implementation 'com.github.yalantis:ucrop:2.2.9-native' ``` - get power of the native code to preserve image quality (+ about 1.5 MB to an apk size)
30 |
31 | 2. Add UCropActivity into your AndroidManifest.xml
32 |
33 | ```
34 |
38 | ```
39 |
40 | 3. The uCrop configuration is created using the builder pattern.
41 |
42 | ```java
43 | UCrop.of(sourceUri, destinationUri)
44 | .withAspectRatio(16, 9)
45 | .withMaxResultSize(maxWidth, maxHeight)
46 | .start(context);
47 | ```
48 |
49 | 4. Override `onActivityResult` method and handle uCrop result.
50 |
51 | ```java
52 | @Override
53 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
54 | if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) {
55 | final Uri resultUri = UCrop.getOutput(data);
56 | } else if (resultCode == UCrop.RESULT_ERROR) {
57 | final Throwable cropError = UCrop.getError(data);
58 | }
59 | }
60 | ```
61 |
62 | 5. You may want to add this to your PROGUARD config:
63 |
64 | ```
65 | -dontwarn com.yalantis.ucrop**
66 | -keep class com.yalantis.ucrop** { *; }
67 | -keep interface com.yalantis.ucrop** { *; }
68 | ```
69 |
70 | # Customization
71 |
72 | If you want to let your users choose crop ratio dynamically, just do not call `withAspectRatio(x, y)`.
73 |
74 | uCrop builder class has method `withOptions(UCrop.Options options)` which extends library configurations.
75 |
76 | Currently, you can change:
77 |
78 | * image compression format (e.g. PNG, JPEG), compression
79 | * image compression quality [0 - 100]. PNG which is lossless, will ignore the quality setting.
80 | * whether all gestures are enabled simultaneously
81 | * maximum size for Bitmap that is decoded from source Uri and used within crop view. If you want to override the default behaviour.
82 | * toggle whether to show crop frame/guidelines
83 | * setup color/width/count of crop frame/rows/columns
84 | * choose whether you want rectangle or oval(`options.setCircleDimmedLayer(true)`) crop area
85 | * the UI colors (Toolbar, StatusBar, active widget state)
86 | * and more...
87 |
88 | # Compatibility
89 |
90 | * Library - Android ICS 4.0+ (API 14) (Android GINGERBREAD 2.3+ (API 10) for versions <= 1.3.2)
91 | * Sample - Android ICS 4.0+ (API 14)
92 | * CPU - armeabi armeabi-v7a x86 x86_64 arm64-v8a (for versions >= 2.1.2)
93 |
94 | # Changelog
95 |
96 | ### Version: 2.2.10
97 |
98 | * Fixed [#926](https://github.com/Yalantis/uCrop/issues/926)
99 |
100 | ### Version: 2.2.9
101 |
102 | * Update compileSdk and targetSdk versions up to 33
103 | * Fixed [#867](https://github.com/Yalantis/uCrop/issues/867)
104 | * Fixed [#873](https://github.com/Yalantis/uCrop/issues/873)
105 | * And other improvements
106 |
107 | ### Version: 2.2.8
108 |
109 | * Merged pending pull requests with improvements and bugfixes
110 | * Update compileSdk and targetSdk versions up to 31
111 | * Add localizations
112 | * Fixed [#609](https://github.com/Yalantis/uCrop/issues/609)
113 | * Fixed [#794](https://github.com/Yalantis/uCrop/issues/794)
114 |
115 |
116 | ### Version: 2.2.5
117 |
118 | * Fixed [#584](https://github.com/Yalantis/uCrop/issues/584)
119 | * Fixed [#598](https://github.com/Yalantis/uCrop/issues/598)
120 | * Fixed [#543](https://github.com/Yalantis/uCrop/issues/543)
121 | * Fixed [#602](https://github.com/Yalantis/uCrop/issues/602)
122 | * And other improvements
123 |
124 | ### Version: 2.2.4
125 |
126 | * **AndroidX migration**
127 | * Redesign
128 | * Several fixes including [#550](https://github.com/Yalantis/uCrop/issues/550)
129 |
130 | ### Version: 2.2.3
131 |
132 | * Several fixes including [#445](https://github.com/Yalantis/uCrop/issues/445), [#465](https://github.com/Yalantis/uCrop/issues/465) and more!
133 | * Material design support
134 | * uCrop fragment as child fragment
135 | * Added the Italian language
136 |
137 | ### Version: 2.2.2
138 |
139 | * uCrop fragment added
140 | * bugfix
141 |
142 | ### Version: 2.2.1
143 |
144 | * Fix including [#285](https://github.com/Yalantis/uCrop/issues/285)
145 |
146 | ### Version: 2.2
147 |
148 | * 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!
149 | * 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)
150 |
151 | ### Version: 2.1
152 |
153 | * 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)
154 | * Added API to set custom set of aspect ratio options for the user. [#131](https://github.com/Yalantis/uCrop/issues/131)
155 | * Added API to set all configs via UCrop.Options class. [#126](https://github.com/Yalantis/uCrop/issues/126)
156 | * Added ABI x86_64 support. [#105](https://github.com/Yalantis/uCrop/issues/105)
157 |
158 | ### Version: 2.0
159 |
160 | * Native image crop (able to crop high-resolution images, e.g. 16MP & 32MP images on Nexus 5X).
161 | * WebP compression format is not supported at the moment (choose JPEG or PNG).
162 | * Now library copies EXIF data to cropped image (size and orientation are updated).
163 |
164 | ### Version: 1.5
165 |
166 | * Introduced "Freestyle" crop (you can resize crop rectangle by dragging it corners) [#32](https://github.com/Yalantis/uCrop/issues/32)
167 | * Now image & crop view paddings are not associated [#68](https://github.com/Yalantis/uCrop/issues/68)
168 | * Updated API
169 |
170 | ### Version: 1.4
171 |
172 | * Introduced HTTP(s) Uri support!
173 | * Image is cropped in a background thread.
174 | * Showing loader while Bitmap is processed (both loading and cropping).
175 | * Several bug fixes.
176 | * Couple new things to configure.
177 | * Updated minSdkVersion to Android ICS 4.0 (no reason to support couple percents of old phones).
178 |
179 | ### Version: 1.3
180 |
181 | * Image is loaded in a background thread. Better error-handling for image decoding.
182 | * Improved EXIF data support (rotation and mirror).
183 | * Small UI updates.
184 | * Couple new things to configure.
185 |
186 | * Sample updated with the possibility to choose custom aspect ratio.
187 |
188 | ### Version: 1.2
189 |
190 | * Updated core logic so an image corrects its position smoothly and obviously.
191 |
192 | ### Version: 1.1
193 |
194 | * UCrop builder was updated and now UCrop.Options class has even more values to setup.
195 |
196 | ### Version: 1.0
197 |
198 | * Initial Build
199 |
200 | ### Let us know!
201 |
202 | 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.
203 |
204 | #### Apps using uCrop
205 |
206 | - [Thirty](https://play.google.com/store/apps/details?id=com.twominds.thirty).
207 | - [Light Smart HD](https://play.google.com/store/apps/details?id=com.SmartCamera.simple).
208 | - [BCReader](https://play.google.com/store/apps/details?id=com.iac.bcreader).
209 | - [Xprezia: Share Your Passion](https://play.google.com/store/apps/details?id=com.xprezzia.cnj).
210 |
211 | ## License
212 |
213 | Copyright 2017, Yalantis
214 |
215 | Software doesn't collect, store or transfer data to Yalantis or third parties.
216 | Emplacement of this Software is carried out locally at device.
217 |
218 | Licensed under the Apache License, Version 2.0 (the "License");
219 | you may not use this file except in compliance with the License.
220 | You may obtain a copy of the License at
221 |
222 | http://www.apache.org/licenses/LICENSE-2.0
223 |
224 | Unless required by applicable law or agreed to in writing, software
225 | distributed under the License is distributed on an "AS IS" BASIS,
226 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
227 | See the License for the specific language governing permissions and
228 | limitations under the License.
229 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext {
3 | androidx_appcompat_version = "1.6.1"
4 | androidx_core_version = "1.9.0"
5 | androidx_exifinterface_version = "1.3.6"
6 | androidx_transition_version = "1.4.1"
7 | constraintlayout_version = "2.1.4"
8 | }
9 |
10 | repositories {
11 | mavenCentral()
12 | maven {
13 | url 'https://maven.google.com/'
14 | name 'Google'
15 | }
16 | }
17 | dependencies {
18 | classpath 'com.android.tools.build:gradle:7.4.2'
19 | }
20 | }
21 |
22 | def isReleaseBuild() {
23 | return version.contains("SNAPSHOT") == false
24 | }
25 |
26 | allprojects {
27 | version = VERSION_NAME
28 | group = GROUP
29 |
30 | repositories {
31 | mavenCentral()
32 | maven {
33 | url 'https://maven.google.com/'
34 | name 'Google'
35 | }
36 | }
37 | }
38 |
39 | task clean(type: Delete) {
40 | delete rootProject.buildDir
41 | }
42 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | VERSION_NAME=2.2.9-native
21 | VERSION_CODE=28
22 | GROUP=com.yalantis
23 |
24 | POM_DESCRIPTION=Android Library for cropping images
25 | POM_URL=https://github.com/Yalantis/uCrop
26 | POM_SCM_URL=https://github.com/Yalantis/uCrop
27 | POM_SCM_CONNECTION=scm:git@github.com/Yalantis/uCrop.git
28 | POM_SCM_DEV_CONNECTION=scm:git@github.com/Yalantis/uCrop.git
29 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
30 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0
31 | POM_LICENCE_DIST=repo
32 | POM_DEVELOPER_ID=yalantis
33 | POM_DEVELOPER_NAME=Yalantis
34 | android.useAndroidX=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 06 16:37:21 EEST 2019
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | # configuration file for building snapshots and releases with jitpack.io
2 | jdk:
3 | - openjdk11
4 | before_install:
5 | - sdk install java 11.0.10-open
6 | - sdk use java 11.0.10-open
--------------------------------------------------------------------------------
/mavenpush.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'maven-publish'
2 | apply plugin: 'signing'
3 |
4 | def sonatypeRepositoryUrl
5 | if (isReleaseBuild()) {
6 | println 'RELEASE BUILD'
7 | sonatypeRepositoryUrl = hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
8 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
9 | } else {
10 | println 'DEBUG BUILD'
11 | sonatypeRepositoryUrl = hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
12 | : "https://oss.sonatype.org/content/repositories/snapshots/"
13 | }
14 |
15 | def getRepositoryUsername() {
16 | return hasProperty('nexusUsername') ? nexusUsername : ""
17 | }
18 |
19 | def getRepositoryPassword() {
20 | return hasProperty('nexusPassword') ? nexusPassword : ""
21 | }
22 |
23 |
24 | tasks.register('androidJavadocs', Javadoc) {
25 | source = android.sourceSets.main.java.sourceFiles
26 | }
27 |
28 | tasks.register('androidJavadocsJar', Jar) {
29 | classifier = 'javadoc'
30 | //basename = artifact_id
31 | from androidJavadocs.destinationDir
32 | }
33 |
34 | tasks.register('androidSourcesJar', Jar) {
35 | classifier = 'sources'
36 | //basename = artifact_id
37 | from android.sourceSets.main.java.sourceFiles
38 | }
39 |
40 | publishing {
41 | repositories {
42 | maven {
43 | url = sonatypeRepositoryUrl
44 | credentials {
45 | username = getRepositoryUsername()
46 | password = getRepositoryPassword()
47 | }
48 | }
49 | }
50 | publications {
51 | maven(MavenPublication) {
52 | afterEvaluate { project ->
53 | from components.release
54 | artifact androidSourcesJar
55 | artifact androidJavadocsJar
56 | version = project.version
57 | }
58 |
59 | pom {
60 | name = POM_NAME
61 | packaging = POM_PACKAGING
62 | description = POM_DESCRIPTION
63 | url = POM_URL
64 | scm {
65 | url = POM_SCM_URL
66 | connection = POM_SCM_CONNECTION
67 | developerConnection = POM_SCM_DEV_CONNECTION
68 | }
69 |
70 | licenses {
71 | license {
72 | name = POM_LICENCE_NAME
73 | url = POM_LICENCE_URL
74 | distribution = POM_LICENCE_DIST
75 | }
76 | }
77 |
78 | developers {
79 | developer {
80 | id = POM_DEVELOPER_ID
81 | name = POM_DEVELOPER_NAME
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 |
90 | signing {
91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("publishing") }
92 | sign publishing.publications.maven
93 | sign configurations.archives
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/preview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/preview.gif
--------------------------------------------------------------------------------
/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/preview.png
--------------------------------------------------------------------------------
/sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdk 33
5 | defaultConfig {
6 | applicationId "com.yalantis.ucrop.sample"
7 | minSdkVersion 14
8 | targetSdkVersion 33
9 | versionCode 13
10 | versionName "1.2.4"
11 | }
12 | flavorDimensions "default"
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | compileOptions {
20 | sourceCompatibility JavaVersion.VERSION_1_8
21 | targetCompatibility JavaVersion.VERSION_1_8
22 | }
23 | lintOptions {
24 | abortOnError false
25 | }
26 | productFlavors {
27 | activity {
28 | buildConfigField("int","RequestMode", "1")
29 | }
30 | fragment {
31 | buildConfigField("int","RequestMode", "2")
32 | }
33 | }
34 | }
35 |
36 | dependencies {
37 | implementation "androidx.appcompat:appcompat:${androidx_appcompat_version}"
38 | implementation "androidx.core:core:${androidx_core_version}"
39 | implementation "androidx.constraintlayout:constraintlayout:${constraintlayout_version}"
40 | implementation "com.squareup.okhttp3:okhttp:3.12.13"
41 | implementation project(':ucrop')
42 | }
--------------------------------------------------------------------------------
/sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/oleksii/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
21 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/yalantis/ucrop/sample/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.sample;
2 |
3 | import android.content.DialogInterface;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.annotation.Nullable;
7 | import androidx.appcompat.app.AlertDialog;
8 | import androidx.appcompat.app.AppCompatActivity;
9 | import androidx.core.app.ActivityCompat;
10 |
11 | /**
12 | * Created by Oleksii Shliama (https://github.com/shliama).
13 | */
14 | public class BaseActivity extends AppCompatActivity {
15 |
16 | protected static final int REQUEST_STORAGE_READ_ACCESS_PERMISSION = 101;
17 | protected static final int REQUEST_STORAGE_WRITE_ACCESS_PERMISSION = 102;
18 |
19 | private AlertDialog mAlertDialog;
20 |
21 | /**
22 | * Hide alert dialog if any.
23 | */
24 | @Override
25 | protected void onStop() {
26 | super.onStop();
27 | if (mAlertDialog != null && mAlertDialog.isShowing()) {
28 | mAlertDialog.dismiss();
29 | }
30 | }
31 |
32 |
33 | /**
34 | * Requests given permission.
35 | * If the permission has been denied previously, a Dialog will prompt the user to grant the
36 | * permission, otherwise it is requested directly.
37 | */
38 | protected void requestPermission(final String permission, String rationale, final int requestCode) {
39 | if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
40 | showAlertDialog(getString(R.string.permission_title_rationale), rationale,
41 | new DialogInterface.OnClickListener() {
42 | @Override
43 | public void onClick(DialogInterface dialog, int which) {
44 | ActivityCompat.requestPermissions(BaseActivity.this,
45 | new String[]{permission}, requestCode);
46 | }
47 | }, getString(R.string.label_ok), null, getString(R.string.label_cancel));
48 | } else {
49 | ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode);
50 | }
51 | }
52 |
53 | /**
54 | * This method shows dialog with given title & message.
55 | * Also there is an option to pass onClickListener for positive & negative button.
56 | *
57 | * @param title - dialog title
58 | * @param message - dialog message
59 | * @param onPositiveButtonClickListener - listener for positive button
60 | * @param positiveText - positive button text
61 | * @param onNegativeButtonClickListener - listener for negative button
62 | * @param negativeText - negative button text
63 | */
64 | protected void showAlertDialog(@Nullable String title, @Nullable String message,
65 | @Nullable DialogInterface.OnClickListener onPositiveButtonClickListener,
66 | @NonNull String positiveText,
67 | @Nullable DialogInterface.OnClickListener onNegativeButtonClickListener,
68 | @NonNull String negativeText) {
69 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
70 | builder.setTitle(title);
71 | builder.setMessage(message);
72 | builder.setPositiveButton(positiveText, onPositiveButtonClickListener);
73 | builder.setNegativeButton(negativeText, onNegativeButtonClickListener);
74 | mAlertDialog = builder.show();
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/yalantis/ucrop/sample/ResultActivity.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.sample;
2 |
3 | import android.Manifest;
4 | import android.annotation.TargetApi;
5 | import android.app.NotificationChannel;
6 | import android.app.NotificationManager;
7 | import android.app.PendingIntent;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.pm.PackageManager;
11 | import android.content.pm.ResolveInfo;
12 | import android.graphics.BitmapFactory;
13 | import android.graphics.Color;
14 | import android.net.Uri;
15 | import android.os.Build;
16 | import android.os.Bundle;
17 | import android.os.Environment;
18 | import android.util.Log;
19 | import android.view.Menu;
20 | import android.view.MenuItem;
21 | import android.widget.Toast;
22 |
23 | import com.yalantis.ucrop.view.UCropView;
24 |
25 | import java.io.File;
26 | import java.io.FileInputStream;
27 | import java.io.FileOutputStream;
28 | import java.nio.channels.FileChannel;
29 | import java.util.Calendar;
30 | import java.util.List;
31 |
32 | import androidx.annotation.NonNull;
33 | import androidx.appcompat.app.ActionBar;
34 | import androidx.appcompat.widget.Toolbar;
35 | import androidx.core.app.ActivityCompat;
36 | import androidx.core.app.NotificationCompat;
37 | import androidx.core.content.FileProvider;
38 |
39 | import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
40 | import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
41 |
42 | /**
43 | * Created by Oleksii Shliama (https://github.com/shliama).
44 | */
45 | public class ResultActivity extends BaseActivity {
46 |
47 | private static final String TAG = "ResultActivity";
48 | private static final String CHANNEL_ID = "3000";
49 | private static final int DOWNLOAD_NOTIFICATION_ID_DONE = 911;
50 |
51 | public static void startWithUri(@NonNull Context context, @NonNull Uri uri) {
52 | Intent intent = new Intent(context, ResultActivity.class);
53 | intent.setData(uri);
54 | context.startActivity(intent);
55 | }
56 |
57 | @Override
58 | protected void onCreate(Bundle savedInstanceState) {
59 | super.onCreate(savedInstanceState);
60 | setContentView(R.layout.activity_result);
61 | Uri uri = getIntent().getData();
62 | if (uri != null) {
63 | try {
64 | UCropView uCropView = findViewById(R.id.ucrop);
65 | uCropView.getCropImageView().setImageUri(uri, null);
66 | uCropView.getOverlayView().setShowCropFrame(false);
67 | uCropView.getOverlayView().setShowCropGrid(false);
68 | uCropView.getOverlayView().setDimmedColor(Color.TRANSPARENT);
69 | } catch (Exception e) {
70 | Log.e(TAG, "setImageUri", e);
71 | Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
72 | }
73 | }
74 | final BitmapFactory.Options options = new BitmapFactory.Options();
75 | options.inJustDecodeBounds = true;
76 | BitmapFactory.decodeFile(new File(getIntent().getData().getPath()).getAbsolutePath(), options);
77 |
78 | setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
79 | final ActionBar actionBar = getSupportActionBar();
80 | if (actionBar != null) {
81 | actionBar.setDisplayHomeAsUpEnabled(true);
82 | actionBar.setTitle(getString(R.string.format_crop_result_d_d, options.outWidth, options.outHeight));
83 | }
84 | }
85 |
86 | @Override
87 | public boolean onCreateOptionsMenu(final Menu menu) {
88 | getMenuInflater().inflate(R.menu.menu_result, menu);
89 | return true;
90 | }
91 |
92 | @Override
93 | public boolean onOptionsItemSelected(MenuItem item) {
94 | if (item.getItemId() == R.id.menu_download) {
95 | saveCroppedImage();
96 | } else if (item.getItemId() == android.R.id.home) {
97 | onBackPressed();
98 | }
99 | return super.onOptionsItemSelected(item);
100 | }
101 |
102 |
103 | /**
104 | * Callback received when a permissions request has been completed.
105 | */
106 | @Override
107 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
108 | switch (requestCode) {
109 | case REQUEST_STORAGE_WRITE_ACCESS_PERMISSION:
110 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
111 | saveCroppedImage();
112 | }
113 | break;
114 | default:
115 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
116 | }
117 | }
118 |
119 | private void saveCroppedImage() {
120 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
121 | != PackageManager.PERMISSION_GRANTED) {
122 | requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
123 | getString(R.string.permission_write_storage_rationale),
124 | REQUEST_STORAGE_WRITE_ACCESS_PERMISSION);
125 | } else {
126 | Uri imageUri = getIntent().getData();
127 | if (imageUri != null && imageUri.getScheme().equals("file")) {
128 | try {
129 | copyFileToDownloads(getIntent().getData());
130 | } catch (Exception e) {
131 | Toast.makeText(ResultActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
132 | Log.e(TAG, imageUri.toString(), e);
133 | }
134 | } else {
135 | Toast.makeText(ResultActivity.this, getString(R.string.toast_unexpected_error), Toast.LENGTH_SHORT).show();
136 | }
137 | }
138 | }
139 |
140 | private void copyFileToDownloads(Uri croppedFileUri) throws Exception {
141 | String downloadsDirectoryPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
142 | String filename = String.format("%d_%s", Calendar.getInstance().getTimeInMillis(), croppedFileUri.getLastPathSegment());
143 |
144 | File saveFile = new File(downloadsDirectoryPath, filename);
145 |
146 | FileInputStream inStream = new FileInputStream(new File(croppedFileUri.getPath()));
147 | FileOutputStream outStream = new FileOutputStream(saveFile);
148 | FileChannel inChannel = inStream.getChannel();
149 | FileChannel outChannel = outStream.getChannel();
150 | inChannel.transferTo(0, inChannel.size(), outChannel);
151 | inStream.close();
152 | outStream.close();
153 |
154 | showNotification(saveFile);
155 | Toast.makeText(this, R.string.notification_image_saved, Toast.LENGTH_SHORT).show();
156 | finish();
157 | }
158 |
159 | private void showNotification(@NonNull File file) {
160 | Intent intent = new Intent(Intent.ACTION_VIEW);
161 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
162 | Uri fileUri = FileProvider.getUriForFile(
163 | this,
164 | getString(R.string.file_provider_authorities),
165 | file);
166 |
167 | intent.setDataAndType(fileUri, "image/*");
168 |
169 | List resInfoList = getPackageManager().queryIntentActivities(
170 | intent,
171 | PackageManager.MATCH_DEFAULT_ONLY);
172 | for (ResolveInfo info : resInfoList) {
173 | grantUriPermission(
174 | info.activityInfo.packageName,
175 | fileUri, FLAG_GRANT_WRITE_URI_PERMISSION | FLAG_GRANT_READ_URI_PERMISSION);
176 | }
177 |
178 | NotificationCompat.Builder notificationBuilder;
179 | NotificationManager notificationManager = (NotificationManager) this
180 | .getSystemService(Context.NOTIFICATION_SERVICE);
181 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
182 | if (notificationManager != null) {
183 | notificationManager.createNotificationChannel(createChannel());
184 | }
185 | notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID);
186 | } else {
187 | notificationBuilder = new NotificationCompat.Builder(this);
188 | }
189 |
190 | notificationBuilder
191 | .setContentTitle(getString(R.string.app_name))
192 | .setContentText(getString(R.string.notification_image_saved_click_to_preview))
193 | .setTicker(getString(R.string.notification_image_saved))
194 | .setSmallIcon(R.drawable.ic_done)
195 | .setOngoing(false)
196 | .setContentIntent(PendingIntent.getActivity(this, 0, intent, 0))
197 | .setAutoCancel(true);
198 | if (notificationManager != null) {
199 | notificationManager.notify(DOWNLOAD_NOTIFICATION_ID_DONE, notificationBuilder.build());
200 | }
201 | }
202 |
203 | @TargetApi(Build.VERSION_CODES.O)
204 | public NotificationChannel createChannel() {
205 | int importance = NotificationManager.IMPORTANCE_LOW;
206 | NotificationChannel channel = new NotificationChannel(CHANNEL_ID, getString(R.string.channel_name), importance);
207 | channel.setDescription(getString(R.string.channel_description));
208 | channel.enableLights(true);
209 | channel.setLightColor(Color.YELLOW);
210 | return channel;
211 | }
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/yalantis/ucrop/sample/SampleApp.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.sample;
2 |
3 | import android.app.Application;
4 |
5 | import com.yalantis.ucrop.UCropHttpClientStore;
6 |
7 | import java.util.Collections;
8 |
9 | import okhttp3.ConnectionSpec;
10 | import okhttp3.OkHttpClient;
11 |
12 | public class SampleApp extends Application {
13 |
14 | @Override
15 | public void onCreate() {
16 | super.onCreate();
17 | setUcropHttpClient();
18 | }
19 |
20 | private void setUcropHttpClient() {
21 | ConnectionSpec cs = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
22 | .allEnabledCipherSuites()
23 | .allEnabledTlsVersions()
24 | .build();
25 |
26 | OkHttpClient client = new OkHttpClient.Builder()
27 | .connectionSpecs(Collections.singletonList(cs))
28 | .build();
29 |
30 | UCropHttpClientStore.INSTANCE.setClient(client);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/bg_rounded_rectangle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_done.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/ic_file_download.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_result.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
23 |
24 |
25 |
33 |
34 |
37 |
38 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/include_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
27 |
28 |
37 |
38 |
47 |
48 |
57 |
58 |
63 |
64 |
68 |
69 |
75 |
76 |
82 |
83 |
90 |
91 |
97 |
98 |
104 |
105 |
109 |
110 |
118 |
119 |
124 |
125 |
133 |
134 |
135 |
136 |
137 |
138 |
143 |
144 |
150 |
151 |
157 |
158 |
162 |
163 |
172 |
173 |
178 |
179 |
188 |
189 |
190 |
191 |
196 |
197 |
201 |
202 |
208 |
209 |
217 |
218 |
225 |
226 |
233 |
234 |
239 |
240 |
241 |
242 |
247 |
248 |
254 |
255 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/menu_result.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FF6E40
4 | #CC5833
5 | #FF6E40
6 |
7 | #03A9F4
8 |
9 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 | 16dp
4 |
5 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | uCrop
4 |
5 | sample
6 | Aspect ratio
7 | Dynamic
8 | Image source
9 | Square
10 | Max cropped image size
11 | Compression settings
12 | UI
13 | Width
14 | Height
15 | Resize image to max size
16 | Hide bottom UI controls
17 | Freestyle crop
18 | Select Picture
19 | Cancel
20 | OK
21 |
22 | Image saved
23 | Image saved. Click to preview.
24 |
25 | Download
26 |
27 |
28 | Crop random image
29 |
30 | Cropped image
31 |
32 | Crop Result (%1$dx%2$d)
33 | Quality: %d
34 | %1$dx%2$d px
35 | Max cropped image size cannot be less then %d
36 | Too big resolution
37 |
38 | Permission needed
39 | Storage read permission is needed to pick files.
40 | Storage write permission is needed to save the image.
41 |
42 | Cannot retrieve selected image
43 | Cannot retrieve cropped image
44 | Unexpected error
45 |
46 | com.yalantis.ucrop.provider
47 | ucrop_chanel
48 | ucrop result image
49 |
50 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
--------------------------------------------------------------------------------
/sample/src/main/res/xml/file_provider_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':ucrop', ':sample'
--------------------------------------------------------------------------------
/ucrop/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/ucrop/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply from: '../mavenpush.gradle'
3 |
4 | android {
5 | compileSdk 33
6 | defaultConfig {
7 | minSdkVersion 14
8 | targetSdkVersion 33
9 | versionCode 27
10 | versionName "2.2.9-native"
11 |
12 | vectorDrawables.useSupportLibrary = true
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | compileOptions {
21 | sourceCompatibility JavaVersion.VERSION_1_8
22 | targetCompatibility JavaVersion.VERSION_1_8
23 | }
24 | lintOptions {
25 | abortOnError false
26 | }
27 |
28 | resourcePrefix 'ucrop_'
29 |
30 | sourceSets.main {
31 | jni.srcDirs = []
32 | }
33 |
34 | }
35 |
36 | dependencies {
37 | implementation "androidx.appcompat:appcompat:${androidx_appcompat_version}"
38 | implementation "androidx.exifinterface:exifinterface:${androidx_exifinterface_version}"
39 | implementation "androidx.transition:transition:${androidx_transition_version}"
40 | // OkHttp3 versions above 3.12.x don't support pre-Lollipop Android versions (API 21)
41 | implementation "com.squareup.okhttp3:okhttp:3.12.13"
42 | }
43 |
--------------------------------------------------------------------------------
/ucrop/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=uCrop
2 | POM_ARTIFACT_ID=ucrop
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/ucrop/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/oleksii/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/ucrop/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/UCropFragmentCallback.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop;
2 |
3 | public interface UCropFragmentCallback {
4 |
5 | /**
6 | * Return loader status
7 | * @param showLoader
8 | */
9 | void loadingProgress(boolean showLoader);
10 |
11 | /**
12 | * Return cropping result or error
13 | * @param result
14 | */
15 | void onCropFinish(UCropFragment.UCropResult result);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/UCropHttpClientStore.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop;
2 |
3 | import androidx.annotation.NonNull;
4 |
5 | import okhttp3.OkHttpClient;
6 |
7 | public class UCropHttpClientStore {
8 |
9 | private UCropHttpClientStore() {}
10 |
11 | public final static UCropHttpClientStore INSTANCE = new UCropHttpClientStore();
12 |
13 | private OkHttpClient client;
14 |
15 | @NonNull
16 | public OkHttpClient getClient() {
17 | if (client == null) {
18 | client = new OkHttpClient();
19 | }
20 | return client;
21 | }
22 |
23 | public void setClient(@NonNull OkHttpClient client) {
24 | this.client = client;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapCropCallback.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.callback;
2 |
3 | import android.net.Uri;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | public interface BitmapCropCallback {
8 |
9 | void onBitmapCropped(@NonNull Uri resultUri, int offsetX, int offsetY, int imageWidth, int imageHeight);
10 |
11 | void onCropFailure(@NonNull Throwable t);
12 |
13 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/callback/BitmapLoadCallback.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.callback;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | import com.yalantis.ucrop.model.ExifInfo;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 |
10 | public interface BitmapLoadCallback {
11 |
12 | void onBitmapLoaded(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo, @NonNull String imageInputPath, @Nullable String imageOutputPath);
13 |
14 | void onFailure(@NonNull Exception bitmapWorkerException);
15 |
16 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/callback/CropBoundsChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.callback;
2 |
3 | /**
4 | * Interface for crop bound change notifying.
5 | */
6 | public interface CropBoundsChangeListener {
7 |
8 | void onCropAspectRatioChanged(float cropRatio);
9 |
10 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/callback/OverlayViewChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.callback;
2 |
3 | import android.graphics.RectF;
4 |
5 | /**
6 | * Created by Oleksii Shliama.
7 | */
8 | public interface OverlayViewChangeListener {
9 |
10 | void onCropRectUpdated(RectF cropRect);
11 |
12 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/model/AspectRatio.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | import androidx.annotation.Nullable;
7 |
8 | /**
9 | * Created by Oleksii Shliama [https://github.com/shliama] on 6/24/16.
10 | */
11 | public class AspectRatio implements Parcelable {
12 |
13 | @Nullable
14 | private final String mAspectRatioTitle;
15 | private final float mAspectRatioX;
16 | private final float mAspectRatioY;
17 |
18 | public AspectRatio(@Nullable String aspectRatioTitle, float aspectRatioX, float aspectRatioY) {
19 | mAspectRatioTitle = aspectRatioTitle;
20 | mAspectRatioX = aspectRatioX;
21 | mAspectRatioY = aspectRatioY;
22 | }
23 |
24 | protected AspectRatio(Parcel in) {
25 | mAspectRatioTitle = in.readString();
26 | mAspectRatioX = in.readFloat();
27 | mAspectRatioY = in.readFloat();
28 | }
29 |
30 | @Override
31 | public void writeToParcel(Parcel dest, int flags) {
32 | dest.writeString(mAspectRatioTitle);
33 | dest.writeFloat(mAspectRatioX);
34 | dest.writeFloat(mAspectRatioY);
35 | }
36 |
37 | @Override
38 | public int describeContents() {
39 | return 0;
40 | }
41 |
42 | public static final Creator CREATOR = new Creator() {
43 | @Override
44 | public AspectRatio createFromParcel(Parcel in) {
45 | return new AspectRatio(in);
46 | }
47 |
48 | @Override
49 | public AspectRatio[] newArray(int size) {
50 | return new AspectRatio[size];
51 | }
52 | };
53 |
54 | @Nullable
55 | public String getAspectRatioTitle() {
56 | return mAspectRatioTitle;
57 | }
58 |
59 | public float getAspectRatioX() {
60 | return mAspectRatioX;
61 | }
62 |
63 | public float getAspectRatioY() {
64 | return mAspectRatioY;
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/model/CropParameters.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.model;
2 |
3 | import android.graphics.Bitmap;
4 |
5 | /**
6 | * Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.
7 | */
8 | public class CropParameters {
9 |
10 | private int mMaxResultImageSizeX, mMaxResultImageSizeY;
11 |
12 | private Bitmap.CompressFormat mCompressFormat;
13 | private int mCompressQuality;
14 | private String mImageInputPath, mImageOutputPath;
15 | private ExifInfo mExifInfo;
16 |
17 |
18 | public CropParameters(int maxResultImageSizeX, int maxResultImageSizeY,
19 | Bitmap.CompressFormat compressFormat, int compressQuality,
20 | String imageInputPath, String imageOutputPath, ExifInfo exifInfo) {
21 | mMaxResultImageSizeX = maxResultImageSizeX;
22 | mMaxResultImageSizeY = maxResultImageSizeY;
23 | mCompressFormat = compressFormat;
24 | mCompressQuality = compressQuality;
25 | mImageInputPath = imageInputPath;
26 | mImageOutputPath = imageOutputPath;
27 | mExifInfo = exifInfo;
28 | }
29 |
30 | public int getMaxResultImageSizeX() {
31 | return mMaxResultImageSizeX;
32 | }
33 |
34 | public int getMaxResultImageSizeY() {
35 | return mMaxResultImageSizeY;
36 | }
37 |
38 | public Bitmap.CompressFormat getCompressFormat() {
39 | return mCompressFormat;
40 | }
41 |
42 | public int getCompressQuality() {
43 | return mCompressQuality;
44 | }
45 |
46 | public String getImageInputPath() {
47 | return mImageInputPath;
48 | }
49 |
50 | public String getImageOutputPath() {
51 | return mImageOutputPath;
52 | }
53 |
54 | public ExifInfo getExifInfo() {
55 | return mExifInfo;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/model/ExifInfo.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.model;
2 |
3 | /**
4 | * Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.
5 | */
6 | public class ExifInfo {
7 |
8 | private int mExifOrientation;
9 | private int mExifDegrees;
10 | private int mExifTranslation;
11 |
12 | public ExifInfo(int exifOrientation, int exifDegrees, int exifTranslation) {
13 | mExifOrientation = exifOrientation;
14 | mExifDegrees = exifDegrees;
15 | mExifTranslation = exifTranslation;
16 | }
17 |
18 | public int getExifOrientation() {
19 | return mExifOrientation;
20 | }
21 |
22 | public int getExifDegrees() {
23 | return mExifDegrees;
24 | }
25 |
26 | public int getExifTranslation() {
27 | return mExifTranslation;
28 | }
29 |
30 | public void setExifOrientation(int exifOrientation) {
31 | mExifOrientation = exifOrientation;
32 | }
33 |
34 | public void setExifDegrees(int exifDegrees) {
35 | mExifDegrees = exifDegrees;
36 | }
37 |
38 | public void setExifTranslation(int exifTranslation) {
39 | mExifTranslation = exifTranslation;
40 | }
41 |
42 | @Override
43 | public boolean equals(Object o) {
44 | if (this == o) return true;
45 | if (o == null || getClass() != o.getClass()) return false;
46 |
47 | ExifInfo exifInfo = (ExifInfo) o;
48 |
49 | if (mExifOrientation != exifInfo.mExifOrientation) return false;
50 | if (mExifDegrees != exifInfo.mExifDegrees) return false;
51 | return mExifTranslation == exifInfo.mExifTranslation;
52 |
53 | }
54 |
55 | @Override
56 | public int hashCode() {
57 | int result = mExifOrientation;
58 | result = 31 * result + mExifDegrees;
59 | result = 31 * result + mExifTranslation;
60 | return result;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/model/ImageState.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.model;
2 |
3 | import android.graphics.RectF;
4 |
5 | /**
6 | * Created by Oleksii Shliama [https://github.com/shliama] on 6/21/16.
7 | */
8 | public class ImageState {
9 |
10 | private RectF mCropRect;
11 | private RectF mCurrentImageRect;
12 |
13 | private float mCurrentScale, mCurrentAngle;
14 |
15 | public ImageState(RectF cropRect, RectF currentImageRect, float currentScale, float currentAngle) {
16 | mCropRect = cropRect;
17 | mCurrentImageRect = currentImageRect;
18 | mCurrentScale = currentScale;
19 | mCurrentAngle = currentAngle;
20 | }
21 |
22 | public RectF getCropRect() {
23 | return mCropRect;
24 | }
25 |
26 | public RectF getCurrentImageRect() {
27 | return mCurrentImageRect;
28 | }
29 |
30 | public float getCurrentScale() {
31 | return mCurrentScale;
32 | }
33 |
34 | public float getCurrentAngle() {
35 | return mCurrentAngle;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapCropTask.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.task;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 | import android.graphics.RectF;
6 | import android.net.Uri;
7 | import android.os.AsyncTask;
8 | import android.util.Log;
9 |
10 | import com.yalantis.ucrop.callback.BitmapCropCallback;
11 | import com.yalantis.ucrop.model.CropParameters;
12 | import com.yalantis.ucrop.model.ExifInfo;
13 | import com.yalantis.ucrop.model.ImageState;
14 | import com.yalantis.ucrop.util.FileUtils;
15 | import com.yalantis.ucrop.util.ImageHeaderParser;
16 |
17 | import java.io.File;
18 | import java.io.IOException;
19 |
20 | import androidx.annotation.NonNull;
21 | import androidx.annotation.Nullable;
22 | import androidx.exifinterface.media.ExifInterface;
23 |
24 | /**
25 | * Crops part of image that fills the crop bounds.
26 | *
27 | * First image is downscaled if max size was set and if resulting image is larger that max size.
28 | * Then image is rotated accordingly.
29 | * Finally new Bitmap object is created and saved to file.
30 | */
31 | public class BitmapCropTask extends AsyncTask {
32 |
33 | private static final String TAG = "BitmapCropTask";
34 |
35 | static {
36 | System.loadLibrary("ucrop");
37 | }
38 |
39 | private Bitmap mViewBitmap;
40 |
41 | private final RectF mCropRect;
42 | private final RectF mCurrentImageRect;
43 |
44 | private float mCurrentScale, mCurrentAngle;
45 | private final int mMaxResultImageSizeX, mMaxResultImageSizeY;
46 |
47 | private final Bitmap.CompressFormat mCompressFormat;
48 | private final int mCompressQuality;
49 | private final String mImageInputPath, mImageOutputPath;
50 | private final ExifInfo mExifInfo;
51 | private final BitmapCropCallback mCropCallback;
52 |
53 | private int mCroppedImageWidth, mCroppedImageHeight;
54 | private int cropOffsetX, cropOffsetY;
55 |
56 | public BitmapCropTask(@Nullable Bitmap viewBitmap, @NonNull ImageState imageState, @NonNull CropParameters cropParameters,
57 | @Nullable BitmapCropCallback cropCallback) {
58 |
59 | mViewBitmap = viewBitmap;
60 | mCropRect = imageState.getCropRect();
61 | mCurrentImageRect = imageState.getCurrentImageRect();
62 |
63 | mCurrentScale = imageState.getCurrentScale();
64 | mCurrentAngle = imageState.getCurrentAngle();
65 | mMaxResultImageSizeX = cropParameters.getMaxResultImageSizeX();
66 | mMaxResultImageSizeY = cropParameters.getMaxResultImageSizeY();
67 |
68 | mCompressFormat = cropParameters.getCompressFormat();
69 | mCompressQuality = cropParameters.getCompressQuality();
70 |
71 | mImageInputPath = cropParameters.getImageInputPath();
72 | mImageOutputPath = cropParameters.getImageOutputPath();
73 | mExifInfo = cropParameters.getExifInfo();
74 |
75 | mCropCallback = cropCallback;
76 | }
77 |
78 | @Override
79 | @Nullable
80 | protected Throwable doInBackground(Void... params) {
81 | if (mViewBitmap == null) {
82 | return new NullPointerException("ViewBitmap is null");
83 | } else if (mViewBitmap.isRecycled()) {
84 | return new NullPointerException("ViewBitmap is recycled");
85 | } else if (mCurrentImageRect.isEmpty()) {
86 | return new NullPointerException("CurrentImageRect is empty");
87 | }
88 |
89 | float resizeScale = resize();
90 |
91 | try {
92 | crop(resizeScale);
93 | mViewBitmap = null;
94 | } catch (Throwable throwable) {
95 | return throwable;
96 | }
97 |
98 | return null;
99 | }
100 |
101 | private float resize() {
102 | final BitmapFactory.Options options = new BitmapFactory.Options();
103 | options.inJustDecodeBounds = true;
104 | BitmapFactory.decodeFile(mImageInputPath, options);
105 |
106 | boolean swapSides = mExifInfo.getExifDegrees() == 90 || mExifInfo.getExifDegrees() == 270;
107 | float scaleX = (swapSides ? options.outHeight : options.outWidth) / (float) mViewBitmap.getWidth();
108 | float scaleY = (swapSides ? options.outWidth : options.outHeight) / (float) mViewBitmap.getHeight();
109 |
110 | float resizeScale = Math.min(scaleX, scaleY);
111 |
112 | mCurrentScale /= resizeScale;
113 |
114 | resizeScale = 1;
115 | if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) {
116 | float cropWidth = mCropRect.width() / mCurrentScale;
117 | float cropHeight = mCropRect.height() / mCurrentScale;
118 |
119 | if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) {
120 |
121 | scaleX = mMaxResultImageSizeX / cropWidth;
122 | scaleY = mMaxResultImageSizeY / cropHeight;
123 | resizeScale = Math.min(scaleX, scaleY);
124 |
125 | mCurrentScale /= resizeScale;
126 | }
127 | }
128 | return resizeScale;
129 | }
130 |
131 | private boolean crop(float resizeScale) throws IOException {
132 | ExifInterface originalExif = new ExifInterface(mImageInputPath);
133 |
134 | cropOffsetX = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale);
135 | cropOffsetY = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale);
136 | mCroppedImageWidth = Math.round(mCropRect.width() / mCurrentScale);
137 | mCroppedImageHeight = Math.round(mCropRect.height() / mCurrentScale);
138 |
139 | boolean shouldCrop = shouldCrop(mCroppedImageWidth, mCroppedImageHeight);
140 | Log.i(TAG, "Should crop: " + shouldCrop);
141 |
142 | if (shouldCrop) {
143 | boolean cropped = cropCImg(mImageInputPath, mImageOutputPath,
144 | cropOffsetX, cropOffsetY, mCroppedImageWidth, mCroppedImageHeight,
145 | mCurrentAngle, resizeScale, mCompressFormat.ordinal(), mCompressQuality,
146 | mExifInfo.getExifDegrees(), mExifInfo.getExifTranslation());
147 | if (cropped && mCompressFormat.equals(Bitmap.CompressFormat.JPEG)) {
148 | ImageHeaderParser.copyExif(originalExif, mCroppedImageWidth, mCroppedImageHeight, mImageOutputPath);
149 | }
150 | return cropped;
151 | } else {
152 | FileUtils.copyFile(mImageInputPath, mImageOutputPath);
153 | return false;
154 | }
155 | }
156 |
157 | /**
158 | * Check whether an image should be cropped at all or just file can be copied to the destination path.
159 | * For each 1000 pixels there is one pixel of error due to matrix calculations etc.
160 | *
161 | * @param width - crop area width
162 | * @param height - crop area height
163 | * @return - true if image must be cropped, false - if original image fits requirements
164 | */
165 | private boolean shouldCrop(int width, int height) {
166 | int pixelError = 1;
167 | pixelError += Math.round(Math.max(width, height) / 1000f);
168 | return (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0)
169 | || Math.abs(mCropRect.left - mCurrentImageRect.left) > pixelError
170 | || Math.abs(mCropRect.top - mCurrentImageRect.top) > pixelError
171 | || Math.abs(mCropRect.bottom - mCurrentImageRect.bottom) > pixelError
172 | || Math.abs(mCropRect.right - mCurrentImageRect.right) > pixelError
173 | || mCurrentAngle != 0;
174 | }
175 |
176 | @SuppressWarnings("JniMissingFunction")
177 | native public static boolean
178 | cropCImg(String inputPath, String outputPath,
179 | int left, int top, int width, int height,
180 | float angle, float resizeScale,
181 | int format, int quality,
182 | int exifDegrees, int exifTranslation) throws IOException, OutOfMemoryError;
183 |
184 | @Override
185 | protected void onPostExecute(@Nullable Throwable t) {
186 | if (mCropCallback != null) {
187 | if (t == null) {
188 | Uri uri = Uri.fromFile(new File(mImageOutputPath));
189 | mCropCallback.onBitmapCropped(uri, cropOffsetX, cropOffsetY, mCroppedImageWidth, mCroppedImageHeight);
190 | } else {
191 | mCropCallback.onCropFailure(t);
192 | }
193 | }
194 | }
195 |
196 | }
197 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/task/BitmapLoadTask.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.task;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Matrix;
7 | import android.net.Uri;
8 | import android.os.AsyncTask;
9 | import android.util.Log;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 |
14 | import com.yalantis.ucrop.UCropHttpClientStore;
15 | import com.yalantis.ucrop.callback.BitmapLoadCallback;
16 | import com.yalantis.ucrop.model.ExifInfo;
17 | import com.yalantis.ucrop.util.BitmapLoadUtils;
18 |
19 | import java.io.File;
20 | import java.io.FileOutputStream;
21 | import java.io.IOException;
22 | import java.io.InputStream;
23 | import java.io.OutputStream;
24 |
25 | import okhttp3.OkHttpClient;
26 | import okhttp3.Request;
27 | import okhttp3.Response;
28 | import okio.BufferedSource;
29 | import okio.Okio;
30 | import okio.Sink;
31 |
32 | /**
33 | * Creates and returns a Bitmap for a given Uri(String url).
34 | * inSampleSize is calculated based on requiredWidth property. However can be adjusted if OOM occurs.
35 | * If any EXIF config is found - bitmap is transformed properly.
36 | */
37 | public class BitmapLoadTask extends AsyncTask {
38 |
39 | private static final String TAG = "BitmapWorkerTask";
40 |
41 | private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024; // 100 MB
42 |
43 | private final Context mContext;
44 | private Uri mInputUri;
45 | private Uri mOutputUri;
46 | private final int mRequiredWidth;
47 | private final int mRequiredHeight;
48 |
49 | private final BitmapLoadCallback mBitmapLoadCallback;
50 |
51 | public static class BitmapWorkerResult {
52 |
53 | Bitmap mBitmapResult;
54 | ExifInfo mExifInfo;
55 | Exception mBitmapWorkerException;
56 |
57 | public BitmapWorkerResult(@NonNull Bitmap bitmapResult, @NonNull ExifInfo exifInfo) {
58 | mBitmapResult = bitmapResult;
59 | mExifInfo = exifInfo;
60 | }
61 |
62 | public BitmapWorkerResult(@NonNull Exception bitmapWorkerException) {
63 | mBitmapWorkerException = bitmapWorkerException;
64 | }
65 |
66 | }
67 |
68 | public BitmapLoadTask(@NonNull Context context,
69 | @NonNull Uri inputUri, @Nullable Uri outputUri,
70 | int requiredWidth, int requiredHeight,
71 | BitmapLoadCallback loadCallback) {
72 | mContext = context;
73 | mInputUri = inputUri;
74 | mOutputUri = outputUri;
75 | mRequiredWidth = requiredWidth;
76 | mRequiredHeight = requiredHeight;
77 | mBitmapLoadCallback = loadCallback;
78 | }
79 |
80 | @Override
81 | @NonNull
82 | protected BitmapWorkerResult doInBackground(Void... params) {
83 | if (mInputUri == null) {
84 | return new BitmapWorkerResult(new NullPointerException("Input Uri cannot be null"));
85 | }
86 |
87 | try {
88 | processInputUri();
89 | } catch (NullPointerException | IOException e) {
90 | return new BitmapWorkerResult(e);
91 | }
92 |
93 | final BitmapFactory.Options options = new BitmapFactory.Options();
94 | options.inJustDecodeBounds = true;
95 | options.inSampleSize = BitmapLoadUtils.calculateInSampleSize(options, mRequiredWidth, mRequiredHeight);
96 | options.inJustDecodeBounds = false;
97 |
98 | Bitmap decodeSampledBitmap = null;
99 |
100 | boolean decodeAttemptSuccess = false;
101 | while (!decodeAttemptSuccess) {
102 | try {
103 | InputStream stream = mContext.getContentResolver().openInputStream(mInputUri);
104 | try {
105 | decodeSampledBitmap = BitmapFactory.decodeStream(stream, null, options);
106 | if (options.outWidth == -1 || options.outHeight == -1) {
107 | return new BitmapWorkerResult(new IllegalArgumentException("Bounds for bitmap could not be retrieved from the Uri: [" + mInputUri + "]"));
108 | }
109 | } finally {
110 | BitmapLoadUtils.close(stream);
111 | }
112 | if (checkSize(decodeSampledBitmap, options)) continue;
113 | decodeAttemptSuccess = true;
114 | } catch (OutOfMemoryError error) {
115 | Log.e(TAG, "doInBackground: BitmapFactory.decodeFileDescriptor: ", error);
116 | options.inSampleSize *= 2;
117 | } catch (IOException e) {
118 | Log.e(TAG, "doInBackground: ImageDecoder.createSource: ", e);
119 | return new BitmapWorkerResult(new IllegalArgumentException("Bitmap could not be decoded from the Uri: [" + mInputUri + "]", e));
120 | }
121 | }
122 |
123 | if (decodeSampledBitmap == null) {
124 | return new BitmapWorkerResult(new IllegalArgumentException("Bitmap could not be decoded from the Uri: [" + mInputUri + "]"));
125 | }
126 |
127 | int exifOrientation = BitmapLoadUtils.getExifOrientation(mContext, mInputUri);
128 | int exifDegrees = BitmapLoadUtils.exifToDegrees(exifOrientation);
129 | int exifTranslation = BitmapLoadUtils.exifToTranslation(exifOrientation);
130 |
131 | ExifInfo exifInfo = new ExifInfo(exifOrientation, exifDegrees, exifTranslation);
132 |
133 | Matrix matrix = new Matrix();
134 | if (exifDegrees != 0) {
135 | matrix.preRotate(exifDegrees);
136 | }
137 | if (exifTranslation != 1) {
138 | matrix.postScale(exifTranslation, 1);
139 | }
140 | if (!matrix.isIdentity()) {
141 | return new BitmapWorkerResult(BitmapLoadUtils.transformBitmap(decodeSampledBitmap, matrix), exifInfo);
142 | }
143 |
144 | return new BitmapWorkerResult(decodeSampledBitmap, exifInfo);
145 | }
146 |
147 | private void processInputUri() throws NullPointerException, IOException {
148 | Log.d(TAG, "Uri scheme: " + mInputUri.getScheme());
149 | if (isDownloadUri(mInputUri)) {
150 | try {
151 | downloadFile(mInputUri, mOutputUri);
152 | } catch (NullPointerException | IOException e) {
153 | Log.e(TAG, "Downloading failed", e);
154 | throw e;
155 | }
156 | } else if (isContentUri(mInputUri)) {
157 | try {
158 | copyFile(mInputUri, mOutputUri);
159 | } catch (NullPointerException | IOException e) {
160 | Log.e(TAG, "Copying failed", e);
161 | throw e;
162 | }
163 | } else if (!isFileUri(mInputUri)) {
164 | String inputUriScheme = mInputUri.getScheme();
165 | Log.e(TAG, "Invalid Uri scheme " + inputUriScheme);
166 | throw new IllegalArgumentException("Invalid Uri scheme" + inputUriScheme);
167 | }
168 | }
169 |
170 | private void copyFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException {
171 | Log.d(TAG, "copyFile");
172 |
173 | if (outputUri == null) {
174 | throw new NullPointerException("Output Uri is null - cannot copy image");
175 | }
176 |
177 | InputStream inputStream = null;
178 | OutputStream outputStream = null;
179 | try {
180 | inputStream = mContext.getContentResolver().openInputStream(inputUri);
181 | if (inputStream == null) {
182 | throw new NullPointerException("InputStream for given input Uri is null");
183 | }
184 |
185 | if (isContentUri(outputUri)) {
186 | outputStream = mContext.getContentResolver().openOutputStream(outputUri);
187 | } else {
188 | outputStream = new FileOutputStream(new File(outputUri.getPath()));
189 | }
190 |
191 | byte buffer[] = new byte[1024];
192 | int length;
193 | while ((length = inputStream.read(buffer)) > 0) {
194 | outputStream.write(buffer, 0, length);
195 | }
196 | } finally {
197 | BitmapLoadUtils.close(outputStream);
198 | BitmapLoadUtils.close(inputStream);
199 |
200 | // swap uris, because input image was copied to the output destination
201 | // (cropped image will override it later)
202 | mInputUri = mOutputUri;
203 | }
204 | }
205 |
206 | private void downloadFile(@NonNull Uri inputUri, @Nullable Uri outputUri) throws NullPointerException, IOException {
207 | Log.d(TAG, "downloadFile");
208 |
209 | if (outputUri == null) {
210 | throw new NullPointerException("Output Uri is null - cannot download image");
211 | }
212 |
213 | OkHttpClient client = UCropHttpClientStore.INSTANCE.getClient();
214 |
215 | BufferedSource source = null;
216 | Sink sink = null;
217 | Response response = null;
218 | try {
219 | Request request = new Request.Builder()
220 | .url(inputUri.toString())
221 | .build();
222 | response = client.newCall(request).execute();
223 | source = response.body().source();
224 |
225 | OutputStream outputStream;
226 |
227 | if (isContentUri(mOutputUri)) {
228 | outputStream = mContext.getContentResolver().openOutputStream(outputUri);
229 | } else {
230 | outputStream = new FileOutputStream(new File(outputUri.getPath()));
231 | }
232 |
233 | if (outputStream != null) {
234 | sink = Okio.sink(outputStream);
235 | source.readAll(sink);
236 | } else {
237 | throw new NullPointerException("OutputStream for given output Uri is null");
238 | }
239 | } finally {
240 | BitmapLoadUtils.close(source);
241 | BitmapLoadUtils.close(sink);
242 | if (response != null) {
243 | BitmapLoadUtils.close(response.body());
244 | }
245 | client.dispatcher().cancelAll();
246 |
247 | // swap uris, because input image was downloaded to the output destination
248 | // (cropped image will override it later)
249 | mInputUri = mOutputUri;
250 | }
251 | }
252 |
253 | @Override
254 | protected void onPostExecute(@NonNull BitmapWorkerResult result) {
255 | if (result.mBitmapWorkerException == null) {
256 | mBitmapLoadCallback.onBitmapLoaded(result.mBitmapResult, result.mExifInfo, mInputUri.getPath(), (mOutputUri == null) ? null : mOutputUri.getPath());
257 | } else {
258 | mBitmapLoadCallback.onFailure(result.mBitmapWorkerException);
259 | }
260 | }
261 |
262 | private boolean checkSize(Bitmap bitmap, BitmapFactory.Options options) {
263 | int bitmapSize = bitmap != null ? bitmap.getByteCount() : 0;
264 | if (bitmapSize > MAX_BITMAP_SIZE) {
265 | options.inSampleSize *= 2;
266 | return true;
267 | }
268 | return false;
269 | }
270 |
271 | private boolean isDownloadUri(Uri uri) {
272 | final String schema = uri.getScheme();
273 | return schema.equals("http") || schema.equals("https");
274 | }
275 |
276 | private boolean isContentUri(Uri uri) {
277 | final String schema = uri.getScheme();
278 | return schema.equals("content");
279 | }
280 |
281 | private boolean isFileUri(Uri uri) {
282 | final String schema = uri.getScheme();
283 | return schema.equals("file");
284 | }
285 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/BitmapLoadUtils.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.util;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import android.graphics.Canvas;
7 | import android.graphics.Matrix;
8 | import android.graphics.Point;
9 | import android.net.Uri;
10 | import android.os.AsyncTask;
11 | import android.util.Log;
12 | import android.view.Display;
13 | import android.view.WindowManager;
14 |
15 | import com.yalantis.ucrop.callback.BitmapLoadCallback;
16 | import com.yalantis.ucrop.task.BitmapLoadTask;
17 |
18 | import java.io.Closeable;
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.util.ArrayList;
22 | import java.util.List;
23 |
24 | import androidx.annotation.NonNull;
25 | import androidx.annotation.Nullable;
26 | import androidx.exifinterface.media.ExifInterface;
27 |
28 | /**
29 | * Created by Oleksii Shliama (https://github.com/shliama).
30 | */
31 | public class BitmapLoadUtils {
32 |
33 | private static final String TAG = "BitmapLoadUtils";
34 |
35 | public static void decodeBitmapInBackground(@NonNull Context context,
36 | @NonNull Uri uri, @Nullable Uri outputUri,
37 | int requiredWidth, int requiredHeight,
38 | BitmapLoadCallback loadCallback) {
39 |
40 | new BitmapLoadTask(context, uri, outputUri, requiredWidth, requiredHeight, loadCallback)
41 | .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
42 | }
43 |
44 | public static Bitmap transformBitmap(@NonNull Bitmap bitmap, @NonNull Matrix transformMatrix) {
45 | try {
46 | Bitmap converted = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), transformMatrix, true);
47 | if (!bitmap.sameAs(converted)) {
48 | bitmap = converted;
49 | }
50 | } catch (OutOfMemoryError error) {
51 | Log.e(TAG, "transformBitmap: ", error);
52 | }
53 | return bitmap;
54 | }
55 |
56 | public static int calculateInSampleSize(@NonNull BitmapFactory.Options options, int reqWidth, int reqHeight) {
57 | // Raw height and width of image
58 | final int height = options.outHeight;
59 | final int width = options.outWidth;
60 | int inSampleSize = 1;
61 |
62 | if (height > reqHeight || width > reqWidth) {
63 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both
64 | // height and width lower or equal to the requested height and width.
65 | while ((height / inSampleSize) > reqHeight || (width / inSampleSize) > reqWidth) {
66 | inSampleSize *= 2;
67 | }
68 | }
69 | return inSampleSize;
70 | }
71 |
72 | public static int getExifOrientation(@NonNull Context context, @NonNull Uri imageUri) {
73 | int orientation = ExifInterface.ORIENTATION_UNDEFINED;
74 | try {
75 | InputStream stream = context.getContentResolver().openInputStream(imageUri);
76 | if (stream == null) {
77 | return orientation;
78 | }
79 | orientation = new ImageHeaderParser(stream).getOrientation();
80 | close(stream);
81 | } catch (IOException e) {
82 | Log.e(TAG, "getExifOrientation: " + imageUri.toString(), e);
83 | }
84 | return orientation;
85 | }
86 |
87 | public static int exifToDegrees(int exifOrientation) {
88 | int rotation;
89 | switch (exifOrientation) {
90 | case ExifInterface.ORIENTATION_ROTATE_90:
91 | case ExifInterface.ORIENTATION_TRANSPOSE:
92 | rotation = 90;
93 | break;
94 | case ExifInterface.ORIENTATION_ROTATE_180:
95 | case ExifInterface.ORIENTATION_FLIP_VERTICAL:
96 | rotation = 180;
97 | break;
98 | case ExifInterface.ORIENTATION_ROTATE_270:
99 | case ExifInterface.ORIENTATION_TRANSVERSE:
100 | rotation = 270;
101 | break;
102 | default:
103 | rotation = 0;
104 | }
105 | return rotation;
106 | }
107 |
108 | public static int exifToTranslation(int exifOrientation) {
109 | int translation;
110 | switch (exifOrientation) {
111 | case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
112 | case ExifInterface.ORIENTATION_FLIP_VERTICAL:
113 | case ExifInterface.ORIENTATION_TRANSPOSE:
114 | case ExifInterface.ORIENTATION_TRANSVERSE:
115 | translation = -1;
116 | break;
117 | default:
118 | translation = 1;
119 | }
120 | return translation;
121 | }
122 |
123 | /**
124 | * This method calculates maximum size of both width and height of bitmap.
125 | * It is twice the device screen diagonal for default implementation (extra quality to zoom image).
126 | * Size cannot exceed max texture size.
127 | *
128 | * @return - max bitmap size in pixels.
129 | */
130 | @SuppressWarnings({"SuspiciousNameCombination", "deprecation"})
131 | public static int calculateMaxBitmapSize(@NonNull Context context) {
132 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
133 | Display display;
134 | int width, height;
135 | Point size = new Point();
136 |
137 | if (wm != null) {
138 | display = wm.getDefaultDisplay();
139 | display.getSize(size);
140 | }
141 |
142 | width = size.x;
143 | height = size.y;
144 |
145 | // Twice the device screen diagonal as default
146 | int maxBitmapSize = (int) Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
147 |
148 | // Check for max texture size via Canvas
149 | Canvas canvas = new Canvas();
150 | final int maxCanvasSize = Math.min(canvas.getMaximumBitmapWidth(), canvas.getMaximumBitmapHeight());
151 | if (maxCanvasSize > 0) {
152 | maxBitmapSize = Math.min(maxBitmapSize, maxCanvasSize);
153 | }
154 |
155 | // Check for max texture size via GL
156 | final int maxTextureSize = EglUtils.getMaxTextureSize();
157 | if (maxTextureSize > 0) {
158 | maxBitmapSize = Math.min(maxBitmapSize, maxTextureSize);
159 | }
160 |
161 | Log.d(TAG, "maxBitmapSize: " + maxBitmapSize);
162 | return maxBitmapSize;
163 | }
164 |
165 | @SuppressWarnings("ConstantConditions")
166 | public static void close(@Nullable Closeable c) {
167 | if (c != null && c instanceof Closeable) { // java.lang.IncompatibleClassChangeError: interface not implemented
168 | try {
169 | c.close();
170 | } catch (IOException e) {
171 | // silence
172 | }
173 | }
174 | }
175 |
176 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/CubicEasing.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.util;
2 |
3 | public final class CubicEasing {
4 |
5 | public static float easeOut(float time, float start, float end, float duration) {
6 | return end * ((time = time / duration - 1.0f) * time * time + 1.0f) + start;
7 | }
8 |
9 | public static float easeIn(float time, float start, float end, float duration) {
10 | return end * (time /= duration) * time * time + start;
11 | }
12 |
13 | public static float easeInOut(float time, float start, float end, float duration) {
14 | return (time /= duration / 2.0f) < 1.0f ? end / 2.0f * time * time * time + start : end / 2.0f * ((time -= 2.0f) * time * time + 2.0f) + start;
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/EglUtils.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.util;
2 |
3 | import android.annotation.TargetApi;
4 | import android.opengl.EGL14;
5 | import android.opengl.EGLConfig;
6 | import android.opengl.EGLContext;
7 | import android.opengl.EGLDisplay;
8 | import android.opengl.EGLSurface;
9 | import android.opengl.GLES10;
10 | import android.opengl.GLES20;
11 | import android.os.Build;
12 | import android.util.Log;
13 |
14 | import javax.microedition.khronos.egl.EGL10;
15 |
16 | /**
17 | * Created by Oleksii Shliama [https://github.com/shliama] on 9/8/16.
18 | */
19 | public class EglUtils {
20 |
21 | private static final String TAG = "EglUtils";
22 |
23 | private EglUtils() {
24 |
25 | }
26 |
27 | public static int getMaxTextureSize() {
28 | try {
29 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
30 | return getMaxTextureEgl14();
31 | } else {
32 | return getMaxTextureEgl10();
33 | }
34 | } catch (Exception e) {
35 | Log.d(TAG, "getMaxTextureSize: ", e);
36 | return 0;
37 | }
38 | }
39 |
40 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
41 | private static int getMaxTextureEgl14() {
42 | EGLDisplay dpy = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
43 | int[] vers = new int[2];
44 | EGL14.eglInitialize(dpy, vers, 0, vers, 1);
45 |
46 | int[] configAttr = {
47 | EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER,
48 | EGL14.EGL_LEVEL, 0,
49 | EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
50 | EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
51 | EGL14.EGL_NONE
52 | };
53 | EGLConfig[] configs = new EGLConfig[1];
54 | int[] numConfig = new int[1];
55 | EGL14.eglChooseConfig(dpy, configAttr, 0,
56 | configs, 0, 1, numConfig, 0);
57 | if (numConfig[0] == 0) {
58 | return 0;
59 | }
60 | EGLConfig config = configs[0];
61 |
62 | int[] surfAttr = {
63 | EGL14.EGL_WIDTH, 64,
64 | EGL14.EGL_HEIGHT, 64,
65 | EGL14.EGL_NONE
66 | };
67 | EGLSurface surf = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0);
68 |
69 | int[] ctxAttrib = {
70 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
71 | EGL14.EGL_NONE
72 | };
73 | EGLContext ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttrib, 0);
74 |
75 | EGL14.eglMakeCurrent(dpy, surf, surf, ctx);
76 |
77 | int[] maxSize = new int[1];
78 | GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);
79 |
80 | EGL14.eglMakeCurrent(dpy, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
81 | EGL14.EGL_NO_CONTEXT);
82 | EGL14.eglDestroySurface(dpy, surf);
83 | EGL14.eglDestroyContext(dpy, ctx);
84 | EGL14.eglTerminate(dpy);
85 |
86 | return maxSize[0];
87 | }
88 |
89 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
90 | private static int getMaxTextureEgl10() {
91 | EGL10 egl = (EGL10) javax.microedition.khronos.egl.EGLContext.getEGL();
92 |
93 | javax.microedition.khronos.egl.EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
94 | int[] vers = new int[2];
95 | egl.eglInitialize(dpy, vers);
96 |
97 | int[] configAttr = {
98 | EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
99 | EGL10.EGL_LEVEL, 0,
100 | EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
101 | EGL10.EGL_NONE
102 | };
103 | javax.microedition.khronos.egl.EGLConfig[] configs = new javax.microedition.khronos.egl.EGLConfig[1];
104 | int[] numConfig = new int[1];
105 | egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
106 | if (numConfig[0] == 0) {
107 | return 0;
108 | }
109 | javax.microedition.khronos.egl.EGLConfig config = configs[0];
110 |
111 | int[] surfAttr = {
112 | EGL10.EGL_WIDTH, 64,
113 | EGL10.EGL_HEIGHT, 64,
114 | EGL10.EGL_NONE
115 | };
116 | javax.microedition.khronos.egl.EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
117 | final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; // missing in EGL10
118 | int[] ctxAttrib = {
119 | EGL_CONTEXT_CLIENT_VERSION, 1,
120 | EGL10.EGL_NONE
121 | };
122 | javax.microedition.khronos.egl.EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttrib);
123 | egl.eglMakeCurrent(dpy, surf, surf, ctx);
124 | int[] maxSize = new int[1];
125 | GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
126 | egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
127 | EGL10.EGL_NO_CONTEXT);
128 | egl.eglDestroySurface(dpy, surf);
129 | egl.eglDestroyContext(dpy, ctx);
130 | egl.eglTerminate(dpy);
131 |
132 | return maxSize[0];
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/FastBitmapDrawable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2008 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.yalantis.ucrop.util;
17 |
18 | import android.graphics.Bitmap;
19 | import android.graphics.Canvas;
20 | import android.graphics.ColorFilter;
21 | import android.graphics.Paint;
22 | import android.graphics.PixelFormat;
23 | import android.graphics.drawable.Drawable;
24 |
25 | public class FastBitmapDrawable extends Drawable {
26 |
27 | private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
28 |
29 | private Bitmap mBitmap;
30 | private int mAlpha;
31 | private int mWidth, mHeight;
32 |
33 | public FastBitmapDrawable(Bitmap b) {
34 | mAlpha = 255;
35 | setBitmap(b);
36 | }
37 |
38 | @Override
39 | public void draw(Canvas canvas) {
40 | if (mBitmap != null && !mBitmap.isRecycled()) {
41 | canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
42 | }
43 | }
44 |
45 | @Override
46 | public void setColorFilter(ColorFilter cf) {
47 | mPaint.setColorFilter(cf);
48 | }
49 |
50 | @Override
51 | public int getOpacity() {
52 | return PixelFormat.TRANSLUCENT;
53 | }
54 |
55 | public void setFilterBitmap(boolean filterBitmap) {
56 | mPaint.setFilterBitmap(filterBitmap);
57 | }
58 |
59 | public int getAlpha() {
60 | return mAlpha;
61 | }
62 |
63 | @Override
64 | public void setAlpha(int alpha) {
65 | mAlpha = alpha;
66 | mPaint.setAlpha(alpha);
67 | }
68 |
69 | @Override
70 | public int getIntrinsicWidth() {
71 | return mWidth;
72 | }
73 |
74 | @Override
75 | public int getIntrinsicHeight() {
76 | return mHeight;
77 | }
78 |
79 | @Override
80 | public int getMinimumWidth() {
81 | return mWidth;
82 | }
83 |
84 | @Override
85 | public int getMinimumHeight() {
86 | return mHeight;
87 | }
88 |
89 | public Bitmap getBitmap() {
90 | return mBitmap;
91 | }
92 |
93 | public void setBitmap(Bitmap b) {
94 | mBitmap = b;
95 | if (b != null) {
96 | mWidth = mBitmap.getWidth();
97 | mHeight = mBitmap.getHeight();
98 | } else {
99 | mWidth = mHeight = 0;
100 | }
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/FileUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007-2008 OpenIntents.org
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.yalantis.ucrop.util;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.content.ContentUris;
21 | import android.content.Context;
22 | import android.database.Cursor;
23 | import android.net.Uri;
24 | import android.os.Build;
25 | import android.os.Environment;
26 | import android.provider.DocumentsContract;
27 | import android.provider.MediaStore;
28 | import android.text.TextUtils;
29 | import android.util.Log;
30 |
31 | import java.io.File;
32 | import java.io.FileInputStream;
33 | import java.io.FileOutputStream;
34 | import java.io.IOException;
35 | import java.nio.channels.FileChannel;
36 | import java.util.Locale;
37 |
38 | import androidx.annotation.NonNull;
39 |
40 | /**
41 | * @author Peli
42 | * @author paulburke (ipaulpro)
43 | * @version 2013-12-11
44 | */
45 | public class FileUtils {
46 |
47 | /**
48 | * TAG for log messages.
49 | */
50 | private static final String TAG = "FileUtils";
51 |
52 | private FileUtils() {
53 | }
54 |
55 | /**
56 | * @param uri The Uri to check.
57 | * @return Whether the Uri authority is ExternalStorageProvider.
58 | * @author paulburke
59 | */
60 | public static boolean isExternalStorageDocument(Uri uri) {
61 | return "com.android.externalstorage.documents".equals(uri.getAuthority());
62 | }
63 |
64 | /**
65 | * @param uri The Uri to check.
66 | * @return Whether the Uri authority is DownloadsProvider.
67 | * @author paulburke
68 | */
69 | public static boolean isDownloadsDocument(Uri uri) {
70 | return "com.android.providers.downloads.documents".equals(uri.getAuthority());
71 | }
72 |
73 | /**
74 | * @param uri The Uri to check.
75 | * @return Whether the Uri authority is MediaProvider.
76 | * @author paulburke
77 | */
78 | public static boolean isMediaDocument(Uri uri) {
79 | return "com.android.providers.media.documents".equals(uri.getAuthority());
80 | }
81 |
82 | /**
83 | * @param uri The Uri to check.
84 | * @return Whether the Uri authority is Google Photos.
85 | */
86 | public static boolean isGooglePhotosUri(Uri uri) {
87 | return "com.google.android.apps.photos.content".equals(uri.getAuthority());
88 | }
89 |
90 | /**
91 | * Get the value of the data column for this Uri. This is useful for
92 | * MediaStore Uris, and other file-based ContentProviders.
93 | *
94 | * @param context The context.
95 | * @param uri The Uri to query.
96 | * @param selection (Optional) Filter used in the query.
97 | * @param selectionArgs (Optional) Selection arguments used in the query.
98 | * @return The value of the _data column, which is typically a file path.
99 | * @author paulburke
100 | */
101 | public static String getDataColumn(Context context, Uri uri, String selection,
102 | String[] selectionArgs) {
103 |
104 | Cursor cursor = null;
105 | final String column = "_data";
106 | final String[] projection = {
107 | column
108 | };
109 |
110 | try {
111 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
112 | null);
113 | if (cursor != null && cursor.moveToFirst()) {
114 | final int column_index = cursor.getColumnIndexOrThrow(column);
115 | return cursor.getString(column_index);
116 | }
117 | } catch (IllegalArgumentException ex) {
118 | Log.i(TAG, String.format(Locale.getDefault(), "getDataColumn: _data - [%s]", ex.getMessage()));
119 | } finally {
120 | if (cursor != null) {
121 | cursor.close();
122 | }
123 | }
124 | return null;
125 | }
126 |
127 | /**
128 | * Get a file path from a Uri. This will get the the path for Storage Access
129 | * Framework Documents, as well as the _data field for the MediaStore and
130 | * other file-based ContentProviders.
131 | *
132 | * Callers should check whether the path is local before assuming it
133 | * represents a local file.
134 | *
135 | * @param context The context.
136 | * @param uri The Uri to query.
137 | * @author paulburke
138 | */
139 | @SuppressLint("NewApi")
140 | public static String getPath(final Context context, final Uri uri) {
141 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
142 |
143 | // DocumentProvider
144 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
145 | if (isExternalStorageDocument(uri)) {
146 | final String docId = DocumentsContract.getDocumentId(uri);
147 | final String[] split = docId.split(":");
148 | final String type = split[0];
149 |
150 | if ("primary".equalsIgnoreCase(type)) {
151 | return Environment.getExternalStorageDirectory() + "/" + split[1];
152 | }
153 |
154 | // TODO handle non-primary volumes
155 | }
156 | // DownloadsProvider
157 | else if (isDownloadsDocument(uri)) {
158 |
159 | final String id = DocumentsContract.getDocumentId(uri);
160 | if (!TextUtils.isEmpty(id)) {
161 | try {
162 | final Uri contentUri = ContentUris.withAppendedId(
163 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
164 | return getDataColumn(context, contentUri, null, null);
165 | } catch (NumberFormatException e) {
166 | Log.i(TAG, e.getMessage());
167 | return null;
168 | }
169 | }
170 |
171 | }
172 | // MediaProvider
173 | else if (isMediaDocument(uri)) {
174 | final String docId = DocumentsContract.getDocumentId(uri);
175 | final String[] split = docId.split(":");
176 | final String type = split[0];
177 |
178 | Uri contentUri = null;
179 | if ("image".equals(type)) {
180 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
181 | } else if ("video".equals(type)) {
182 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
183 | } else if ("audio".equals(type)) {
184 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
185 | }
186 |
187 | final String selection = "_id=?";
188 | final String[] selectionArgs = new String[]{
189 | split[1]
190 | };
191 |
192 | return getDataColumn(context, contentUri, selection, selectionArgs);
193 | }
194 | }
195 | // MediaStore (and general)
196 | else if ("content".equalsIgnoreCase(uri.getScheme())) {
197 |
198 | // Return the remote address
199 | if (isGooglePhotosUri(uri)) {
200 | return uri.getLastPathSegment();
201 | }
202 |
203 | return getDataColumn(context, uri, null, null);
204 | }
205 | // File
206 | else if ("file".equalsIgnoreCase(uri.getScheme())) {
207 | return uri.getPath();
208 | }
209 |
210 | return null;
211 | }
212 |
213 | /**
214 | * Copies one file into the other with the given paths.
215 | * In the event that the paths are the same, trying to copy one file to the other
216 | * will cause both files to become null.
217 | * Simply skipping this step if the paths are identical.
218 | */
219 | public static void copyFile(@NonNull String pathFrom, @NonNull String pathTo) throws IOException {
220 | if (pathFrom.equalsIgnoreCase(pathTo)) {
221 | return;
222 | }
223 |
224 | FileChannel outputChannel = null;
225 | FileChannel inputChannel = null;
226 | try {
227 | inputChannel = new FileInputStream(new File(pathFrom)).getChannel();
228 | outputChannel = new FileOutputStream(new File(pathTo)).getChannel();
229 | inputChannel.transferTo(0, inputChannel.size(), outputChannel);
230 | inputChannel.close();
231 | } finally {
232 | if (inputChannel != null) inputChannel.close();
233 | if (outputChannel != null) outputChannel.close();
234 | }
235 | }
236 |
237 | }
238 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/RectUtils.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.util;
2 |
3 | import android.graphics.RectF;
4 |
5 | public class RectUtils {
6 |
7 | /**
8 | * Gets a float array of the 2D coordinates representing a rectangles
9 | * corners.
10 | * The order of the corners in the float array is:
11 | * 0------->1
12 | * ^ |
13 | * | |
14 | * | v
15 | * 3<-------2
16 | *
17 | * @param r the rectangle to get the corners of
18 | * @return the float array of corners (8 floats)
19 | */
20 | public static float[] getCornersFromRect(RectF r) {
21 | return new float[]{
22 | r.left, r.top,
23 | r.right, r.top,
24 | r.right, r.bottom,
25 | r.left, r.bottom
26 | };
27 | }
28 |
29 | /**
30 | * Gets a float array of two lengths representing a rectangles width and height
31 | * The order of the corners in the input float array is:
32 | * 0------->1
33 | * ^ |
34 | * | |
35 | * | v
36 | * 3<-------2
37 | *
38 | * @param corners the float array of corners (8 floats)
39 | * @return the float array of width and height (2 floats)
40 | */
41 | public static float[] getRectSidesFromCorners(float[] corners) {
42 | return new float[]{(float) Math.sqrt(Math.pow(corners[0] - corners[2], 2) + Math.pow(corners[1] - corners[3], 2)),
43 | (float) Math.sqrt(Math.pow(corners[2] - corners[4], 2) + Math.pow(corners[3] - corners[5], 2))};
44 | }
45 |
46 | public static float[] getCenterFromRect(RectF r) {
47 | return new float[]{r.centerX(), r.centerY()};
48 | }
49 |
50 | /**
51 | * Takes an array of 2D coordinates representing corners and returns the
52 | * smallest rectangle containing those coordinates.
53 | *
54 | * @param array array of 2D coordinates
55 | * @return smallest rectangle containing coordinates
56 | */
57 | public static RectF trapToRect(float[] array) {
58 | RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
59 | Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
60 | for (int i = 1; i < array.length; i += 2) {
61 | float x = Math.round(array[i - 1] * 10) / 10.f;
62 | float y = Math.round(array[i] * 10) / 10.f;
63 | r.left = (x < r.left) ? x : r.left;
64 | r.top = (y < r.top) ? y : r.top;
65 | r.right = (x > r.right) ? x : r.right;
66 | r.bottom = (y > r.bottom) ? y : r.bottom;
67 | }
68 | r.sort();
69 | return r;
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/RotationGestureDetector.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.util;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | public class RotationGestureDetector {
8 |
9 | private static final int INVALID_POINTER_INDEX = -1;
10 |
11 | private float fX, fY, sX, sY;
12 |
13 | private int mPointerIndex1, mPointerIndex2;
14 | private float mAngle;
15 | private boolean mIsFirstTouch;
16 |
17 | private OnRotationGestureListener mListener;
18 |
19 | public RotationGestureDetector(OnRotationGestureListener listener) {
20 | mListener = listener;
21 | mPointerIndex1 = INVALID_POINTER_INDEX;
22 | mPointerIndex2 = INVALID_POINTER_INDEX;
23 | }
24 |
25 | public float getAngle() {
26 | return mAngle;
27 | }
28 |
29 | public boolean onTouchEvent(@NonNull MotionEvent event) {
30 | switch (event.getActionMasked()) {
31 | case MotionEvent.ACTION_DOWN:
32 | sX = event.getX();
33 | sY = event.getY();
34 | mPointerIndex1 = event.findPointerIndex(event.getPointerId(0));
35 | mAngle = 0;
36 | mIsFirstTouch = true;
37 | break;
38 | case MotionEvent.ACTION_POINTER_DOWN:
39 | fX = event.getX();
40 | fY = event.getY();
41 | mPointerIndex2 = event.findPointerIndex(event.getPointerId(event.getActionIndex()));
42 | mAngle = 0;
43 | mIsFirstTouch = true;
44 | break;
45 | case MotionEvent.ACTION_MOVE:
46 | if (mPointerIndex1 != INVALID_POINTER_INDEX && mPointerIndex2 != INVALID_POINTER_INDEX && event.getPointerCount() > mPointerIndex2) {
47 | float nfX, nfY, nsX, nsY;
48 |
49 | nsX = event.getX(mPointerIndex1);
50 | nsY = event.getY(mPointerIndex1);
51 | nfX = event.getX(mPointerIndex2);
52 | nfY = event.getY(mPointerIndex2);
53 |
54 | if (mIsFirstTouch) {
55 | mAngle = 0;
56 | mIsFirstTouch = false;
57 | } else {
58 | calculateAngleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
59 | }
60 |
61 | if (mListener != null) {
62 | mListener.onRotation(this);
63 | }
64 | fX = nfX;
65 | fY = nfY;
66 | sX = nsX;
67 | sY = nsY;
68 | }
69 | break;
70 | case MotionEvent.ACTION_UP:
71 | mPointerIndex1 = INVALID_POINTER_INDEX;
72 | break;
73 | case MotionEvent.ACTION_POINTER_UP:
74 | mPointerIndex2 = INVALID_POINTER_INDEX;
75 | break;
76 | }
77 | return true;
78 | }
79 |
80 | private float calculateAngleBetweenLines(float fx1, float fy1, float fx2, float fy2,
81 | float sx1, float sy1, float sx2, float sy2) {
82 | return calculateAngleDelta(
83 | (float) Math.toDegrees((float) Math.atan2((fy1 - fy2), (fx1 - fx2))),
84 | (float) Math.toDegrees((float) Math.atan2((sy1 - sy2), (sx1 - sx2))));
85 | }
86 |
87 | private float calculateAngleDelta(float angleFrom, float angleTo) {
88 | mAngle = angleTo % 360.0f - angleFrom % 360.0f;
89 |
90 | if (mAngle < -180.0f) {
91 | mAngle += 360.0f;
92 | } else if (mAngle > 180.0f) {
93 | mAngle -= 360.0f;
94 | }
95 |
96 | return mAngle;
97 | }
98 |
99 | public static class SimpleOnRotationGestureListener implements OnRotationGestureListener {
100 |
101 | @Override
102 | public boolean onRotation(RotationGestureDetector rotationDetector) {
103 | return false;
104 | }
105 | }
106 |
107 | public interface OnRotationGestureListener {
108 |
109 | boolean onRotation(RotationGestureDetector rotationDetector);
110 | }
111 |
112 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/util/SelectedStateListDrawable.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.util;
2 |
3 | import android.graphics.PorterDuff;
4 | import android.graphics.drawable.Drawable;
5 | import android.graphics.drawable.StateListDrawable;
6 |
7 | /**
8 | * Hack class to properly support state drawable back to Android 1.6
9 | */
10 | public class SelectedStateListDrawable extends StateListDrawable {
11 |
12 | private int mSelectionColor;
13 |
14 | public SelectedStateListDrawable(Drawable drawable, int selectionColor) {
15 | super();
16 | this.mSelectionColor = selectionColor;
17 | addState(new int[]{android.R.attr.state_selected}, drawable);
18 | addState(new int[]{}, drawable);
19 | }
20 |
21 | @Override
22 | protected boolean onStateChange(int[] states) {
23 | boolean isStatePressedInArray = false;
24 | for (int state : states) {
25 | if (state == android.R.attr.state_selected) {
26 | isStatePressedInArray = true;
27 | }
28 | }
29 | if (isStatePressedInArray) {
30 | super.setColorFilter(mSelectionColor, PorterDuff.Mode.SRC_ATOP);
31 | } else {
32 | super.clearColorFilter();
33 | }
34 | return super.onStateChange(states);
35 | }
36 |
37 | @Override
38 | public boolean isStateful() {
39 | return true;
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/view/GestureCropImageView.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.GestureDetector;
6 | import android.view.MotionEvent;
7 | import android.view.ScaleGestureDetector;
8 |
9 | import com.yalantis.ucrop.util.RotationGestureDetector;
10 |
11 | /**
12 | * Created by Oleksii Shliama (https://github.com/shliama).
13 | */
14 | public class GestureCropImageView extends CropImageView {
15 |
16 | private static final int DOUBLE_TAP_ZOOM_DURATION = 200;
17 |
18 | private ScaleGestureDetector mScaleDetector;
19 | private RotationGestureDetector mRotateDetector;
20 | private GestureDetector mGestureDetector;
21 |
22 | private float mMidPntX, mMidPntY;
23 |
24 | private boolean mIsRotateEnabled = true, mIsScaleEnabled = true, mIsGestureEnabled = true;
25 | private int mDoubleTapScaleSteps = 5;
26 |
27 | public GestureCropImageView(Context context) {
28 | super(context);
29 | }
30 |
31 | public GestureCropImageView(Context context, AttributeSet attrs) {
32 | this(context, attrs, 0);
33 | }
34 |
35 | public GestureCropImageView(Context context, AttributeSet attrs, int defStyle) {
36 | super(context, attrs, defStyle);
37 | }
38 |
39 | public void setScaleEnabled(boolean scaleEnabled) {
40 | mIsScaleEnabled = scaleEnabled;
41 | }
42 |
43 | public boolean isScaleEnabled() {
44 | return mIsScaleEnabled;
45 | }
46 |
47 | public void setRotateEnabled(boolean rotateEnabled) {
48 | mIsRotateEnabled = rotateEnabled;
49 | }
50 |
51 | public boolean isRotateEnabled() {
52 | return mIsRotateEnabled;
53 | }
54 |
55 | public void setGestureEnabled(boolean gestureEnabled) {
56 | mIsGestureEnabled = gestureEnabled;
57 | }
58 |
59 | public boolean isGestureEnabled() {
60 | return mIsGestureEnabled;
61 | }
62 |
63 | public void setDoubleTapScaleSteps(int doubleTapScaleSteps) {
64 | mDoubleTapScaleSteps = doubleTapScaleSteps;
65 | }
66 |
67 | public int getDoubleTapScaleSteps() {
68 | return mDoubleTapScaleSteps;
69 | }
70 |
71 | /**
72 | * If it's ACTION_DOWN event - user touches the screen and all current animation must be canceled.
73 | * If it's ACTION_UP event - user removed all fingers from the screen and current image position must be corrected.
74 | * If there are more than 2 fingers - update focal point coordinates.
75 | * Pass the event to the gesture detectors if those are enabled.
76 | */
77 | @Override
78 | public boolean onTouchEvent(MotionEvent event) {
79 | if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
80 | cancelAllAnimations();
81 | }
82 |
83 | if (event.getPointerCount() > 1) {
84 | mMidPntX = (event.getX(0) + event.getX(1)) / 2;
85 | mMidPntY = (event.getY(0) + event.getY(1)) / 2;
86 | }
87 |
88 | if (mIsGestureEnabled) {
89 | mGestureDetector.onTouchEvent(event);
90 | }
91 |
92 | if (mIsScaleEnabled) {
93 | mScaleDetector.onTouchEvent(event);
94 | }
95 |
96 | if (mIsRotateEnabled) {
97 | mRotateDetector.onTouchEvent(event);
98 | }
99 |
100 | if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
101 | setImageToWrapCropBounds();
102 | }
103 | return true;
104 | }
105 |
106 | @Override
107 | protected void init() {
108 | super.init();
109 | setupGestureListeners();
110 | }
111 |
112 | /**
113 | * This method calculates target scale value for double tap gesture.
114 | * User is able to zoom the image from min scale value
115 | * to the max scale value with {@link #mDoubleTapScaleSteps} double taps.
116 | */
117 | protected float getDoubleTapTargetScale() {
118 | return getCurrentScale() * (float) Math.pow(getMaxScale() / getMinScale(), 1.0f / mDoubleTapScaleSteps);
119 | }
120 |
121 | private void setupGestureListeners() {
122 | mGestureDetector = new GestureDetector(getContext(), new GestureListener(), null, true);
123 | mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
124 | mRotateDetector = new RotationGestureDetector(new RotateListener());
125 | }
126 |
127 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
128 |
129 | @Override
130 | public boolean onScale(ScaleGestureDetector detector) {
131 | postScale(detector.getScaleFactor(), mMidPntX, mMidPntY);
132 | return true;
133 | }
134 | }
135 |
136 | private class GestureListener extends GestureDetector.SimpleOnGestureListener {
137 |
138 | @Override
139 | public boolean onDoubleTap(MotionEvent e) {
140 | zoomImageToPosition(getDoubleTapTargetScale(), e.getX(), e.getY(), DOUBLE_TAP_ZOOM_DURATION);
141 | return super.onDoubleTap(e);
142 | }
143 |
144 | @Override
145 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
146 | postTranslate(-distanceX, -distanceY);
147 | return true;
148 | }
149 |
150 | }
151 |
152 | private class RotateListener extends RotationGestureDetector.SimpleOnRotationGestureListener {
153 |
154 | @Override
155 | public boolean onRotation(RotationGestureDetector rotationDetector) {
156 | postRotate(rotationDetector.getAngle(), mMidPntX, mMidPntY);
157 | return true;
158 | }
159 |
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/view/TransformImageView.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Matrix;
6 | import android.graphics.RectF;
7 | import android.graphics.drawable.Drawable;
8 | import android.net.Uri;
9 | import android.util.AttributeSet;
10 | import android.util.Log;
11 |
12 | import com.yalantis.ucrop.callback.BitmapLoadCallback;
13 | import com.yalantis.ucrop.model.ExifInfo;
14 | import com.yalantis.ucrop.util.BitmapLoadUtils;
15 | import com.yalantis.ucrop.util.FastBitmapDrawable;
16 | import com.yalantis.ucrop.util.RectUtils;
17 |
18 | import androidx.annotation.IntRange;
19 | import androidx.annotation.NonNull;
20 | import androidx.annotation.Nullable;
21 | import androidx.appcompat.widget.AppCompatImageView;
22 |
23 | /**
24 | * Created by Oleksii Shliama (https://github.com/shliama).
25 | *
26 | * This class provides base logic to setup the image, transform it with matrix (move, scale, rotate),
27 | * and methods to get current matrix state.
28 | */
29 | public class TransformImageView extends AppCompatImageView {
30 |
31 | private static final String TAG = "TransformImageView";
32 |
33 | private static final int RECT_CORNER_POINTS_COORDS = 8;
34 | private static final int RECT_CENTER_POINT_COORDS = 2;
35 | private static final int MATRIX_VALUES_COUNT = 9;
36 |
37 | protected final float[] mCurrentImageCorners = new float[RECT_CORNER_POINTS_COORDS];
38 | protected final float[] mCurrentImageCenter = new float[RECT_CENTER_POINT_COORDS];
39 |
40 | private final float[] mMatrixValues = new float[MATRIX_VALUES_COUNT];
41 |
42 | protected Matrix mCurrentImageMatrix = new Matrix();
43 | protected int mThisWidth, mThisHeight;
44 |
45 | protected TransformImageListener mTransformImageListener;
46 |
47 | private float[] mInitialImageCorners;
48 | private float[] mInitialImageCenter;
49 |
50 | protected boolean mBitmapDecoded = false;
51 | protected boolean mBitmapLaidOut = false;
52 |
53 | private int mMaxBitmapSize = 0;
54 |
55 | private String mImageInputPath, mImageOutputPath;
56 | private ExifInfo mExifInfo;
57 |
58 | /**
59 | * Interface for rotation and scale change notifying.
60 | */
61 | public interface TransformImageListener {
62 |
63 | void onLoadComplete();
64 |
65 | void onLoadFailure(@NonNull Exception e);
66 |
67 | void onRotate(float currentAngle);
68 |
69 | void onScale(float currentScale);
70 |
71 | }
72 |
73 | public TransformImageView(Context context) {
74 | this(context, null);
75 | }
76 |
77 | public TransformImageView(Context context, AttributeSet attrs) {
78 | this(context, attrs, 0);
79 | }
80 |
81 | public TransformImageView(Context context, AttributeSet attrs, int defStyle) {
82 | super(context, attrs, defStyle);
83 | init();
84 | }
85 |
86 | public void setTransformImageListener(TransformImageListener transformImageListener) {
87 | mTransformImageListener = transformImageListener;
88 | }
89 |
90 | @Override
91 | public void setScaleType(ScaleType scaleType) {
92 | if (scaleType == ScaleType.MATRIX) {
93 | super.setScaleType(scaleType);
94 | } else {
95 | Log.w(TAG, "Invalid ScaleType. Only ScaleType.MATRIX can be used");
96 | }
97 | }
98 |
99 | /**
100 | * Setter for {@link #mMaxBitmapSize} value.
101 | * Be sure to call it before {@link #setImageURI(Uri)} or other image setters.
102 | *
103 | * @param maxBitmapSize - max size for both width and height of bitmap that will be used in the view.
104 | */
105 | public void setMaxBitmapSize(int maxBitmapSize) {
106 | mMaxBitmapSize = maxBitmapSize;
107 | }
108 |
109 | public int getMaxBitmapSize() {
110 | if (mMaxBitmapSize <= 0) {
111 | mMaxBitmapSize = BitmapLoadUtils.calculateMaxBitmapSize(getContext());
112 | }
113 | return mMaxBitmapSize;
114 | }
115 |
116 | @Override
117 | public void setImageBitmap(final Bitmap bitmap) {
118 | setImageDrawable(new FastBitmapDrawable(bitmap));
119 | }
120 |
121 | public String getImageInputPath() {
122 | return mImageInputPath;
123 | }
124 |
125 | public String getImageOutputPath() {
126 | return mImageOutputPath;
127 | }
128 |
129 | public ExifInfo getExifInfo() {
130 | return mExifInfo;
131 | }
132 |
133 | /**
134 | * This method takes an Uri as a parameter, then calls method to decode it into Bitmap with specified size.
135 | *
136 | * @param imageUri - image Uri
137 | * @throws Exception - can throw exception if having problems with decoding Uri or OOM.
138 | */
139 | public void setImageUri(@NonNull Uri imageUri, @Nullable Uri outputUri) throws Exception {
140 | int maxBitmapSize = getMaxBitmapSize();
141 |
142 | BitmapLoadUtils.decodeBitmapInBackground(getContext(), imageUri, outputUri, maxBitmapSize, maxBitmapSize,
143 | new BitmapLoadCallback() {
144 |
145 | @Override
146 | public void onBitmapLoaded(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo, @NonNull String imageInputPath, @Nullable String imageOutputPath) {
147 | mImageInputPath = imageInputPath;
148 | mImageOutputPath = imageOutputPath;
149 | mExifInfo = exifInfo;
150 |
151 | mBitmapDecoded = true;
152 | setImageBitmap(bitmap);
153 | }
154 |
155 | @Override
156 | public void onFailure(@NonNull Exception bitmapWorkerException) {
157 | Log.e(TAG, "onFailure: setImageUri", bitmapWorkerException);
158 | if (mTransformImageListener != null) {
159 | mTransformImageListener.onLoadFailure(bitmapWorkerException);
160 | }
161 | }
162 | });
163 | }
164 |
165 | /**
166 | * @return - current image scale value.
167 | * [1.0f - for original image, 2.0f - for 200% scaled image, etc.]
168 | */
169 | public float getCurrentScale() {
170 | return getMatrixScale(mCurrentImageMatrix);
171 | }
172 |
173 | /**
174 | * This method calculates scale value for given Matrix object.
175 | */
176 | public float getMatrixScale(@NonNull Matrix matrix) {
177 | return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2)
178 | + Math.pow(getMatrixValue(matrix, Matrix.MSKEW_Y), 2));
179 | }
180 |
181 | /**
182 | * @return - current image rotation angle.
183 | */
184 | public float getCurrentAngle() {
185 | return getMatrixAngle(mCurrentImageMatrix);
186 | }
187 |
188 | /**
189 | * This method calculates rotation angle for given Matrix object.
190 | */
191 | public float getMatrixAngle(@NonNull Matrix matrix) {
192 | return (float) -(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X),
193 | getMatrixValue(matrix, Matrix.MSCALE_X)) * (180 / Math.PI));
194 | }
195 |
196 | @Override
197 | public void setImageMatrix(Matrix matrix) {
198 | super.setImageMatrix(matrix);
199 | mCurrentImageMatrix.set(matrix);
200 | updateCurrentImagePoints();
201 | }
202 |
203 | @Nullable
204 | public Bitmap getViewBitmap() {
205 | if (getDrawable() == null || !(getDrawable() instanceof FastBitmapDrawable)) {
206 | return null;
207 | } else {
208 | return ((FastBitmapDrawable) getDrawable()).getBitmap();
209 | }
210 | }
211 |
212 | /**
213 | * This method translates current image.
214 | *
215 | * @param deltaX - horizontal shift
216 | * @param deltaY - vertical shift
217 | */
218 | public void postTranslate(float deltaX, float deltaY) {
219 | if (deltaX != 0 || deltaY != 0) {
220 | mCurrentImageMatrix.postTranslate(deltaX, deltaY);
221 | setImageMatrix(mCurrentImageMatrix);
222 | }
223 | }
224 |
225 | /**
226 | * This method scales current image.
227 | *
228 | * @param deltaScale - scale value
229 | * @param px - scale center X
230 | * @param py - scale center Y
231 | */
232 | public void postScale(float deltaScale, float px, float py) {
233 | if (deltaScale != 0) {
234 | mCurrentImageMatrix.postScale(deltaScale, deltaScale, px, py);
235 | setImageMatrix(mCurrentImageMatrix);
236 | if (mTransformImageListener != null) {
237 | mTransformImageListener.onScale(getMatrixScale(mCurrentImageMatrix));
238 | }
239 | }
240 | }
241 |
242 | /**
243 | * This method rotates current image.
244 | *
245 | * @param deltaAngle - rotation angle
246 | * @param px - rotation center X
247 | * @param py - rotation center Y
248 | */
249 | public void postRotate(float deltaAngle, float px, float py) {
250 | if (deltaAngle != 0) {
251 | mCurrentImageMatrix.postRotate(deltaAngle, px, py);
252 | setImageMatrix(mCurrentImageMatrix);
253 | if (mTransformImageListener != null) {
254 | mTransformImageListener.onRotate(getMatrixAngle(mCurrentImageMatrix));
255 | }
256 | }
257 | }
258 |
259 | protected void init() {
260 | setScaleType(ScaleType.MATRIX);
261 | }
262 |
263 | @Override
264 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
265 | super.onLayout(changed, left, top, right, bottom);
266 | if (changed || (mBitmapDecoded && !mBitmapLaidOut)) {
267 |
268 | left = getPaddingLeft();
269 | top = getPaddingTop();
270 | right = getWidth() - getPaddingRight();
271 | bottom = getHeight() - getPaddingBottom();
272 | mThisWidth = right - left;
273 | mThisHeight = bottom - top;
274 |
275 | onImageLaidOut();
276 | }
277 | }
278 |
279 | /**
280 | * When image is laid out {@link #mInitialImageCenter} and {@link #mInitialImageCenter}
281 | * must be set.
282 | */
283 | protected void onImageLaidOut() {
284 | final Drawable drawable = getDrawable();
285 | if (drawable == null) {
286 | return;
287 | }
288 |
289 | float w = drawable.getIntrinsicWidth();
290 | float h = drawable.getIntrinsicHeight();
291 |
292 | Log.d(TAG, String.format("Image size: [%d:%d]", (int) w, (int) h));
293 |
294 | RectF initialImageRect = new RectF(0, 0, w, h);
295 | mInitialImageCorners = RectUtils.getCornersFromRect(initialImageRect);
296 | mInitialImageCenter = RectUtils.getCenterFromRect(initialImageRect);
297 |
298 | mBitmapLaidOut = true;
299 |
300 | if (mTransformImageListener != null) {
301 | mTransformImageListener.onLoadComplete();
302 | }
303 | }
304 |
305 | /**
306 | * This method returns Matrix value for given index.
307 | *
308 | * @param matrix - valid Matrix object
309 | * @param valueIndex - index of needed value. See {@link Matrix#MSCALE_X} and others.
310 | * @return - matrix value for index
311 | */
312 | protected float getMatrixValue(@NonNull Matrix matrix, @IntRange(from = 0, to = MATRIX_VALUES_COUNT) int valueIndex) {
313 | matrix.getValues(mMatrixValues);
314 | return mMatrixValues[valueIndex];
315 | }
316 |
317 | /**
318 | * This method logs given matrix X, Y, scale, and angle values.
319 | * Can be used for debug.
320 | */
321 | @SuppressWarnings("unused")
322 | protected void printMatrix(@NonNull String logPrefix, @NonNull Matrix matrix) {
323 | float x = getMatrixValue(matrix, Matrix.MTRANS_X);
324 | float y = getMatrixValue(matrix, Matrix.MTRANS_Y);
325 | float rScale = getMatrixScale(matrix);
326 | float rAngle = getMatrixAngle(matrix);
327 | Log.d(TAG, logPrefix + ": matrix: { x: " + x + ", y: " + y + ", scale: " + rScale + ", angle: " + rAngle + " }");
328 | }
329 |
330 | /**
331 | * This method updates current image corners and center points that are stored in
332 | * {@link #mCurrentImageCorners} and {@link #mCurrentImageCenter} arrays.
333 | * Those are used for several calculations.
334 | */
335 | private void updateCurrentImagePoints() {
336 | mCurrentImageMatrix.mapPoints(mCurrentImageCorners, mInitialImageCorners);
337 | mCurrentImageMatrix.mapPoints(mCurrentImageCenter, mInitialImageCenter);
338 | }
339 |
340 | }
341 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/view/UCropView.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.graphics.RectF;
6 | import android.util.AttributeSet;
7 | import android.view.LayoutInflater;
8 | import android.widget.FrameLayout;
9 |
10 | import com.yalantis.ucrop.R;
11 | import com.yalantis.ucrop.callback.CropBoundsChangeListener;
12 | import com.yalantis.ucrop.callback.OverlayViewChangeListener;
13 |
14 | import androidx.annotation.NonNull;
15 |
16 | public class UCropView extends FrameLayout {
17 |
18 | private GestureCropImageView mGestureCropImageView;
19 | private final OverlayView mViewOverlay;
20 |
21 | public UCropView(Context context, AttributeSet attrs) {
22 | this(context, attrs, 0);
23 | }
24 |
25 | public UCropView(Context context, AttributeSet attrs, int defStyleAttr) {
26 | super(context, attrs, defStyleAttr);
27 |
28 | LayoutInflater.from(context).inflate(R.layout.ucrop_view, this, true);
29 | mGestureCropImageView = findViewById(R.id.image_view_crop);
30 | mViewOverlay = findViewById(R.id.view_overlay);
31 |
32 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ucrop_UCropView);
33 | mViewOverlay.processStyledAttributes(a);
34 | mGestureCropImageView.processStyledAttributes(a);
35 | a.recycle();
36 |
37 |
38 | setListenersToViews();
39 | }
40 |
41 | private void setListenersToViews() {
42 | mGestureCropImageView.setCropBoundsChangeListener(new CropBoundsChangeListener() {
43 | @Override
44 | public void onCropAspectRatioChanged(float cropRatio) {
45 | mViewOverlay.setTargetAspectRatio(cropRatio);
46 | }
47 | });
48 | mViewOverlay.setOverlayViewChangeListener(new OverlayViewChangeListener() {
49 | @Override
50 | public void onCropRectUpdated(RectF cropRect) {
51 | mGestureCropImageView.setCropRect(cropRect);
52 | }
53 | });
54 | }
55 |
56 | @Override
57 | public boolean shouldDelayChildPressedState() {
58 | return false;
59 | }
60 |
61 | @NonNull
62 | public GestureCropImageView getCropImageView() {
63 | return mGestureCropImageView;
64 | }
65 |
66 | @NonNull
67 | public OverlayView getOverlayView() {
68 | return mViewOverlay;
69 | }
70 |
71 | /**
72 | * Method for reset state for UCropImageView such as rotation, scale, translation.
73 | * Be careful: this method recreate UCropImageView instance and reattach it to layout.
74 | */
75 | public void resetCropImageView() {
76 | removeView(mGestureCropImageView);
77 | mGestureCropImageView = new GestureCropImageView(getContext());
78 | setListenersToViews();
79 | mGestureCropImageView.setCropRect(getOverlayView().getCropViewRect());
80 | addView(mGestureCropImageView, 0);
81 | }
82 | }
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/view/widget/AspectRatioTextView.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.view.widget;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.res.ColorStateList;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Canvas;
8 | import android.graphics.Paint;
9 | import android.graphics.Rect;
10 | import android.os.Build;
11 | import android.text.TextUtils;
12 | import android.util.AttributeSet;
13 | import android.view.Gravity;
14 |
15 | import com.yalantis.ucrop.R;
16 | import com.yalantis.ucrop.model.AspectRatio;
17 | import com.yalantis.ucrop.view.CropImageView;
18 |
19 | import java.util.Locale;
20 |
21 | import androidx.annotation.ColorInt;
22 | import androidx.annotation.NonNull;
23 | import androidx.appcompat.widget.AppCompatTextView;
24 | import androidx.core.content.ContextCompat;
25 |
26 | /**
27 | * Created by Oleksii Shliama (https://github.com/shliama).
28 | */
29 | public class AspectRatioTextView extends AppCompatTextView {
30 |
31 | private final float MARGIN_MULTIPLIER = 1.5f;
32 | private final Rect mCanvasClipBounds = new Rect();
33 | private Paint mDotPaint;
34 | private int mDotSize;
35 | private float mAspectRatio;
36 |
37 | private String mAspectRatioTitle;
38 | private float mAspectRatioX, mAspectRatioY;
39 |
40 | public AspectRatioTextView(Context context) {
41 | this(context, null);
42 | }
43 |
44 | public AspectRatioTextView(Context context, AttributeSet attrs) {
45 | this(context, attrs, 0);
46 | }
47 |
48 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
49 | public AspectRatioTextView(Context context, AttributeSet attrs, int defStyleAttr) {
50 | super(context, attrs, defStyleAttr);
51 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ucrop_AspectRatioTextView);
52 | init(a);
53 | }
54 |
55 | /**
56 | * @param activeColor the resolved color for active elements
57 | */
58 |
59 | public void setActiveColor(@ColorInt int activeColor) {
60 | applyActiveColor(activeColor);
61 | invalidate();
62 | }
63 |
64 | public void setAspectRatio(@NonNull AspectRatio aspectRatio) {
65 | mAspectRatioTitle = aspectRatio.getAspectRatioTitle();
66 | mAspectRatioX = aspectRatio.getAspectRatioX();
67 | mAspectRatioY = aspectRatio.getAspectRatioY();
68 |
69 | if (mAspectRatioX == CropImageView.SOURCE_IMAGE_ASPECT_RATIO || mAspectRatioY == CropImageView.SOURCE_IMAGE_ASPECT_RATIO) {
70 | mAspectRatio = CropImageView.SOURCE_IMAGE_ASPECT_RATIO;
71 | } else {
72 | mAspectRatio = mAspectRatioX / mAspectRatioY;
73 | }
74 |
75 | setTitle();
76 | }
77 |
78 | public float getAspectRatio(boolean toggleRatio) {
79 | if (toggleRatio) {
80 | toggleAspectRatio();
81 | setTitle();
82 | }
83 | return mAspectRatio;
84 | }
85 |
86 | @Override
87 | protected void onDraw(Canvas canvas) {
88 | super.onDraw(canvas);
89 |
90 | if (isSelected()) {
91 | canvas.getClipBounds(mCanvasClipBounds);
92 |
93 | float x = (mCanvasClipBounds.right - mCanvasClipBounds.left) / 2.0f;
94 | float y = (mCanvasClipBounds.bottom - mCanvasClipBounds.top / 2f) - mDotSize * MARGIN_MULTIPLIER;
95 |
96 | canvas.drawCircle(x, y, mDotSize / 2f, mDotPaint);
97 | }
98 | }
99 |
100 | @SuppressWarnings("deprecation")
101 | private void init(@NonNull TypedArray a) {
102 | setGravity(Gravity.CENTER_HORIZONTAL);
103 |
104 | mAspectRatioTitle = a.getString(R.styleable.ucrop_AspectRatioTextView_ucrop_artv_ratio_title);
105 | mAspectRatioX = a.getFloat(R.styleable.ucrop_AspectRatioTextView_ucrop_artv_ratio_x, CropImageView.SOURCE_IMAGE_ASPECT_RATIO);
106 | mAspectRatioY = a.getFloat(R.styleable.ucrop_AspectRatioTextView_ucrop_artv_ratio_y, CropImageView.SOURCE_IMAGE_ASPECT_RATIO);
107 |
108 | if (mAspectRatioX == CropImageView.SOURCE_IMAGE_ASPECT_RATIO || mAspectRatioY == CropImageView.SOURCE_IMAGE_ASPECT_RATIO) {
109 | mAspectRatio = CropImageView.SOURCE_IMAGE_ASPECT_RATIO;
110 | } else {
111 | mAspectRatio = mAspectRatioX / mAspectRatioY;
112 | }
113 |
114 | mDotSize = getContext().getResources().getDimensionPixelSize(R.dimen.ucrop_size_dot_scale_text_view);
115 | mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
116 | mDotPaint.setStyle(Paint.Style.FILL);
117 |
118 | setTitle();
119 |
120 | int activeColor = getResources().getColor(R.color.ucrop_color_widget_active);
121 | applyActiveColor(activeColor);
122 |
123 | a.recycle();
124 | }
125 |
126 | private void applyActiveColor(@ColorInt int activeColor) {
127 | if (mDotPaint != null) {
128 | mDotPaint.setColor(activeColor);
129 | }
130 | ColorStateList textViewColorStateList = new ColorStateList(
131 | new int[][]{
132 | new int[]{android.R.attr.state_selected},
133 | new int[]{0}
134 | },
135 | new int[]{
136 | activeColor,
137 | ContextCompat.getColor(getContext(), R.color.ucrop_color_widget)
138 | }
139 | );
140 |
141 | setTextColor(textViewColorStateList);
142 | }
143 |
144 | private void toggleAspectRatio() {
145 | if (mAspectRatio != CropImageView.SOURCE_IMAGE_ASPECT_RATIO) {
146 | float tempRatioW = mAspectRatioX;
147 | mAspectRatioX = mAspectRatioY;
148 | mAspectRatioY = tempRatioW;
149 |
150 | mAspectRatio = mAspectRatioX / mAspectRatioY;
151 | }
152 | }
153 |
154 | private void setTitle() {
155 | if (!TextUtils.isEmpty(mAspectRatioTitle)) {
156 | setText(mAspectRatioTitle);
157 | } else {
158 | setText(String.format(Locale.US, "%d:%d", (int) mAspectRatioX, (int) mAspectRatioY));
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/ucrop/src/main/java/com/yalantis/ucrop/view/widget/HorizontalProgressWheelView.java:
--------------------------------------------------------------------------------
1 | package com.yalantis.ucrop.view.widget;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.graphics.Canvas;
6 | import android.graphics.Paint;
7 | import android.graphics.Rect;
8 | import android.os.Build;
9 | import android.util.AttributeSet;
10 | import android.view.MotionEvent;
11 | import android.view.View;
12 |
13 | import com.yalantis.ucrop.R;
14 |
15 | import androidx.annotation.ColorInt;
16 | import androidx.core.content.ContextCompat;
17 |
18 | /**
19 | * Created by Oleksii Shliama (https://github.com/shliama).
20 | */
21 | public class HorizontalProgressWheelView extends View {
22 |
23 | private final Rect mCanvasClipBounds = new Rect();
24 |
25 | private ScrollingListener mScrollingListener;
26 | private float mLastTouchedPosition;
27 |
28 | private Paint mProgressLinePaint;
29 | private Paint mProgressMiddleLinePaint;
30 | private int mProgressLineWidth, mProgressLineHeight;
31 | private int mProgressLineMargin;
32 |
33 | private boolean mScrollStarted;
34 | private float mTotalScrollDistance;
35 |
36 | private int mMiddleLineColor;
37 |
38 | public HorizontalProgressWheelView(Context context) {
39 | this(context, null);
40 | }
41 |
42 | public HorizontalProgressWheelView(Context context, AttributeSet attrs) {
43 | this(context, attrs, 0);
44 | }
45 |
46 | public HorizontalProgressWheelView(Context context, AttributeSet attrs, int defStyleAttr) {
47 | super(context, attrs, defStyleAttr);
48 | init();
49 | }
50 |
51 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
52 | public HorizontalProgressWheelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
53 | super(context, attrs, defStyleAttr, defStyleRes);
54 | }
55 |
56 | public void setScrollingListener(ScrollingListener scrollingListener) {
57 | mScrollingListener = scrollingListener;
58 | }
59 |
60 | public void setMiddleLineColor(@ColorInt int middleLineColor) {
61 | mMiddleLineColor = middleLineColor;
62 | mProgressMiddleLinePaint.setColor(mMiddleLineColor);
63 | invalidate();
64 | }
65 |
66 | @Override
67 | public boolean onTouchEvent(MotionEvent event) {
68 | switch (event.getAction()) {
69 | case MotionEvent.ACTION_DOWN:
70 | mLastTouchedPosition = event.getX();
71 | break;
72 | case MotionEvent.ACTION_UP:
73 | if (mScrollingListener != null) {
74 | mScrollStarted = false;
75 | mScrollingListener.onScrollEnd();
76 | }
77 | break;
78 | case MotionEvent.ACTION_MOVE:
79 | float distance = event.getX() - mLastTouchedPosition;
80 | if (distance != 0) {
81 | if (!mScrollStarted) {
82 | mScrollStarted = true;
83 | if (mScrollingListener != null) {
84 | mScrollingListener.onScrollStart();
85 | }
86 | }
87 | onScrollEvent(event, distance);
88 | }
89 | break;
90 | }
91 | return true;
92 | }
93 |
94 | @Override
95 | protected void onDraw(Canvas canvas) {
96 | super.onDraw(canvas);
97 | canvas.getClipBounds(mCanvasClipBounds);
98 |
99 | int linesCount = mCanvasClipBounds.width() / (mProgressLineWidth + mProgressLineMargin);
100 | float deltaX = (mTotalScrollDistance) % (float) (mProgressLineMargin + mProgressLineWidth);
101 |
102 | for (int i = 0; i < linesCount; i++) {
103 | if (i < (linesCount / 4)) {
104 | mProgressLinePaint.setAlpha((int) (255 * (i / (float) (linesCount / 4))));
105 | } else if (i > (linesCount * 3 / 4)) {
106 | mProgressLinePaint.setAlpha((int) (255 * ((linesCount - i) / (float) (linesCount / 4))));
107 | } else {
108 | mProgressLinePaint.setAlpha(255);
109 | }
110 | canvas.drawLine(
111 | -deltaX + mCanvasClipBounds.left + i * (mProgressLineWidth + mProgressLineMargin),
112 | mCanvasClipBounds.centerY() - mProgressLineHeight / 4.0f,
113 | -deltaX + mCanvasClipBounds.left + i * (mProgressLineWidth + mProgressLineMargin),
114 | mCanvasClipBounds.centerY() + mProgressLineHeight / 4.0f, mProgressLinePaint);
115 | }
116 |
117 | canvas.drawLine(mCanvasClipBounds.centerX(), mCanvasClipBounds.centerY() - mProgressLineHeight / 2.0f, mCanvasClipBounds.centerX(), mCanvasClipBounds.centerY() + mProgressLineHeight / 2.0f, mProgressMiddleLinePaint);
118 |
119 | }
120 |
121 | private void onScrollEvent(MotionEvent event, float distance) {
122 | mTotalScrollDistance -= distance;
123 | postInvalidate();
124 | mLastTouchedPosition = event.getX();
125 | if (mScrollingListener != null) {
126 | mScrollingListener.onScroll(-distance, mTotalScrollDistance);
127 | }
128 | }
129 |
130 | private void init() {
131 | mMiddleLineColor = ContextCompat.getColor(getContext(), R.color.ucrop_color_widget_rotate_mid_line);
132 |
133 | mProgressLineWidth = getContext().getResources().getDimensionPixelSize(R.dimen.ucrop_width_horizontal_wheel_progress_line);
134 | mProgressLineHeight = getContext().getResources().getDimensionPixelSize(R.dimen.ucrop_height_horizontal_wheel_progress_line);
135 | mProgressLineMargin = getContext().getResources().getDimensionPixelSize(R.dimen.ucrop_margin_horizontal_wheel_progress_line);
136 |
137 | mProgressLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
138 | mProgressLinePaint.setStyle(Paint.Style.STROKE);
139 | mProgressLinePaint.setStrokeWidth(mProgressLineWidth);
140 | mProgressLinePaint.setColor(getResources().getColor(R.color.ucrop_color_progress_wheel_line));
141 |
142 | mProgressMiddleLinePaint = new Paint(mProgressLinePaint);
143 | mProgressMiddleLinePaint.setColor(mMiddleLineColor);
144 | mProgressMiddleLinePaint.setStrokeCap(Paint.Cap.ROUND);
145 | mProgressMiddleLinePaint.setStrokeWidth(getContext().getResources().getDimensionPixelSize(R.dimen.ucrop_width_middle_wheel_progress_line));
146 | }
147 |
148 | public interface ScrollingListener {
149 |
150 | void onScrollStart();
151 |
152 | void onScroll(float delta, float totalDistance);
153 |
154 | void onScrollEnd();
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/ucrop/src/main/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH := $(call my-dir)
2 |
3 | include $(CLEAR_VARS)
4 |
5 | LOCAL_MODULE := ucrop
6 | LOCAL_SRC_FILES := uCrop.cpp
7 |
8 | LOCAL_LDLIBS := -landroid -llog -lz
9 | LOCAL_STATIC_LIBRARIES := libpng libjpeg_static
10 |
11 | include $(BUILD_SHARED_LIBRARY)
12 |
13 | $(call import-module,libpng)
14 | $(call import-module,libjpeg)
--------------------------------------------------------------------------------
/ucrop/src/main/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_STL := gnustl_static
2 | APP_ABI := armeabi armeabi-v7a x86 x86_64 arm64-v8a
3 | APP_CPPFLAGS += -frtti
4 | APP_CPPFLAGS += -fexceptions
5 | APP_CPPFLAGS += -DANDROID
6 | APP_PLATFORM := android-14
--------------------------------------------------------------------------------
/ucrop/src/main/jni/com_yalantis_ucrop_task_BitmapCropTask.h:
--------------------------------------------------------------------------------
1 | /* DO NOT EDIT THIS FILE - it is machine generated */
2 | #include
3 | /* Header for class com_yalantis_ucrop_task_BitmapCropTask */
4 |
5 | #ifndef _Included_com_yalantis_ucrop_task_BitmapCropTask
6 | #define _Included_com_yalantis_ucrop_task_BitmapCropTask
7 | #ifdef __cplusplus
8 | extern "C" {
9 | #endif
10 |
11 | /*
12 | * Class: com_yalantis_ucrop_task_BitmapCropTask
13 | * Method: cropCImg
14 | * Signature: (Ljava/lang/String;Ljava/lang/String;IIIIF)Z
15 | */
16 | JNIEXPORT jboolean JNICALL Java_com_yalantis_ucrop_task_BitmapCropTask_cropCImg
17 | (JNIEnv *, jobject, jstring, jstring, jint, jint, jint, jint, jfloat, jfloat, jint, jint, jint, jint);
18 |
19 | #ifdef __cplusplus
20 | }
21 | #endif
22 | #endif
23 |
--------------------------------------------------------------------------------
/ucrop/src/main/jni/uCrop.cpp:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Oleksii Shliama on 3/13/16.
3 | //
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "com_yalantis_ucrop_task_BitmapCropTask.h"
10 |
11 | using namespace std;
12 |
13 | #define cimg_display 0
14 | #define cimg_use_jpeg
15 | #define cimg_use_png
16 | #define cimg_use_openmp
17 |
18 | #include "CImg.h"
19 |
20 | using namespace cimg_library;
21 |
22 | #define LOG_TAG "uCrop JNI"
23 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
24 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
25 |
26 | #define SAVE_FORMAT_JPEG 0
27 | #define SAVE_FORMAT_PNG 1
28 |
29 | JNIEXPORT jboolean JNICALL Java_com_yalantis_ucrop_task_BitmapCropTask_cropCImg
30 | (JNIEnv *env, jobject obj,
31 | jstring pathSource, jstring pathResult,
32 | jint left, jint top, jint width, jint height, jfloat angle, jfloat resizeScale,
33 | jint format, jint quality,
34 | jint exifDegrees, jint exifTranslation) {
35 |
36 | LOGD("Crop image with CImg");
37 |
38 | const char *file_source_path = env->GetStringUTFChars(pathSource, 0);
39 | const char *file_result_path = env->GetStringUTFChars(pathResult, 0);
40 |
41 | try {
42 | CImg img(file_source_path);
43 | const int
44 | x0 = left, y0 = top,
45 | x1 = left + width - 1, y1 = top + height - 1;
46 |
47 | /*
48 | LOGD("left %d\ntop: %d", left, top);
49 | LOGD("width %d\nheight: %d", width, height);
50 | LOGD("angle %f\nresizeScale: %f", angle, resizeScale);
51 | LOGD("image size pre: %d x %d", img.width(), img.height());
52 | LOGD("exifDegrees: %d \nexifTranslation: %d", exifDegrees, exifTranslation);
53 | */
54 |
55 | // Handle exif. However it is slow, maybe calculate warp field according to exif rotation/translation.
56 | if (exifDegrees != 0) {
57 | img.rotate(exifDegrees);
58 | }
59 | if (exifTranslation != 1) {
60 | img.mirror("x");
61 | }
62 |
63 | const int
64 | size_x = img.width() * resizeScale, size_y = img.height() * resizeScale,
65 | size_z = -100, size_c = -100, interpolation_type = 1;
66 |
67 | const unsigned int boundary_conditions = 0;
68 | const float
69 | centering_x = 0, centering_y = 0, centering_z = 0, centering_c = 0;
70 | if (resizeScale != 1) {
71 | img.resize(size_x, size_y, size_z, size_c, interpolation_type, boundary_conditions, centering_x, centering_y, centering_z, centering_c);
72 | }
73 |
74 | // Create warp field.
75 | CImg warp(cimg::abs(x1 - x0 + 1), cimg::abs(y1 - y0 + 1), 1, 2);
76 |
77 | const float
78 | rad = angle * cimg::PI/180,
79 | ca = std::cos(rad), sa = std::sin(rad),
80 | ux = cimg::abs(img.width() * ca), uy = cimg::abs(img.width() * sa),
81 | vx = cimg::abs(img.height() * sa), vy = cimg::abs(img.height() * ca),
82 | w2 = 0.5f * img.width(), h2 = 0.5f * img.height(),
83 | dw2 = 0.5f * (ux + vx), dh2 = 0.5f * (uy + vy);
84 |
85 | cimg_forXY(warp, x, y) {
86 | const float
87 | u = x + x0 - dw2, v = y + y0 - dh2;
88 |
89 | warp(x, y, 0) = w2 + u*ca + v*sa;
90 | warp(x, y, 1) = h2 - u*sa + v*ca;
91 | }
92 |
93 | img = img.get_warp(warp, 0, 1, 2);
94 |
95 | if (format == SAVE_FORMAT_JPEG) {
96 | img.save_jpeg(file_result_path, quality);
97 | } else if (format == SAVE_FORMAT_PNG) {
98 | img.save_png(file_result_path, 0);
99 | } else {
100 | img.save(file_result_path);
101 | }
102 |
103 | ~img;
104 | env->ReleaseStringUTFChars(pathSource, file_source_path);
105 | env->ReleaseStringUTFChars(pathResult, file_result_path);
106 |
107 | return true;
108 |
109 | } catch (CImgInstanceException e) {
110 | env->ThrowNew(env->FindClass("java/lang/OutOfMemoryError"), e.what());
111 | } catch (CImgIOException e) {
112 | env->ThrowNew(env->FindClass("java/io/IOException"), e.what());
113 | }
114 |
115 | return false;
116 | }
--------------------------------------------------------------------------------
/ucrop/src/main/jniLibs/arm64-v8a/libucrop.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/jniLibs/arm64-v8a/libucrop.so
--------------------------------------------------------------------------------
/ucrop/src/main/jniLibs/armeabi-v7a/libucrop.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/jniLibs/armeabi-v7a/libucrop.so
--------------------------------------------------------------------------------
/ucrop/src/main/jniLibs/armeabi/libucrop.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/jniLibs/armeabi/libucrop.so
--------------------------------------------------------------------------------
/ucrop/src/main/jniLibs/x86/libucrop.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/jniLibs/x86/libucrop.so
--------------------------------------------------------------------------------
/ucrop/src/main/jniLibs/x86_64/libucrop.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/jniLibs/x86_64/libucrop.so
--------------------------------------------------------------------------------
/ucrop/src/main/res/anim/ucrop_loader_circle_path.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
19 |
20 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/anim/ucrop_loader_circle_scale.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
19 |
20 |
27 |
28 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/color/ucrop_scale_text_view_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-hdpi/ucrop_ic_angle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-hdpi/ucrop_ic_angle.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-hdpi/ucrop_ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-hdpi/ucrop_ic_done.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-ldpi/ucrop_ic_angle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-ldpi/ucrop_ic_angle.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-ldpi/ucrop_ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-ldpi/ucrop_ic_done.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-mdpi/ucrop_ic_angle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-mdpi/ucrop_ic_angle.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-mdpi/ucrop_ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-mdpi/ucrop_ic_done.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-xhdpi/ucrop_ic_angle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-xhdpi/ucrop_ic_angle.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-xhdpi/ucrop_ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-xhdpi/ucrop_ic_done.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-xxhdpi/ucrop_ic_angle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-xxhdpi/ucrop_ic_angle.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-xxhdpi/ucrop_ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-xxhdpi/ucrop_ic_done.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-xxxhdpi/ucrop_ic_angle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-xxxhdpi/ucrop_ic_angle.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable-xxxhdpi/ucrop_ic_done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Yalantis/uCrop/016672137e413a9462313b5d73ca6f736b8ad99f/ucrop/src/main/res/drawable-xxxhdpi/ucrop_ic_done.png
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_crop.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_crop.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_crop_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_cross.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_next.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_reset.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_rotate.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_rotate_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_scale.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_ic_scale_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_rotate.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_scale.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_shadow_upside.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_vector_ic_crop.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_vector_loader.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_vector_loader_animated.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/drawable/ucrop_wrapper_controls_shape.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/layout/ucrop_activity_photobox.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
23 |
24 |
25 |
26 |
33 |
34 |
42 |
43 |
48 |
49 |
50 |
51 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/layout/ucrop_aspect_ratio.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/layout/ucrop_controls.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
19 |
20 |
26 |
27 |
30 |
31 |
34 |
35 |
36 |
37 |
43 |
44 |
53 |
54 |
57 |
58 |
64 |
65 |
69 |
70 |
71 |
72 |
75 |
76 |
80 |
81 |
85 |
86 |
87 |
88 |
91 |
92 |
96 |
97 |
101 |
102 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/layout/ucrop_fragment_photobox.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
15 |
16 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/layout/ucrop_layout_rotate_wheel.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
25 |
26 |
31 |
32 |
35 |
36 |
37 |
38 |
45 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/layout/ucrop_layout_scale_wheel.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/layout/ucrop_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
13 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/menu/ucrop_menu_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Foto editieren
4 | Original
5 | Zuschneiden
6 |
7 | Rotieren
8 | Skalieren
9 | Zuschneiden
10 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-es/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Original
4 | Editar Foto
5 |
6 | Cortar
7 |
8 | Rotar
9 | Escalar
10 | Cortar
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-fa/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | اصلی
5 | ویرایش عکس
6 |
7 | برش
8 |
9 | چرخش
10 | اندازه
11 | برش
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-fi/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Muokkaa kuvaa
4 | Rajaa
5 | Sekä sisäänmeno- että ulostulo-Urit täytyy olla määritettyinä
6 |
7 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-fr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Original
3 | Modifier la photo
4 | Recadrer
5 |
6 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-in/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Asli
4 | Edit Foto
5 |
6 | Pangkas
7 |
8 | Memutar
9 | Skala
10 | Pangkas
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-it/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Originale
3 | Modifica foto
4 | Taglia
5 |
6 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | オリジナル
4 | 画像編集
5 |
6 | 切り抜き
7 |
8 | 拡大
9 | 回転
10 | 比率
11 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-ko/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 사진 편집
4 | 원본
5 | 확인
6 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-nl/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Origineel
3 | Foto bewerken
4 | Bijsnijden
5 |
6 | Draaien
7 | Schalen
8 | Bijsnijden
9 |
10 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-pt/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Original
4 | Editar Foto
5 |
6 | Cortar
7 |
8 | Girar
9 | Tamanho
10 | Cortar
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Оригинал
4 | Повернуть
5 | Масштабировать
6 | Обрезать
7 | Обрезать
8 | Редактировать фото
9 |
10 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-sk/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Pôvodná
4 | Upraviť fotografiu
5 | Orezať
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-th/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | เป็นต้นฉบับ
4 | แก้ไขรูปภาพ
5 |
6 | พืชผล
7 |
8 | หมุน
9 | ขนาด
10 | พืชผล
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-tr/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Orijinal
4 | Fotoğrafı Düzenle
5 |
6 | Kırp
7 |
8 | Döndürme
9 | Ölçek
10 | Kırp
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 原始比例
4 | 裁切
5 |
6 | 裁切
7 |
8 | 必須指定輸入以及輸出的 Uri
9 | 在你的 App 內覆寫顏色資源檔 (ucrop_color_toolbar_widget) 使 5.0 以前裝置正常運作
10 | 旋轉
11 | 縮放
12 | 裁切
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 原始比例
4 | 裁剪
5 |
6 | 裁剪
7 | 旋转
8 | 缩放
9 | 裁剪
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | #000
6 | #FF6300
7 | #20242F
8 | #B3BECE
9 | #FFF
10 |
11 |
12 | @color/ucrop_color_white
13 | #f5f5f5
14 | @color/ucrop_color_black
15 | @color/ucrop_color_black
16 | @color/ucrop_color_white
17 | @color/ucrop_color_blaze_orange
18 | @color/ucrop_color_heather
19 | @color/ucrop_color_blaze_orange
20 | @color/ucrop_color_ebony_clay
21 | @color/ucrop_color_blaze_orange
22 | @color/ucrop_color_ebony_clay
23 | @color/ucrop_color_ebony_clay
24 | @color/ucrop_color_blaze_orange
25 | @color/ucrop_color_ebony_clay
26 | @color/ucrop_color_black
27 |
28 |
29 | #80ffffff
30 | #ffffff
31 | #8c000000
32 | #4f212121
33 |
34 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 8dp
6 | 20dp
7 | 2dp
8 | 4dp
9 | 10dp
10 | 64dp
11 | 72dp
12 | 3dp
13 | 13sp
14 | 11sp
15 | 10dp
16 | 4dp
17 | 50dp
18 | 40dp
19 | 30dp
20 |
21 |
22 | 200dp
23 | 1dp
24 | 1dp
25 | 30dp
26 | 100dp
27 | 10dp
28 |
29 |
30 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values/public.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Original
4 | Edit Photo
5 |
6 | Crop
7 |
8 | Both input and output Uri must be specified
9 | Therefore, override color resource (ucrop_color_toolbar_widget) in your app to make it work on pre-L devices
10 | Rotate
11 | Scale
12 | Crop
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
21 |
22 |
28 |
29 |
36 |
37 |
46 |
47 |
56 |
57 |
--------------------------------------------------------------------------------
/ucrop/src/main/res/values/values.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1500
4 |
--------------------------------------------------------------------------------