├── settings.gradle
├── image
├── 1.gif
└── 2.gif
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── sample
├── src
│ └── main
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── drawable-hdpi
│ │ │ └── ic_image_white.png
│ │ ├── drawable-xhdpi
│ │ │ └── ic_image_white.png
│ │ ├── drawable-xxhdpi
│ │ │ └── ic_image_white.png
│ │ ├── drawable-xxxhdpi
│ │ │ └── ic_image_white.png
│ │ ├── values
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── drawable
│ │ │ └── decoration_white.xml
│ │ ├── layout
│ │ │ ├── item_main_image.xml
│ │ │ ├── content_main.xml
│ │ │ └── activity_main.xml
│ │ └── menu
│ │ │ └── menu_main.xml
│ │ ├── java
│ │ └── com
│ │ │ └── yanzhenjie
│ │ │ └── durban
│ │ │ └── sample
│ │ │ ├── Utils.java
│ │ │ ├── GridAdapter.java
│ │ │ └── MainActivity.java
│ │ └── AndroidManifest.xml
└── build.gradle
├── durban
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable-hdpi
│ │ │ ├── durban_ic_back_white.png
│ │ │ ├── durban_ic_done_white.png
│ │ │ ├── durban_ic_scale_in_white.png
│ │ │ ├── durban_ic_scale_out_white.png
│ │ │ ├── durban_ic_rotation_90_left_white.png
│ │ │ └── durban_ic_rotation_90_right_white.png
│ │ ├── drawable-xhdpi
│ │ │ ├── durban_ic_back_white.png
│ │ │ ├── durban_ic_done_white.png
│ │ │ ├── durban_ic_scale_in_white.png
│ │ │ ├── durban_ic_scale_out_white.png
│ │ │ ├── durban_ic_rotation_90_left_white.png
│ │ │ └── durban_ic_rotation_90_right_white.png
│ │ ├── drawable-xxhdpi
│ │ │ ├── durban_ic_back_white.png
│ │ │ ├── durban_ic_done_white.png
│ │ │ ├── durban_ic_scale_in_white.png
│ │ │ ├── durban_ic_scale_out_white.png
│ │ │ ├── durban_ic_rotation_90_left_white.png
│ │ │ └── durban_ic_rotation_90_right_white.png
│ │ ├── drawable-xxxhdpi
│ │ │ ├── durban_ic_back_white.png
│ │ │ ├── durban_ic_done_white.png
│ │ │ ├── durban_ic_scale_in_white.png
│ │ │ ├── durban_ic_scale_out_white.png
│ │ │ ├── durban_ic_rotation_90_left_white.png
│ │ │ └── durban_ic_rotation_90_right_white.png
│ │ ├── drawable
│ │ │ └── durban_vector_ic_crop_logo.xml
│ │ ├── values-zh
│ │ │ └── strings.xml
│ │ ├── values-zh-rHK
│ │ │ └── strings.xml
│ │ ├── values-zh-rTW
│ │ │ └── strings.xml
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── dimens.xml
│ │ │ ├── colors.xml
│ │ │ ├── attrs.xml
│ │ │ └── styles.xml
│ │ ├── menu
│ │ │ └── durban_menu_activity.xml
│ │ └── layout
│ │ │ ├── durban_crop_view.xml
│ │ │ └── durban_activity_photobox.xml
│ │ ├── java
│ │ └── com
│ │ │ └── yanzhenjie
│ │ │ └── durban
│ │ │ ├── callback
│ │ │ ├── CropBoundsChangeListener.java
│ │ │ ├── OverlayViewChangeListener.java
│ │ │ ├── BitmapCropCallback.java
│ │ │ └── BitmapLoadCallback.java
│ │ │ ├── error
│ │ │ └── StorageError.java
│ │ │ ├── util
│ │ │ ├── CubicEasing.java
│ │ │ ├── DurbanUtils.java
│ │ │ ├── FileUtils.java
│ │ │ ├── FastBitmapDrawable.java
│ │ │ ├── RectUtils.java
│ │ │ ├── RotationGestureDetector.java
│ │ │ ├── BitmapLoadUtils.java
│ │ │ ├── EglUtils.java
│ │ │ └── ImageHeaderParser.java
│ │ │ ├── model
│ │ │ ├── ImageState.java
│ │ │ ├── CropParameters.java
│ │ │ ├── ExifInfo.java
│ │ │ └── AspectRatio.java
│ │ │ ├── DurbanConfig.java
│ │ │ ├── view
│ │ │ ├── CropView.java
│ │ │ ├── GestureCropImageView.java
│ │ │ └── TransformImageView.java
│ │ │ ├── Controller.java
│ │ │ ├── task
│ │ │ ├── BitmapLoadTask.java
│ │ │ └── BitmapCropTask.java
│ │ │ ├── Durban.java
│ │ │ └── DurbanActivity.java
│ │ └── AndroidManifest.xml
└── build.gradle
├── gradle.properties
├── config.gradle
├── gradlew.bat
├── maven.gradle
├── README-CN.md
├── README.md
└── gradlew
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':durban', ':sample'
--------------------------------------------------------------------------------
/image/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/image/1.gif
--------------------------------------------------------------------------------
/image/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/image/2.gif
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | local.properties
2 | *.idea
3 | *.iml
4 | build
5 | captures
6 | .gradle
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_image_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/drawable-hdpi/ic_image_white.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xhdpi/ic_image_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/drawable-xhdpi/ic_image_white.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxhdpi/ic_image_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/drawable-xxhdpi/ic_image_white.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-xxxhdpi/ic_image_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/drawable-xxxhdpi/ic_image_white.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-hdpi/durban_ic_back_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-hdpi/durban_ic_back_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-hdpi/durban_ic_done_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-hdpi/durban_ic_done_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xhdpi/durban_ic_back_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xhdpi/durban_ic_back_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xhdpi/durban_ic_done_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xhdpi/durban_ic_done_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxhdpi/durban_ic_back_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxhdpi/durban_ic_back_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxhdpi/durban_ic_done_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxhdpi/durban_ic_done_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-hdpi/durban_ic_scale_in_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-hdpi/durban_ic_scale_in_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxxhdpi/durban_ic_back_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxxhdpi/durban_ic_back_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxxhdpi/durban_ic_done_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxxhdpi/durban_ic_done_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-hdpi/durban_ic_scale_out_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-hdpi/durban_ic_scale_out_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xhdpi/durban_ic_scale_in_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xhdpi/durban_ic_scale_in_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xhdpi/durban_ic_scale_out_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xhdpi/durban_ic_scale_out_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxhdpi/durban_ic_scale_in_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxhdpi/durban_ic_scale_in_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxhdpi/durban_ic_scale_out_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxhdpi/durban_ic_scale_out_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxxhdpi/durban_ic_scale_in_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxxhdpi/durban_ic_scale_in_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxxhdpi/durban_ic_scale_out_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxxhdpi/durban_ic_scale_out_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-hdpi/durban_ic_rotation_90_left_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-hdpi/durban_ic_rotation_90_left_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-hdpi/durban_ic_rotation_90_right_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-hdpi/durban_ic_rotation_90_right_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xhdpi/durban_ic_rotation_90_left_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xhdpi/durban_ic_rotation_90_left_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xhdpi/durban_ic_rotation_90_right_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xhdpi/durban_ic_rotation_90_right_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxhdpi/durban_ic_rotation_90_left_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxhdpi/durban_ic_rotation_90_left_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxhdpi/durban_ic_rotation_90_right_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxhdpi/durban_ic_rotation_90_right_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxxhdpi/durban_ic_rotation_90_left_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxxhdpi/durban_ic_rotation_90_left_white.png
--------------------------------------------------------------------------------
/durban/src/main/res/drawable-xxxhdpi/durban_ic_rotation_90_right_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yanzhenjie/Durban/HEAD/durban/src/main/res/drawable-xxxhdpi/durban_ic_rotation_90_right_white.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 15 16:17:48 EET 2017
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-3.5-all.zip
7 |
--------------------------------------------------------------------------------
/durban/src/main/res/drawable/durban_vector_ic_crop_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | 5dp
19 |
--------------------------------------------------------------------------------
/durban/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: rootProject.ext.plugins.library
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.android.compileSdkVersion
5 | buildToolsVersion rootProject.ext.android.buildToolsVersion
6 |
7 | defaultConfig {
8 | minSdkVersion rootProject.ext.android.libraryMinSdkVersion
9 | targetSdkVersion rootProject.ext.android.targetSdkVersion
10 |
11 | vectorDrawables.useSupportLibrary = true
12 | }
13 |
14 | compileOptions {
15 | sourceCompatibility JavaVersion.VERSION_1_7
16 | targetCompatibility JavaVersion.VERSION_1_7
17 | }
18 |
19 | resourcePrefix 'durban_'
20 | }
21 |
22 | dependencies {
23 | compile rootProject.ext.dependencies.appcompat
24 | compile rootProject.ext.dependencies.loading
25 | }
26 |
27 | apply from: "../maven.gradle"
28 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | Durban
19 | Album
20 |
21 |
--------------------------------------------------------------------------------
/sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: rootProject.ext.plugins.application
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.android.compileSdkVersion
5 | buildToolsVersion rootProject.ext.android.buildToolsVersion
6 |
7 | defaultConfig {
8 | applicationId rootProject.ext.android.applicationId
9 | minSdkVersion rootProject.ext.android.sampleMinSdkVersion
10 | targetSdkVersion rootProject.ext.android.targetSdkVersion
11 | versionCode rootProject.ext.android.versionCode
12 | versionName rootProject.ext.android.versionName
13 | }
14 | }
15 |
16 | dependencies {
17 | compile fileTree(dir: 'libs', include: ['*.jar'])
18 | compile rootProject.ext.dependencies.appcompat
19 | compile rootProject.ext.dependencies.design
20 | compile rootProject.ext.dependencies.album
21 | compile rootProject.ext.dependencies.durban
22 | }
23 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/callback/CropBoundsChangeListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.callback;
17 |
18 |
19 | /**
20 | * Update by Yan Zhenjie on 2017/5/23.
21 | */
22 | public interface CropBoundsChangeListener {
23 |
24 | void onCropAspectRatioChanged(float cropRatio);
25 |
26 | }
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/callback/OverlayViewChangeListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.callback;
17 |
18 | import android.graphics.RectF;
19 |
20 | /**
21 | * Update by Yan Zhenjie on 2017/5/23.
22 | */
23 | public interface OverlayViewChangeListener {
24 |
25 | void onCropRectUpdated(RectF cropRect);
26 |
27 | }
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/decoration_white.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
24 |
25 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/item_main_image.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/durban/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 图片裁剪
20 | 确定
21 |
22 | 旋转
23 | 缩放
24 |
25 |
26 |
--------------------------------------------------------------------------------
/durban/src/main/res/values-zh-rHK/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 图片裁剪
20 | 确定
21 |
22 | 旋轉
23 | 縮放
24 |
25 |
26 |
--------------------------------------------------------------------------------
/durban/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 图片裁剪
20 | 确定
21 |
22 | 旋轉
23 | 縮放
24 |
25 |
26 |
--------------------------------------------------------------------------------
/durban/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | ImageCrop
20 | OK
21 |
22 | Rotation
23 | Scale
24 |
25 |
26 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 | #2196F3
19 | #1E88E5
20 | @color/colorPrimary
21 | #FF2B2B2B
22 |
23 | #FFFFFFFF
24 |
25 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/error/StorageError.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.error;
17 |
18 | /**
19 | * Created by Yan Zhenjie on 2017/5/23.
20 | */
21 | public class StorageError extends Exception {
22 |
23 | public StorageError(String message) {
24 | super(message);
25 | }
26 |
27 | public StorageError(Throwable cause) {
28 | super(cause);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/callback/BitmapCropCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.callback;
17 |
18 | import android.support.annotation.NonNull;
19 |
20 | /**
21 | * Update by Yan Zhenjie on 2017/5/23.
22 | */
23 | public interface BitmapCropCallback {
24 |
25 | void onBitmapCropped(@NonNull String imagePath, int imageWidth, int imageHeight);
26 |
27 | void onCropFailure(@NonNull Throwable t);
28 | }
--------------------------------------------------------------------------------
/durban/src/main/res/menu/durban_menu_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/durban/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 1dp
20 | 10dp
21 | 16dp
22 | 30dp
23 | 70dp
24 | 100dp
25 | 200dp
26 |
27 |
28 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/callback/BitmapLoadCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.callback;
17 |
18 | import android.graphics.Bitmap;
19 | import android.support.annotation.NonNull;
20 |
21 | import com.yanzhenjie.durban.model.ExifInfo;
22 |
23 |
24 | /**
25 | * Update by Yan Zhenjie on 2017/5/23.
26 | */
27 | public interface BitmapLoadCallback {
28 |
29 | void onSuccessfully(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo);
30 |
31 | void onFailure();
32 |
33 | }
--------------------------------------------------------------------------------
/sample/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
30 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/CubicEasing.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | /**
19 | * Update by Yan Zhenjie on 2017/5/23.
20 | */
21 | public final class CubicEasing {
22 |
23 | public static float easeOut(float time, float start, float end, float duration) {
24 | return end * ((time = time / duration - 1.0f) * time * time + 1.0f) + start;
25 | }
26 |
27 | public static float easeInOut(float time, float start, float end, float duration) {
28 | return (time /= duration / 2.0f) < 1.0f ? end / 2.0f * time * time * time + start : end / 2.0f * ((time -= 2.0f) * time
29 | * time + 2.0f) + start;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/durban/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/durban/src/main/res/layout/durban_crop_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
27 |
28 |
33 |
34 |
--------------------------------------------------------------------------------
/durban/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #2196F3
20 | #1E88E5
21 | #FF2B2B2B
22 |
23 | #FFFFFFFF
24 |
25 | @color/durban_ColorPrimaryBlack
26 |
27 | #99FFFFFF
28 | @color/durban_ColorPrimaryBlack
29 | #8C000000
30 | #80FFFFFF
31 | #FFFFFFFF
32 |
33 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
26 |
27 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/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.0-native
21 | VERSION_CODE=22
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
--------------------------------------------------------------------------------
/sample/src/main/java/com/yanzhenjie/durban/sample/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.sample;
17 |
18 | import android.content.Context;
19 | import android.os.Environment;
20 |
21 | import java.io.File;
22 |
23 | /**
24 | * Created by Yan Zhenjie on 2017/5/23.
25 | */
26 | public class Utils {
27 |
28 | public static File getAppRootPath(Context context) {
29 | if (sdCardIsAvailable()) {
30 | return Environment.getExternalStorageDirectory();
31 | } else {
32 | return context.getFilesDir();
33 | }
34 | }
35 |
36 | public static boolean sdCardIsAvailable() {
37 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
38 | return Environment.getExternalStorageDirectory().canWrite();
39 | } else
40 | return false;
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/model/ImageState.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.model;
17 |
18 | import android.graphics.RectF;
19 |
20 | /**
21 | * Update by Yan Zhenjie on 2017/5/23.
22 | */
23 | public class ImageState {
24 |
25 | private RectF mCropRect;
26 | private RectF mCurrentImageRect;
27 |
28 | private float mCurrentScale, mCurrentAngle;
29 |
30 | public ImageState(RectF cropRect, RectF currentImageRect, float currentScale, float currentAngle) {
31 | mCropRect = cropRect;
32 | mCurrentImageRect = currentImageRect;
33 | mCurrentScale = currentScale;
34 | mCurrentAngle = currentAngle;
35 | }
36 |
37 | public RectF getCropRect() {
38 | return mCropRect;
39 | }
40 |
41 | public RectF getCurrentImageRect() {
42 | return mCurrentImageRect;
43 | }
44 |
45 | public float getCurrentScale() {
46 | return mCurrentScale;
47 | }
48 |
49 | public float getCurrentAngle() {
50 | return mCurrentAngle;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/DurbanUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | import android.content.Context;
19 | import android.content.res.Configuration;
20 | import android.content.res.Resources;
21 | import android.os.Build;
22 |
23 | import java.util.Locale;
24 |
25 | /**
26 | * Created by Yan Zhenjie on 2017/5/30.
27 | */
28 | public class DurbanUtils {
29 |
30 | /**
31 | * Setting {@link Locale} for {@link Context}.
32 | */
33 | public static Context applyLanguageForContext(Context context, Locale locale) {
34 | Resources resources = context.getResources();
35 | Configuration config = resources.getConfiguration();
36 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
37 | config.setLocale(locale);
38 | return context.createConfigurationContext(config);
39 | } else {
40 | config.locale = locale;
41 | resources.updateConfiguration(config, resources.getDisplayMetrics());
42 | return context;
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/durban/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
24 |
25 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 |
21 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/config.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | plugins = [
3 | application: 'com.android.application',
4 | library : 'com.android.library',
5 | maven : 'com.github.dcendents.android-maven',
6 | bintray : 'com.jfrog.bintray'
7 | ]
8 |
9 | android = [
10 | applicationId : "com.yanzhenjie.durban.sample",
11 | compileSdkVersion : 25,
12 | buildToolsVersion : "25.0.3",
13 | libraryMinSdkVersion: 11,
14 | sampleMinSdkVersion : 11,
15 | targetSdkVersion : 25,
16 | versionCode : 2,
17 | versionName : "1.0.1"
18 | ]
19 |
20 | maven = [
21 | version : "1.0.1",
22 |
23 | siteUrl : 'https://github.com/yanzhenjie/Durban',
24 | gitUrl : 'git@github.com:yanzhenjie/Durban.git',
25 |
26 | group : "com.yanzhenjie",
27 |
28 | // project
29 | packaging : 'aar',
30 | name : 'Durban',
31 | description : 'Durban Crop for Android',
32 |
33 | // project.license
34 | licenseName : 'The Apache Software License, Version 2.0',
35 | licenseUrl : 'http://www.apache.org/licenses/LICENSE-2.0.txt',
36 |
37 | // project.developers
38 | developerId : 'yanzhenjie',
39 | developerName : 'yanzhenjie',
40 | developerEmail: 'smallajax@foxmail.com',
41 |
42 | // bintray
43 | binrayLibrary : "Durban",
44 | bintrayRepo : "maven",
45 | bintrayUser : 'yolanda',
46 | bintrayLicense: "Apache-2.0"
47 | ]
48 |
49 |
50 | dependencies = [
51 | appcompat: 'com.android.support:appcompat-v7:25.3.1',
52 | loading : 'com.yanzhenjie:loading:1.0.0',
53 |
54 | design : 'com.android.support:design:25.3.1',
55 | album : 'com.yanzhenjie:album:1.0.6',
56 | durban : 'com.yanzhenjie:durban:1.0.1',
57 | ]
58 | }
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/DurbanConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie. All Rights Reserved
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.yanzhenjie.durban;
17 |
18 | import android.content.Context;
19 |
20 | import java.util.Locale;
21 |
22 | /**
23 | * Durban config.
24 | * Created by Yan Zhenjie on 2017/5/30.
25 | */
26 | public class DurbanConfig {
27 |
28 | /**
29 | * Create a new builder.
30 | */
31 | public static Builder newBuilder(Context context) {
32 | return new Builder(context);
33 | }
34 |
35 | private Locale mLocale;
36 |
37 | private DurbanConfig(Builder build) {
38 | this.mLocale = build.mLocale;
39 | }
40 |
41 | /**
42 | * Get {@link Locale}.
43 | *
44 | * @return {@link Locale}.
45 | */
46 | public Locale getLocale() {
47 | return mLocale;
48 | }
49 |
50 | public static final class Builder {
51 |
52 | private Locale mLocale;
53 |
54 | private Builder(Context context) {
55 | }
56 |
57 | /**
58 | * Set locale for language.
59 | *
60 | * @param locale {@link Locale}.
61 | * @return {@link Builder}.
62 | */
63 | public Builder setLocale(Locale locale) {
64 | this.mLocale = locale;
65 | return this;
66 | }
67 |
68 | /**
69 | * Create AlbumConfig.
70 | *
71 | * @return {@link DurbanConfig}.
72 | */
73 | public DurbanConfig build() {
74 | return new DurbanConfig(this);
75 | }
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/model/CropParameters.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.model;
17 |
18 | import android.graphics.Bitmap;
19 |
20 | /**
21 | * Update by Yan Zhenjie on 2017/5/23.
22 | */
23 | public class CropParameters {
24 |
25 | private int mMaxResultImageSizeX, mMaxResultImageSizeY;
26 |
27 | private Bitmap.CompressFormat mCompressFormat;
28 | private int mCompressQuality;
29 | private String mImagePath;
30 | private String mImageOutputPath;
31 | private ExifInfo mExifInfo;
32 |
33 |
34 | public CropParameters(
35 | int maxResultImageSizeX,
36 | int maxResultImageSizeY,
37 | Bitmap.CompressFormat compressFormat,
38 | int compressQuality,
39 | String imagePath,
40 | String imageOutputPath,
41 | ExifInfo exifInfo) {
42 | mMaxResultImageSizeX = maxResultImageSizeX;
43 | mMaxResultImageSizeY = maxResultImageSizeY;
44 | mCompressFormat = compressFormat;
45 | mCompressQuality = compressQuality;
46 | this.mImagePath = imagePath;
47 | mImageOutputPath = imageOutputPath;
48 | mExifInfo = exifInfo;
49 | }
50 |
51 | public int getMaxResultImageSizeX() {
52 | return mMaxResultImageSizeX;
53 | }
54 |
55 | public int getMaxResultImageSizeY() {
56 | return mMaxResultImageSizeY;
57 | }
58 |
59 | public Bitmap.CompressFormat getCompressFormat() {
60 | return mCompressFormat;
61 | }
62 |
63 | public int getCompressQuality() {
64 | return mCompressQuality;
65 | }
66 |
67 | public String getImagePath() {
68 | return mImagePath;
69 | }
70 |
71 | public String getImageOutputPath() {
72 | return mImageOutputPath;
73 | }
74 |
75 | public ExifInfo getExifInfo() {
76 | return mExifInfo;
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/model/ExifInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.model;
17 |
18 | /**
19 | * Update by Yan Zhenjie on 2017/5/23.
20 | */
21 | public class ExifInfo {
22 |
23 | private int mExifOrientation;
24 | private int mExifDegrees;
25 | private int mExifTranslation;
26 |
27 | public ExifInfo(int exifOrientation, int exifDegrees, int exifTranslation) {
28 | mExifOrientation = exifOrientation;
29 | mExifDegrees = exifDegrees;
30 | mExifTranslation = exifTranslation;
31 | }
32 |
33 | public int getExifOrientation() {
34 | return mExifOrientation;
35 | }
36 |
37 | public int getExifDegrees() {
38 | return mExifDegrees;
39 | }
40 |
41 | public int getExifTranslation() {
42 | return mExifTranslation;
43 | }
44 |
45 | public void setExifOrientation(int exifOrientation) {
46 | mExifOrientation = exifOrientation;
47 | }
48 |
49 | public void setExifDegrees(int exifDegrees) {
50 | mExifDegrees = exifDegrees;
51 | }
52 |
53 | public void setExifTranslation(int exifTranslation) {
54 | mExifTranslation = exifTranslation;
55 | }
56 |
57 | @Override
58 | public boolean equals(Object o) {
59 | if (this == o) return true;
60 | if (o == null || getClass() != o.getClass()) return false;
61 |
62 | ExifInfo exifInfo = (ExifInfo) o;
63 |
64 | if (mExifOrientation != exifInfo.mExifOrientation) return false;
65 | if (mExifDegrees != exifInfo.mExifDegrees) return false;
66 | return mExifTranslation == exifInfo.mExifTranslation;
67 |
68 | }
69 |
70 | @Override
71 | public int hashCode() {
72 | int result = mExifOrientation;
73 | result = 31 * result + mExifDegrees;
74 | result = 31 * result + mExifTranslation;
75 | return result;
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/model/AspectRatio.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.model;
17 |
18 | import android.os.Parcel;
19 | import android.os.Parcelable;
20 | import android.support.annotation.Nullable;
21 |
22 | /**
23 | * Update by Yan Zhenjie on 2017/5/23.
24 | */
25 | public class AspectRatio implements Parcelable {
26 |
27 | @Nullable
28 | private final String mAspectRatioTitle;
29 | private final float mAspectRatioX;
30 | private final float mAspectRatioY;
31 |
32 | public AspectRatio(@Nullable String aspectRatioTitle, float aspectRatioX, float aspectRatioY) {
33 | mAspectRatioTitle = aspectRatioTitle;
34 | mAspectRatioX = aspectRatioX;
35 | mAspectRatioY = aspectRatioY;
36 | }
37 |
38 | protected AspectRatio(Parcel in) {
39 | mAspectRatioTitle = in.readString();
40 | mAspectRatioX = in.readFloat();
41 | mAspectRatioY = in.readFloat();
42 | }
43 |
44 | @Override
45 | public void writeToParcel(Parcel dest, int flags) {
46 | dest.writeString(mAspectRatioTitle);
47 | dest.writeFloat(mAspectRatioX);
48 | dest.writeFloat(mAspectRatioY);
49 | }
50 |
51 | @Override
52 | public int describeContents() {
53 | return 0;
54 | }
55 |
56 | public static final Creator CREATOR = new Creator() {
57 | @Override
58 | public AspectRatio createFromParcel(Parcel in) {
59 | return new AspectRatio(in);
60 | }
61 |
62 | @Override
63 | public AspectRatio[] newArray(int size) {
64 | return new AspectRatio[size];
65 | }
66 | };
67 |
68 | @Nullable
69 | public String getAspectRatioTitle() {
70 | return mAspectRatioTitle;
71 | }
72 |
73 | public float getAspectRatioX() {
74 | return mAspectRatioX;
75 | }
76 |
77 | public float getAspectRatioY() {
78 | return mAspectRatioY;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/maven.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: rootProject.ext.plugins.maven
2 | apply plugin: rootProject.ext.plugins.bintray
3 |
4 | version = rootProject.ext.maven.version
5 |
6 | def siteUrl = rootProject.ext.maven.siteUrl
7 | def gitUrl = rootProject.ext.maven.gitUrl
8 |
9 | group = rootProject.ext.maven.group
10 |
11 | install {
12 | repositories.mavenInstaller {
13 | pom {
14 | project {
15 | packaging rootProject.ext.maven.packaging
16 | name rootProject.ext.maven.name
17 | description rootProject.ext.maven.description
18 | url siteUrl
19 | licenses {
20 | license {
21 | name rootProject.ext.maven.licenseName
22 | url rootProject.ext.maven.licenseUrl
23 | }
24 | }
25 | developers {
26 | developer {
27 | id rootProject.ext.maven.developerId
28 | name rootProject.ext.maven.developerName
29 | email rootProject.ext.maven.developerEmail
30 | }
31 | }
32 | scm {
33 | connection gitUrl
34 | developerConnection gitUrl
35 | url siteUrl
36 | }
37 | }
38 | }
39 | }
40 | }
41 | task sourcesJar(type: Jar) {
42 | from android.sourceSets.main.java.srcDirs
43 | classifier = 'sources'
44 | }
45 | task javadoc(type: Javadoc) {
46 | source = android.sourceSets.main.java.srcDirs
47 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
48 | failOnError false
49 | }
50 | task javadocJar(type: Jar, dependsOn: javadoc) {
51 | classifier = 'javadoc'
52 | //noinspection GroovyAccessibility
53 | from javadoc.destinationDir
54 | }
55 | artifacts {
56 | archives javadocJar
57 | archives sourcesJar
58 | }
59 | Properties properties = new Properties()
60 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
61 | bintray {
62 | user = rootProject.ext.maven.bintrayUser
63 | key = properties.getProperty("bintray.apikey")
64 |
65 | configurations = ['archives']
66 | pkg {
67 | repo = rootProject.ext.maven.bintrayRepo
68 | name = rootProject.ext.maven.binrayLibrary
69 | userOrg = rootProject.ext.maven.bintrayUser
70 | websiteUrl = siteUrl
71 | vcsUrl = gitUrl
72 | licenses = [rootProject.ext.maven.bintrayLicense]
73 | publish = true
74 | version {
75 | gpg {
76 | sign = true
77 | passphrase = properties.getProperty("bintray.gpg.password")
78 | }
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/FileUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | import android.graphics.Bitmap;
19 | import android.support.annotation.NonNull;
20 |
21 | import com.yanzhenjie.durban.error.StorageError;
22 |
23 | import java.io.Closeable;
24 | import java.io.File;
25 | import java.io.FileInputStream;
26 | import java.io.FileOutputStream;
27 | import java.io.IOException;
28 | import java.text.SimpleDateFormat;
29 | import java.util.Date;
30 | import java.util.Locale;
31 | import java.util.Random;
32 |
33 | /**
34 | * Create by Yan Zhenjie on 2017/5/23.
35 | */
36 | public class FileUtils {
37 |
38 | private static Random random = new Random();
39 |
40 | private FileUtils() {
41 | }
42 |
43 | public static void validateDirectory(String path) throws StorageError {
44 | File file = new File(path);
45 | try {
46 | if (file.isFile())
47 | file.delete();
48 | if (!file.exists())
49 | file.mkdirs();
50 | } catch (Exception e) {
51 | throw new StorageError("Directory creation failed.");
52 | }
53 | }
54 |
55 | public static String randomImageName(Bitmap.CompressFormat format) {
56 | SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd_HHmmSSS", Locale.getDefault());
57 | Date curDate = new Date(System.currentTimeMillis());
58 | return formatter.format(curDate) + random.nextInt(9000) + "." + format;
59 | }
60 |
61 | public static void copyFile(@NonNull String pathFrom, @NonNull String pathTo) throws StorageError {
62 | try {
63 | FileInputStream inputStream = new FileInputStream(pathFrom);
64 | FileOutputStream outputStream = new FileOutputStream(pathTo);
65 | int len;
66 | byte[] buffer = new byte[2048];
67 | while ((len = inputStream.read(buffer)) != -1)
68 | outputStream.write(buffer, 0, len);
69 | } catch (IOException e) {
70 | throw new StorageError(e);
71 | }
72 | }
73 |
74 | public static void close(Closeable c) {
75 | if (c != null) {
76 | try {
77 | c.close();
78 | } catch (IOException ignored) {
79 | }
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/FastBitmapDrawable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.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 | /**
26 | * Update by Yan Zhenjie on 2017/5/23.
27 | */
28 | public class FastBitmapDrawable extends Drawable {
29 |
30 | private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
31 |
32 | private Bitmap mBitmap;
33 | private int mAlpha;
34 | private int mWidth, mHeight;
35 |
36 | public FastBitmapDrawable(Bitmap b) {
37 | mAlpha = 255;
38 | setBitmap(b);
39 | }
40 |
41 | @Override
42 | public void draw(Canvas canvas) {
43 | if (mBitmap != null && !mBitmap.isRecycled()) {
44 | canvas.drawBitmap(mBitmap, null, getBounds(), mPaint);
45 | }
46 | }
47 |
48 | @Override
49 | public void setColorFilter(ColorFilter cf) {
50 | mPaint.setColorFilter(cf);
51 | }
52 |
53 | @Override
54 | public int getOpacity() {
55 | return PixelFormat.TRANSLUCENT;
56 | }
57 |
58 | public void setFilterBitmap(boolean filterBitmap) {
59 | mPaint.setFilterBitmap(filterBitmap);
60 | }
61 |
62 | public int getAlpha() {
63 | return mAlpha;
64 | }
65 |
66 | @Override
67 | public void setAlpha(int alpha) {
68 | mAlpha = alpha;
69 | mPaint.setAlpha(alpha);
70 | }
71 |
72 | @Override
73 | public int getIntrinsicWidth() {
74 | return mWidth;
75 | }
76 |
77 | @Override
78 | public int getIntrinsicHeight() {
79 | return mHeight;
80 | }
81 |
82 | @Override
83 | public int getMinimumWidth() {
84 | return mWidth;
85 | }
86 |
87 | @Override
88 | public int getMinimumHeight() {
89 | return mHeight;
90 | }
91 |
92 | public Bitmap getBitmap() {
93 | return mBitmap;
94 | }
95 |
96 | public void setBitmap(Bitmap b) {
97 | mBitmap = b;
98 | if (b != null) {
99 | mWidth = mBitmap.getWidth();
100 | mHeight = mBitmap.getHeight();
101 | } else {
102 | mWidth = mHeight = 0;
103 | }
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/yanzhenjie/durban/sample/GridAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.sample;
17 |
18 | import android.content.Context;
19 | import android.support.v7.widget.RecyclerView;
20 | import android.view.LayoutInflater;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.widget.ImageView;
24 |
25 | import com.yanzhenjie.album.Album;
26 |
27 | import java.util.List;
28 |
29 | /**
30 | * Image adapter.
31 | * Created by Yan Zhenjie on 2016/10/30.
32 | */
33 | public class GridAdapter extends RecyclerView.Adapter {
34 |
35 | private LayoutInflater mInflater;
36 | private int itemSize;
37 | private List mImagePathList;
38 |
39 | public GridAdapter(Context context, int itemSize) {
40 | this.mInflater = LayoutInflater.from(context);
41 | this.itemSize = itemSize;
42 | }
43 |
44 | public void notifyDataSetChanged(List imagePathList) {
45 | this.mImagePathList = imagePathList;
46 | super.notifyDataSetChanged();
47 | }
48 |
49 | @Override
50 | public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
51 | return new ImageViewHolder(itemSize, mInflater.inflate(R.layout.item_main_image, parent, false));
52 | }
53 |
54 | @Override
55 | public void onBindViewHolder(ImageViewHolder holder, int position) {
56 | holder.loadImage(mImagePathList.get(holder.getAdapterPosition()));
57 | }
58 |
59 | @Override
60 | public int getItemCount() {
61 | return mImagePathList == null ? 0 : mImagePathList.size();
62 | }
63 |
64 | static class ImageViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
65 |
66 | private int itemSize;
67 | private ImageView mIvIcon;
68 |
69 | ImageViewHolder(int itemSize, View itemView) {
70 | super(itemView);
71 | itemView.setOnClickListener(this);
72 | this.itemSize = itemSize;
73 | itemView.getLayoutParams().height = itemSize;
74 | itemView.requestLayout();
75 | mIvIcon = (ImageView) itemView.findViewById(R.id.iv_icon);
76 | }
77 |
78 | public void loadImage(String imagePath) {
79 | Album.getAlbumConfig().getImageLoader().loadImage(mIvIcon, imagePath, itemSize, itemSize);
80 | }
81 |
82 | @Override
83 | public void onClick(View v) {
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/view/CropView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.view;
17 |
18 | import android.content.Context;
19 | import android.content.res.TypedArray;
20 | import android.graphics.RectF;
21 | import android.support.annotation.NonNull;
22 | import android.util.AttributeSet;
23 | import android.view.LayoutInflater;
24 | import android.widget.FrameLayout;
25 |
26 | import com.yanzhenjie.durban.R;
27 | import com.yanzhenjie.durban.callback.CropBoundsChangeListener;
28 | import com.yanzhenjie.durban.callback.OverlayViewChangeListener;
29 |
30 | /**
31 | * Update by Yan Zhenjie on 2017/5/23.
32 | */
33 | public class CropView extends FrameLayout {
34 |
35 | private GestureCropImageView mGestureCropImageView;
36 | private final OverlayView mViewOverlay;
37 |
38 | public CropView(Context context, AttributeSet attrs) {
39 | this(context, attrs, 0);
40 | }
41 |
42 | public CropView(Context context, AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 |
45 | LayoutInflater.from(context).inflate(R.layout.durban_crop_view, this, true);
46 | mGestureCropImageView = (GestureCropImageView) findViewById(R.id.image_view_crop);
47 | mViewOverlay = (OverlayView) findViewById(R.id.view_overlay);
48 |
49 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.durban_CropView);
50 | mViewOverlay.processStyledAttributes(a);
51 | mGestureCropImageView.processStyledAttributes(a);
52 | a.recycle();
53 |
54 | setListenersToViews();
55 | }
56 |
57 | private void setListenersToViews() {
58 | mGestureCropImageView.setCropBoundsChangeListener(new CropBoundsChangeListener() {
59 | @Override
60 | public void onCropAspectRatioChanged(float cropRatio) {
61 | mViewOverlay.setTargetAspectRatio(cropRatio);
62 | }
63 | });
64 | mViewOverlay.setOverlayViewChangeListener(new OverlayViewChangeListener() {
65 | @Override
66 | public void onCropRectUpdated(RectF cropRect) {
67 | mGestureCropImageView.setCropRect(cropRect);
68 | }
69 | });
70 | }
71 |
72 | @Override
73 | public boolean shouldDelayChildPressedState() {
74 | return false;
75 | }
76 |
77 | @NonNull
78 | public GestureCropImageView getCropImageView() {
79 | return mGestureCropImageView;
80 | }
81 |
82 | @NonNull
83 | public OverlayView getOverlayView() {
84 | return mViewOverlay;
85 | }
86 | }
--------------------------------------------------------------------------------
/durban/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
32 |
33 |
36 |
37 |
38 |
42 |
43 |
47 |
48 |
52 |
53 |
56 |
57 |
61 |
62 |
66 |
67 |
71 |
72 |
76 |
77 |
81 |
82 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/RectUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | import android.graphics.RectF;
19 |
20 | /**
21 | * Update by Yan Zhenjie on 2017/5/23.
22 | */
23 | public class RectUtils {
24 |
25 | /**
26 | * Gets a float array of the 2D coordinates representing a rectangles
27 | * corners.
28 | * The order of the corners in the float array is:
29 | * 0------->1
30 | * ^ |
31 | * | |
32 | * | v
33 | * 3<-------2
34 | *
35 | * @param r the rectangle to get the corners of
36 | * @return the float array of corners (8 floats)
37 | */
38 | public static float[] getCornersFromRect(RectF r) {
39 | return new float[]{
40 | r.left, r.top,
41 | r.right, r.top,
42 | r.right, r.bottom,
43 | r.left, r.bottom
44 | };
45 | }
46 |
47 | /**
48 | * Gets a float array of two lengths representing a rectangles width and height
49 | * The order of the corners in the input float array is:
50 | * 0------->1
51 | * ^ |
52 | * | |
53 | * | v
54 | * 3<-------2
55 | *
56 | * @param corners the float array of corners (8 floats)
57 | * @return the float array of width and height (2 floats)
58 | */
59 | public static float[] getRectSidesFromCorners(float[] corners) {
60 | return new float[]{(float) Math.sqrt(Math.pow(corners[0] - corners[2], 2) + Math.pow(corners[1] - corners[3], 2)),
61 | (float) Math.sqrt(Math.pow(corners[2] - corners[4], 2) + Math.pow(corners[3] - corners[5], 2))};
62 | }
63 |
64 | public static float[] getCenterFromRect(RectF r) {
65 | return new float[]{r.centerX(), r.centerY()};
66 | }
67 |
68 | /**
69 | * Takes an array of 2D coordinates representing corners and returns the
70 | * smallest rectangle containing those coordinates.
71 | *
72 | * @param array array of 2D coordinates
73 | * @return smallest rectangle containing coordinates
74 | */
75 | public static RectF trapToRect(float[] array) {
76 | RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
77 | Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
78 | for (int i = 1; i < array.length; i += 2) {
79 | float x = Math.round(array[i - 1] * 10) / 10.f;
80 | float y = Math.round(array[i] * 10) / 10.f;
81 | r.left = (x < r.left) ? x : r.left;
82 | r.top = (y < r.top) ? y : r.top;
83 | r.right = (x > r.right) ? x : r.right;
84 | r.bottom = (y > r.bottom) ? y : r.bottom;
85 | }
86 | r.sort();
87 | return r;
88 | }
89 |
90 | }
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/Controller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban;
17 |
18 | import android.os.Parcel;
19 | import android.os.Parcelable;
20 |
21 | /**
22 | * Control panel configuration.
23 | * Created by Yan Zhenjie on 2017/5/30.
24 | */
25 | public class Controller implements Parcelable {
26 |
27 | private Controller(Parcel in) {
28 | this.enable = in.readByte() != 0;
29 | this.rotation = in.readByte() != 0;
30 | this.rotationTitle = in.readByte() != 0;
31 | this.scale = in.readByte() != 0;
32 | this.scaleTitle = in.readByte() != 0;
33 | }
34 |
35 | @Override
36 | public void writeToParcel(Parcel dest, int flags) {
37 | dest.writeByte((byte) (enable ? 1 : 0));
38 | dest.writeByte((byte) (rotation ? 1 : 0));
39 | dest.writeByte((byte) (rotationTitle ? 1 : 0));
40 | dest.writeByte((byte) (scale ? 1 : 0));
41 | dest.writeByte((byte) (scaleTitle ? 1 : 0));
42 | }
43 |
44 | @Override
45 | public int describeContents() {
46 | return 0;
47 | }
48 |
49 | public static final Creator CREATOR = new Creator() {
50 | @Override
51 | public Controller createFromParcel(Parcel in) {
52 | return new Controller(in);
53 | }
54 |
55 | @Override
56 | public Controller[] newArray(int size) {
57 | return new Controller[size];
58 | }
59 | };
60 |
61 | /**
62 | * Create a Builder.
63 | */
64 | public static Builder newBuilder() {
65 | return new Builder();
66 | }
67 |
68 | private boolean enable;
69 |
70 | private boolean rotation;
71 | private boolean rotationTitle;
72 |
73 | private boolean scale;
74 | private boolean scaleTitle;
75 |
76 | private Controller(Builder builder) {
77 | this.enable = builder.enable;
78 | this.rotation = builder.rotation;
79 | this.rotationTitle = builder.rotationTitle;
80 | this.scale = builder.scale;
81 | this.scaleTitle = builder.scaleTitle;
82 | }
83 |
84 | public boolean isEnable() {
85 | return enable;
86 | }
87 |
88 | public boolean isRotation() {
89 | return rotation;
90 | }
91 |
92 | public boolean isRotationTitle() {
93 | return rotationTitle;
94 | }
95 |
96 | public boolean isScale() {
97 | return scale;
98 | }
99 |
100 | public boolean isScaleTitle() {
101 | return scaleTitle;
102 | }
103 |
104 | public static final class Builder {
105 |
106 | private boolean enable = true;
107 |
108 | private boolean rotation = true;
109 | private boolean rotationTitle = true;
110 |
111 | private boolean scale = true;
112 | private boolean scaleTitle = true;
113 |
114 | private Builder() {
115 | }
116 |
117 | public Builder enable(boolean enable) {
118 | this.enable = enable;
119 | return this;
120 | }
121 |
122 | public Builder rotation(boolean rotation) {
123 | this.rotation = rotation;
124 | return this;
125 | }
126 |
127 | public Builder rotationTitle(boolean rotationTitle) {
128 | this.rotationTitle = rotationTitle;
129 | return this;
130 | }
131 |
132 | public Builder scale(boolean scale) {
133 | this.scale = scale;
134 | return this;
135 | }
136 |
137 | public Builder scaleTitle(boolean scaleTitle) {
138 | this.scaleTitle = scaleTitle;
139 | return this;
140 | }
141 |
142 | public Controller build() {
143 | return new Controller(this);
144 | }
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/README-CN.md:
--------------------------------------------------------------------------------
1 | `Durban`是一个MD风格的图片裁剪工具,结合图片选择库[Album](https://github.com/yanzhenjie/Album)是最好的搭配。
2 |
3 | 它来自[uCrop](https://github.com/Yalantis/uCrop),我修改了大部分代码,让它使用起来更加友好,**重点是一次可以裁剪多张图片**,同时更适合和[Album](https://github.com/yanzhenjie/Album)结合使用。
4 |
5 | QQ技术交流群:[547839514](https://jq.qq.com/?_wv=1027&k=49Xx0JX)
6 | 图片选择库推荐:[https://github.com/yanzhenjie/Album](https://github.com/yanzhenjie/Album)
7 |
8 | **[English Document](./README.md)**
9 |
10 | # 截图
11 |
12 |
13 | # 依赖
14 | * Gradle:
15 | ```groovy
16 | compile 'com.yanzhenjie:durban:1.0.1'
17 | ```
18 |
19 | * Maven:
20 | ```xml
21 |
22 | com.yanzhenjie
23 | durban
24 | 1.0.1
25 | pom
26 |
27 | ```
28 |
29 | # 使用介绍
30 | 支持在`Activity`、`android.app.Fragment`、`android.support.v4.app.Fragment`中调用:
31 | ```java
32 | Durban.with(Activity);
33 | Durban.with(android.app.Fragment);
34 | Durban.with(android.support.v4.app.Fragment);
35 | ```
36 |
37 | 调用示例:
38 | ```java
39 | Durban.with(this)
40 | // 裁剪界面的标题。
41 | .title("Crop")
42 | .statusBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark))
43 | .toolBarColor(ContextCompat.getColor(this, R.color.colorPrimary))
44 | .navigationBarColor(ContextCompat.getColor(this, R.color.colorPrimaryBlack))
45 | // 图片路径list或者数组。
46 | .inputImagePaths(imagePathList)
47 | // 图片输出文件夹路径。
48 | .outputDirectory(cropDirectory)
49 | // 裁剪图片输出的最大宽高。
50 | .maxWidthHeight(500, 500)
51 | // 裁剪时的宽高比。
52 | .aspectRatio(1, 1)
53 | // 图片压缩格式:JPEG、PNG。
54 | .compressFormat(Durban.COMPRESS_JPEG)
55 | // 图片压缩质量,请参考:Bitmap#compress(Bitmap.CompressFormat, int, OutputStream)
56 | .compressQuality(90)
57 | // 裁剪时的手势支持:ROTATE, SCALE, ALL, NONE.
58 | .gesture(Durban.GESTURE_ALL)
59 | .controller(
60 | Controller.newBuilder()
61 | .enable(false) // 是否开启控制面板。
62 | .rotation(true) // 是否有旋转按钮。
63 | .rotationTitle(true) // 旋转控制按钮上面的标题。
64 | .scale(true) // 是否有缩放按钮。
65 | .scaleTitle(true) // 缩放控制按钮上面的标题。
66 | .build()) // 创建控制面板配置。
67 | .requestCode(200)
68 | .start();
69 | ```
70 | **注意**:`inputImagePaths()`方法的参数可以是路径list,也可以是路径数组:
71 | ```java
72 | // 例如:
73 | List imagePathList = ...
74 | Durban.with(this)
75 | ...
76 | .inputImagePaths(imagePathList)
77 | ...
78 |
79 | // 或者:
80 | String[] imagePathArray = ...
81 | Durban.with(this)
82 | ...
83 | .inputImagePaths(imagePathArray)
84 | ...
85 |
86 | // 或者:
87 | Durban.with(this)
88 | ...
89 | .inputImagePaths(path1, path2, path3...)
90 | ...
91 | ```
92 |
93 | 然后重写`onActivityResult()`方法接受剪裁结果:
94 | ```java
95 | @Override
96 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
97 | switch (requestCode) {
98 | case 200: {
99 | // 解析剪切结果:
100 | if (resultCode != RESULT_OK) {
101 | ArrayList mImageList = Durban.parseResult(data);
102 | } else {
103 | // TODO 其它处理...
104 | }
105 | break;
106 | }
107 | }
108 | }
109 | ```
110 |
111 | ## 意外的语言配置
112 | Durban支持英文、简体中文和繁体中文,如果需要支持其它语言,你可以在`values-xxx`(xxx的意思是语言标记,比如:values-zh、values-zh-rHK)复写Durban的`string.xml`的资源,并且在`Application#onCreate()`中配置语言:
113 | ```java
114 | Durban.initialize(
115 | DurbanConfig.newBuilder(this)
116 | .setLocale(...)
117 | .build()
118 | );
119 | ```
120 |
121 | # 混淆规则
122 | Durban可以被完全混淆,如果出现了问题请添加如下混淆规则:
123 | ```txt
124 | -dontwarn com.yanzhenjie.curban.**
125 | -keep class com.yanzhenjie.curban.**{*;}
126 |
127 | -dontwarn com.yanzhenjie.loading.**
128 | -keep class com.yanzhenjie.loading.**{*;}
129 | ```
130 |
131 | # 感谢
132 | 1. [uCrop](https://github.com/Yalantis/uCrop)
133 | 2. [Album](https://github.com/yanzhenjie/Album)
134 |
135 | # License
136 | ```text
137 | Copyright 2017 Yan Zhenjie
138 |
139 | Licensed under the Apache License, Version 2.0 (the "License");
140 | you may not use this file except in compliance with the License.
141 | You may obtain a copy of the License at
142 |
143 | http://www.apache.org/licenses/LICENSE-2.0
144 |
145 | Unless required by applicable law or agreed to in writing, software
146 | distributed under the License is distributed on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
148 | See the License for the specific language governing permissions and
149 | limitations under the License.
150 | ```
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/task/BitmapLoadTask.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.task;
17 |
18 | import android.content.Context;
19 | import android.graphics.Bitmap;
20 | import android.graphics.BitmapFactory;
21 | import android.graphics.Matrix;
22 | import android.os.AsyncTask;
23 | import android.support.annotation.NonNull;
24 |
25 | import com.yanzhenjie.durban.callback.BitmapLoadCallback;
26 | import com.yanzhenjie.durban.model.ExifInfo;
27 | import com.yanzhenjie.durban.util.BitmapLoadUtils;
28 | import com.yanzhenjie.loading.dialog.LoadingDialog;
29 |
30 | /**
31 | *
32 | * Asynchronous loading of images.
33 | *
34 | * Create by Yan Zhenjie on 2017/5/22.
35 | */
36 | public class BitmapLoadTask extends AsyncTask {
37 |
38 | static class BitmapWorkerResult {
39 |
40 | final Bitmap bitmap;
41 | final ExifInfo exifInfo;
42 |
43 | BitmapWorkerResult(Bitmap bitmapResult, ExifInfo exifInfo) {
44 | this.bitmap = bitmapResult;
45 | this.exifInfo = exifInfo;
46 | }
47 | }
48 |
49 | private final LoadingDialog mDialog;
50 | private final int mRequiredWidth;
51 | private final int mRequiredHeight;
52 |
53 | private final BitmapLoadCallback mCallback;
54 |
55 | public BitmapLoadTask(
56 | Context context,
57 | int requiredWidth,
58 | int requiredHeight,
59 | BitmapLoadCallback loadCallback) {
60 | mDialog = new LoadingDialog(context);
61 | mRequiredWidth = requiredWidth;
62 | mRequiredHeight = requiredHeight;
63 | mCallback = loadCallback;
64 | }
65 |
66 | @Override
67 | protected void onPreExecute() {
68 | if (!mDialog.isShowing()) mDialog.show();
69 | }
70 |
71 | @Override
72 | protected void onPostExecute(@NonNull BitmapLoadTask.BitmapWorkerResult result) {
73 | if (mDialog.isShowing()) mDialog.dismiss();
74 | if (result.bitmap == null) {
75 | mCallback.onFailure();
76 | } else {
77 | mCallback.onSuccessfully(result.bitmap, result.exifInfo);
78 | }
79 | }
80 |
81 | @Override
82 | protected BitmapLoadTask.BitmapWorkerResult doInBackground(String... params) {
83 | String imagePath = params[0];
84 |
85 | final BitmapFactory.Options options = new BitmapFactory.Options();
86 | options.inJustDecodeBounds = true;
87 | BitmapFactory.decodeFile(imagePath, options);
88 | if (options.outWidth == -1 || options.outHeight == -1)
89 | return new BitmapLoadTask.BitmapWorkerResult(null, null);
90 |
91 | options.inSampleSize = BitmapLoadUtils.calculateInSampleSize(options, mRequiredWidth, mRequiredHeight);
92 | options.inJustDecodeBounds = false;
93 |
94 | Bitmap decodeSampledBitmap = null;
95 |
96 | boolean decodeAttemptSuccess = false;
97 | while (!decodeAttemptSuccess) {
98 | try {
99 | decodeSampledBitmap = BitmapFactory.decodeFile(imagePath, options);
100 | decodeAttemptSuccess = true;
101 | } catch (Throwable error) {
102 | options.inSampleSize *= 2;
103 | }
104 | }
105 |
106 | int exifOrientation = BitmapLoadUtils.getExifOrientation(imagePath);
107 | int exifDegrees = BitmapLoadUtils.exifToDegrees(exifOrientation);
108 | int exifTranslation = BitmapLoadUtils.exifToTranslation(exifOrientation);
109 |
110 | ExifInfo exifInfo = new ExifInfo(exifOrientation, exifDegrees, exifTranslation);
111 |
112 | Matrix matrix = new Matrix();
113 | if (exifDegrees != 0)
114 | matrix.preRotate(exifDegrees);
115 | if (exifTranslation != 1)
116 | matrix.postScale(exifTranslation, 1);
117 | if (!matrix.isIdentity())
118 | return new BitmapLoadTask.BitmapWorkerResult(BitmapLoadUtils.transformBitmap(decodeSampledBitmap, matrix), exifInfo);
119 | return new BitmapLoadTask.BitmapWorkerResult(decodeSampledBitmap, exifInfo);
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/RotationGestureDetector.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | import android.support.annotation.NonNull;
19 | import android.view.MotionEvent;
20 |
21 | /**
22 | * Update by Yan Zhenjie on 2017/5/23.
23 | */
24 | public class RotationGestureDetector {
25 |
26 | private static final int INVALID_POINTER_INDEX = -1;
27 |
28 | private float fX, fY, sX, sY;
29 |
30 | private int mPointerIndex1, mPointerIndex2;
31 | private float mAngle;
32 | private boolean mIsFirstTouch;
33 |
34 | private OnRotationGestureListener mListener;
35 |
36 | public RotationGestureDetector(OnRotationGestureListener listener) {
37 | mListener = listener;
38 | mPointerIndex1 = INVALID_POINTER_INDEX;
39 | mPointerIndex2 = INVALID_POINTER_INDEX;
40 | }
41 |
42 | public float getAngle() {
43 | return mAngle;
44 | }
45 |
46 | public boolean onTouchEvent(@NonNull MotionEvent event) {
47 | switch (event.getActionMasked()) {
48 | case MotionEvent.ACTION_DOWN:
49 | sX = event.getX();
50 | sY = event.getY();
51 | mPointerIndex1 = event.findPointerIndex(event.getPointerId(0));
52 | mAngle = 0;
53 | mIsFirstTouch = true;
54 | break;
55 | case MotionEvent.ACTION_POINTER_DOWN:
56 | fX = event.getX();
57 | fY = event.getY();
58 | mPointerIndex2 = event.findPointerIndex(event.getPointerId(event.getActionIndex()));
59 | mAngle = 0;
60 | mIsFirstTouch = true;
61 | break;
62 | case MotionEvent.ACTION_MOVE:
63 | if (mPointerIndex1 != INVALID_POINTER_INDEX && mPointerIndex2 != INVALID_POINTER_INDEX && event.getPointerCount
64 | () > mPointerIndex2) {
65 | float nfX, nfY, nsX, nsY;
66 |
67 | nsX = event.getX(mPointerIndex1);
68 | nsY = event.getY(mPointerIndex1);
69 | nfX = event.getX(mPointerIndex2);
70 | nfY = event.getY(mPointerIndex2);
71 |
72 | if (mIsFirstTouch) {
73 | mAngle = 0;
74 | mIsFirstTouch = false;
75 | } else {
76 | calculateAngleBetweenLines(fX, fY, sX, sY, nfX, nfY, nsX, nsY);
77 | }
78 |
79 | if (mListener != null) {
80 | mListener.onRotation(this);
81 | }
82 | fX = nfX;
83 | fY = nfY;
84 | sX = nsX;
85 | sY = nsY;
86 | }
87 | break;
88 | case MotionEvent.ACTION_UP:
89 | mPointerIndex1 = INVALID_POINTER_INDEX;
90 | break;
91 | case MotionEvent.ACTION_POINTER_UP:
92 | mPointerIndex2 = INVALID_POINTER_INDEX;
93 | break;
94 | }
95 | return true;
96 | }
97 |
98 | private float calculateAngleBetweenLines(float fx1, float fy1, float fx2, float fy2,
99 | float sx1, float sy1, float sx2, float sy2) {
100 | return calculateAngleDelta(
101 | (float) Math.toDegrees((float) Math.atan2((fy1 - fy2), (fx1 - fx2))),
102 | (float) Math.toDegrees((float) Math.atan2((sy1 - sy2), (sx1 - sx2))));
103 | }
104 |
105 | private float calculateAngleDelta(float angleFrom, float angleTo) {
106 | mAngle = angleTo % 360.0f - angleFrom % 360.0f;
107 |
108 | if (mAngle < -180.0f) {
109 | mAngle += 360.0f;
110 | } else if (mAngle > 180.0f) {
111 | mAngle -= 360.0f;
112 | }
113 |
114 | return mAngle;
115 | }
116 |
117 | public static class SimpleOnRotationGestureListener implements OnRotationGestureListener {
118 |
119 | @Override
120 | public boolean onRotation(RotationGestureDetector rotationDetector) {
121 | return false;
122 | }
123 | }
124 |
125 | public interface OnRotationGestureListener {
126 |
127 | boolean onRotation(RotationGestureDetector rotationDetector);
128 | }
129 |
130 | }
--------------------------------------------------------------------------------
/durban/src/main/res/layout/durban_activity_photobox.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
22 |
23 |
27 |
28 |
35 |
36 |
39 |
40 |
44 |
45 |
49 |
50 |
51 |
52 |
54 |
55 |
58 |
59 |
63 |
64 |
65 |
68 |
69 |
73 |
74 |
75 |
78 |
79 |
83 |
84 |
85 |
88 |
89 |
93 |
94 |
95 |
96 |
97 |
98 |
104 |
105 |
112 |
113 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | `Durban` is an MD-style picture cropping tool that combines the image selection tool with [Album](https://github.com/yanzhenjie/Album) Use is the best.
2 |
3 | It comes from the [uCrop](https://github.com/Yalantis/uCrop), but I modified most of the code, let it use more friendly.
4 |
5 | Recommended image selection library: [https://github.com/yanzhenjie/Album](https://github.com/yanzhenjie/Album)
6 |
7 | [中文文档](./README-CN.md)
8 |
9 | # Screenshot
10 |
11 |
12 | # Dependencies
13 | * Gradle:
14 | ```groovy
15 | compile 'com.yanzhenjie:durban:1.0.1'
16 | ```
17 |
18 | * Maven:
19 | ```xml
20 |
21 | com.yanzhenjie
22 | durban
23 | 1.0.1
24 | pom
25 |
26 | ```
27 |
28 | # Usage
29 | Support `Activity`, `android.app.Fragment`, `android.support.v4.app.Fragment`:
30 | ```java
31 | Durban.with(Activity);
32 | Durban.with(android.app.Fragment);
33 | Durban.with(android.support.v4.app.Fragment);
34 | ```
35 |
36 | How to call clipping:
37 | ```java
38 | Durban.with(this)
39 | // Che title of the UI.
40 | .title("Crop")
41 | .statusBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark))
42 | .toolBarColor(ContextCompat.getColor(this, R.color.colorPrimary))
43 | .navigationBarColor(ContextCompat.getColor(this, R.color.colorPrimaryBlack))
44 | // Image path list/array.
45 | .inputImagePaths(imagePathList)
46 | // Image output directory.
47 | .outputDirectory(cropDirectory)
48 | // Image size limit.
49 | .maxWidthHeight(500, 500)
50 | // Aspect ratio.
51 | .aspectRatio(1, 1)
52 | // Output format: JPEG, PNG.
53 | .compressFormat(Durban.COMPRESS_JPEG)
54 | // Compress quality, see Bitmap#compress(Bitmap.CompressFormat, int, OutputStream)
55 | .compressQuality(90)
56 | // Gesture: ROTATE, SCALE, ALL, NONE.
57 | .gesture(Durban.GESTURE_ALL)
58 | .controller(
59 | Controller.newBuilder() // Create Builder of Controller.
60 | .enable(false) // Enable the control panel.
61 | .rotation(true) // Rotation button.
62 | .rotationTitle(true) // Rotation button title.
63 | .scale(true) // Scale button.
64 | .scaleTitle(true) // Scale button title.
65 | .build()) // Create Controller Config.
66 | .requestCode(200)
67 | .start();
68 | ```
69 | **Note:** `inputImagePaths()` method can be a picture path list, can also be a picture path array, such as:
70 | ```java
71 | // Such as:
72 | List imagePathList = ...
73 | Durban.with(this)
74 | ...
75 | .inputImagePaths(imagePathList)
76 | ...
77 |
78 | // OR:
79 | String[] imagePathArray = ...
80 | Durban.with(this)
81 | ...
82 | .inputImagePaths(imagePathArray)
83 | ...
84 |
85 | // OR:
86 | Durban.with(this)
87 | ...
88 | .inputImagePaths(path1, path2, path3...)
89 | ...
90 | ```
91 |
92 | And then override the `onActivityResult()` method to accept the cropping results:
93 | ```java
94 | @Override
95 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
96 | switch (requestCode) {
97 | case 200: {
98 | // Analyze the list of paths after cropping.
99 | if (resultCode != RESULT_OK) {
100 | ArrayList mImageList = Durban.parseResult(data);
101 | } else {
102 | // TODO other...
103 | }
104 | break;
105 | }
106 | }
107 | }
108 | ```
109 |
110 | ## Unexpected language configuration
111 | Durban supports English, Chinese Simplified and Traditional Chinese, and if you need to configure other languages, you can rewrite the resources in Durban's `String.xml` in `value-xxx`(xxx means other languages, such as: values-zh, values-zh-rHK), and use the following configuration language in `Application#onCreate()`:
112 | ```java
113 | Durban.initialize(
114 | DurbanConfig.newBuilder(this)
115 | .setLocale(...)
116 | .build()
117 | );
118 | ```
119 |
120 | # Proguard-rules
121 | Durban can be completely confusing, if there is a problem, add the rule to the proguard-rules:
122 | ```txt
123 | -dontwarn com.yanzhenjie.curban.**
124 | -keep class com.yanzhenjie.curban.**{*;}
125 |
126 | -dontwarn com.yanzhenjie.loading.**
127 | -keep class com.yanzhenjie.loading.**{*;}
128 | ```
129 |
130 | # Thanks
131 | 1. [uCrop](https://github.com/Yalantis/uCrop)
132 | 2. [Album](https://github.com/yanzhenjie/Album)
133 |
134 | # License
135 | ```text
136 | Copyright 2017 Yan Zhenjie
137 |
138 | Licensed under the Apache License, Version 2.0 (the "License");
139 | you may not use this file except in compliance with the License.
140 | You may obtain a copy of the License at
141 |
142 | http://www.apache.org/licenses/LICENSE-2.0
143 |
144 | Unless required by applicable law or agreed to in writing, software
145 | distributed under the License is distributed on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
147 | See the License for the specific language governing permissions and
148 | limitations under the License.
149 | ```
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/BitmapLoadUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | import android.content.Context;
19 | import android.graphics.Bitmap;
20 | import android.graphics.BitmapFactory;
21 | import android.graphics.Matrix;
22 | import android.graphics.Point;
23 | import android.media.ExifInterface;
24 | import android.os.Build;
25 | import android.support.annotation.NonNull;
26 | import android.view.Display;
27 | import android.view.WindowManager;
28 |
29 | import java.io.FileInputStream;
30 | import java.io.IOException;
31 | import java.io.InputStream;
32 |
33 | /**
34 | * Update by Yan Zhenjie on 2017/5/23.
35 | */
36 | public class BitmapLoadUtils {
37 |
38 | public static Bitmap transformBitmap(@NonNull Bitmap bitmap, @NonNull Matrix transformMatrix) {
39 | try {
40 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), transformMatrix, true);
41 | } catch (OutOfMemoryError error) {
42 | return bitmap;
43 | }
44 | }
45 |
46 | public static int calculateInSampleSize(@NonNull BitmapFactory.Options options, int reqWidth, int reqHeight) {
47 | // Raw height and width of image
48 | final int height = options.outHeight;
49 | final int width = options.outWidth;
50 | int inSampleSize = 1;
51 |
52 | if (height > reqHeight || width > reqWidth) {
53 | // Calculate the largest inSampleSize value that is a power of 2 and keeps both
54 | // height and width lower or equal to the requested height and width.
55 | while ((height / inSampleSize) > reqHeight || (width / inSampleSize) > reqWidth) {
56 | inSampleSize *= 2;
57 | }
58 | }
59 | return inSampleSize;
60 | }
61 |
62 | public static int getExifOrientation(@NonNull String imagePath) {
63 | int orientation = ExifInterface.ORIENTATION_UNDEFINED;
64 | try {
65 | InputStream stream = new FileInputStream(imagePath);
66 | orientation = new ImageHeaderParser(stream).getOrientation();
67 | stream.close();
68 | } catch (IOException e) {
69 | e.printStackTrace();
70 | }
71 | return orientation;
72 | }
73 |
74 | public static int exifToDegrees(int exifOrientation) {
75 | int rotation;
76 | switch (exifOrientation) {
77 | case ExifInterface.ORIENTATION_ROTATE_90:
78 | case ExifInterface.ORIENTATION_TRANSPOSE:
79 | rotation = 90;
80 | break;
81 | case ExifInterface.ORIENTATION_ROTATE_180:
82 | case ExifInterface.ORIENTATION_FLIP_VERTICAL:
83 | rotation = 180;
84 | break;
85 | case ExifInterface.ORIENTATION_ROTATE_270:
86 | case ExifInterface.ORIENTATION_TRANSVERSE:
87 | rotation = 270;
88 | break;
89 | default:
90 | rotation = 0;
91 | }
92 | return rotation;
93 | }
94 |
95 | public static int exifToTranslation(int exifOrientation) {
96 | int translation;
97 | switch (exifOrientation) {
98 | case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
99 | case ExifInterface.ORIENTATION_FLIP_VERTICAL:
100 | case ExifInterface.ORIENTATION_TRANSPOSE:
101 | case ExifInterface.ORIENTATION_TRANSVERSE:
102 | translation = -1;
103 | break;
104 | default:
105 | translation = 1;
106 | }
107 | return translation;
108 | }
109 |
110 | /**
111 | * This method calculates maximum size of both width and height of exception.
112 | * It is twice the device screen diagonal for default implementation (extra quality to zoom image).
113 | * Size cannot exceed max texture size.
114 | *
115 | * @return - max exception size in pixels.
116 | */
117 | public static int calculateMaxBitmapSize(@NonNull Context context) {
118 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
119 | Display display = wm.getDefaultDisplay();
120 |
121 | Point size = new Point();
122 | int width, height;
123 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
124 | display.getSize(size);
125 | width = size.x;
126 | height = size.y;
127 | } else {
128 | width = display.getWidth();
129 | height = display.getHeight();
130 | }
131 |
132 | // Twice the device screen diagonal as default
133 | int maxBitmapSize = (int) Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
134 |
135 | // Check for max texture size via GL
136 | final int maxTextureSize = EglUtils.getMaxTextureSize();
137 | if (maxTextureSize > 0) {
138 | maxBitmapSize = Math.min(maxBitmapSize, maxTextureSize);
139 | }
140 | return maxBitmapSize;
141 | }
142 |
143 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/EglUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | import android.annotation.TargetApi;
19 | import android.opengl.EGL14;
20 | import android.opengl.EGLConfig;
21 | import android.opengl.EGLContext;
22 | import android.opengl.EGLDisplay;
23 | import android.opengl.EGLSurface;
24 | import android.opengl.GLES10;
25 | import android.opengl.GLES20;
26 | import android.os.Build;
27 | import android.util.Log;
28 |
29 | import javax.microedition.khronos.egl.EGL10;
30 |
31 | /**
32 | * Update by Yan Zhenjie on 2017/5/23.
33 | */
34 | public class EglUtils {
35 |
36 | private static final String TAG = "EglUtils";
37 |
38 | private EglUtils() {
39 |
40 | }
41 |
42 | public static int getMaxTextureSize() {
43 | try {
44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
45 | return getMaxTextureEgl14();
46 | } else {
47 | return getMaxTextureEgl10();
48 | }
49 | } catch (Exception e) {
50 | Log.d(TAG, "getMaxTextureSize: ", e);
51 | return 0;
52 | }
53 | }
54 |
55 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
56 | private static int getMaxTextureEgl14() {
57 | EGLDisplay dpy = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
58 | int[] vers = new int[2];
59 | EGL14.eglInitialize(dpy, vers, 0, vers, 1);
60 |
61 | int[] configAttr = {
62 | EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER,
63 | EGL14.EGL_LEVEL, 0,
64 | EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
65 | EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
66 | EGL14.EGL_NONE
67 | };
68 | EGLConfig[] configs = new EGLConfig[1];
69 | int[] numConfig = new int[1];
70 | EGL14.eglChooseConfig(dpy, configAttr, 0,
71 | configs, 0, 1, numConfig, 0);
72 | if (numConfig[0] == 0) {
73 | return 0;
74 | }
75 | EGLConfig config = configs[0];
76 |
77 | int[] surfAttr = {
78 | EGL14.EGL_WIDTH, 64,
79 | EGL14.EGL_HEIGHT, 64,
80 | EGL14.EGL_NONE
81 | };
82 | EGLSurface surf = EGL14.eglCreatePbufferSurface(dpy, config, surfAttr, 0);
83 |
84 | int[] ctxAttribute = {
85 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
86 | EGL14.EGL_NONE
87 | };
88 | EGLContext ctx = EGL14.eglCreateContext(dpy, config, EGL14.EGL_NO_CONTEXT, ctxAttribute, 0);
89 |
90 | EGL14.eglMakeCurrent(dpy, surf, surf, ctx);
91 |
92 | int[] maxSize = new int[1];
93 | GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxSize, 0);
94 |
95 | EGL14.eglMakeCurrent(dpy, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
96 | EGL14.EGL_NO_CONTEXT);
97 | EGL14.eglDestroySurface(dpy, surf);
98 | EGL14.eglDestroyContext(dpy, ctx);
99 | EGL14.eglTerminate(dpy);
100 |
101 | return maxSize[0];
102 | }
103 |
104 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
105 | private static int getMaxTextureEgl10() {
106 | EGL10 egl = (EGL10) javax.microedition.khronos.egl.EGLContext.getEGL();
107 |
108 | javax.microedition.khronos.egl.EGLDisplay dpy = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
109 | int[] vers = new int[2];
110 | egl.eglInitialize(dpy, vers);
111 |
112 | int[] configAttr = {
113 | EGL10.EGL_COLOR_BUFFER_TYPE, EGL10.EGL_RGB_BUFFER,
114 | EGL10.EGL_LEVEL, 0,
115 | EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
116 | EGL10.EGL_NONE
117 | };
118 | javax.microedition.khronos.egl.EGLConfig[] configs = new javax.microedition.khronos.egl.EGLConfig[1];
119 | int[] numConfig = new int[1];
120 | egl.eglChooseConfig(dpy, configAttr, configs, 1, numConfig);
121 | if (numConfig[0] == 0) {
122 | return 0;
123 | }
124 | javax.microedition.khronos.egl.EGLConfig config = configs[0];
125 |
126 | int[] surfAttr = {
127 | EGL10.EGL_WIDTH, 64,
128 | EGL10.EGL_HEIGHT, 64,
129 | EGL10.EGL_NONE
130 | };
131 | javax.microedition.khronos.egl.EGLSurface surf = egl.eglCreatePbufferSurface(dpy, config, surfAttr);
132 | final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; // missing in EGL10
133 | int[] ctxAttribute = {
134 | EGL_CONTEXT_CLIENT_VERSION, 1,
135 | EGL10.EGL_NONE
136 | };
137 | javax.microedition.khronos.egl.EGLContext ctx = egl.eglCreateContext(dpy, config, EGL10.EGL_NO_CONTEXT, ctxAttribute);
138 | egl.eglMakeCurrent(dpy, surf, surf, ctx);
139 | int[] maxSize = new int[1];
140 | GLES10.glGetIntegerv(GLES10.GL_MAX_TEXTURE_SIZE, maxSize, 0);
141 | egl.eglMakeCurrent(dpy, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
142 | EGL10.EGL_NO_CONTEXT);
143 | egl.eglDestroySurface(dpy, surf);
144 | egl.eglDestroyContext(dpy, ctx);
145 | egl.eglTerminate(dpy);
146 |
147 | return maxSize[0];
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/view/GestureCropImageView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.view;
17 |
18 | import android.content.Context;
19 | import android.util.AttributeSet;
20 | import android.view.GestureDetector;
21 | import android.view.MotionEvent;
22 | import android.view.ScaleGestureDetector;
23 |
24 | import com.yanzhenjie.durban.util.RotationGestureDetector;
25 |
26 | /**
27 | * Update by Yan Zhenjie on 2017/5/23.
28 | */
29 | public class GestureCropImageView extends CropImageView {
30 |
31 | private static final int DOUBLE_TAP_ZOOM_DURATION = 200;
32 |
33 | private ScaleGestureDetector mScaleDetector;
34 | private RotationGestureDetector mRotateDetector;
35 | private GestureDetector mGestureDetector;
36 |
37 | private float mMidPntX, mMidPntY;
38 |
39 | private boolean mIsRotateEnabled = true, mIsScaleEnabled = true;
40 | private int mDoubleTapScaleSteps = 5;
41 |
42 | public GestureCropImageView(Context context) {
43 | super(context);
44 | }
45 |
46 | public GestureCropImageView(Context context, AttributeSet attrs) {
47 | this(context, attrs, 0);
48 | }
49 |
50 | public GestureCropImageView(Context context, AttributeSet attrs, int defStyle) {
51 | super(context, attrs, defStyle);
52 | }
53 |
54 | public void setScaleEnabled(boolean scaleEnabled) {
55 | mIsScaleEnabled = scaleEnabled;
56 | }
57 |
58 | public boolean isScaleEnabled() {
59 | return mIsScaleEnabled;
60 | }
61 |
62 | public void setRotateEnabled(boolean rotateEnabled) {
63 | mIsRotateEnabled = rotateEnabled;
64 | }
65 |
66 | public boolean isRotateEnabled() {
67 | return mIsRotateEnabled;
68 | }
69 |
70 | public void setDoubleTapScaleSteps(int doubleTapScaleSteps) {
71 | mDoubleTapScaleSteps = doubleTapScaleSteps;
72 | }
73 |
74 | public int getDoubleTapScaleSteps() {
75 | return mDoubleTapScaleSteps;
76 | }
77 |
78 | /**
79 | * If it's ACTION_DOWN event - user touches the screen and all current animation must be canceled.
80 | * If it's ACTION_UP event - user removed all fingers from the screen and current image position must be corrected.
81 | * If there are more than 2 fingers - update focal point coordinates.
82 | * Pass the event to the gesture detectors if those are enabled.
83 | */
84 | @Override
85 | public boolean onTouchEvent(MotionEvent event) {
86 | if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) {
87 | cancelAllAnimations();
88 | }
89 |
90 | if (event.getPointerCount() > 1) {
91 | mMidPntX = (event.getX(0) + event.getX(1)) / 2;
92 | mMidPntY = (event.getY(0) + event.getY(1)) / 2;
93 | }
94 |
95 | mGestureDetector.onTouchEvent(event);
96 |
97 | if (mIsScaleEnabled) {
98 | mScaleDetector.onTouchEvent(event);
99 | }
100 |
101 | if (mIsRotateEnabled) {
102 | mRotateDetector.onTouchEvent(event);
103 | }
104 |
105 | if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
106 | setImageToWrapCropBounds();
107 | }
108 | return true;
109 | }
110 |
111 | @Override
112 | protected void init() {
113 | super.init();
114 | setupGestureListeners();
115 | }
116 |
117 | /**
118 | * This method calculates target scale value for double tap gesture.
119 | * User is able to zoom the image from min scale value
120 | * to the max scale value with {@link #mDoubleTapScaleSteps} double taps.
121 | */
122 | protected float getDoubleTapTargetScale() {
123 | return getCurrentScale() * (float) Math.pow(getMaxScale() / getMinScale(), 1.0f / mDoubleTapScaleSteps);
124 | }
125 |
126 | private void setupGestureListeners() {
127 | mGestureDetector = new GestureDetector(getContext(), new GestureListener(), null, true);
128 | mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
129 | mRotateDetector = new RotationGestureDetector(new RotateListener());
130 | }
131 |
132 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
133 |
134 | @Override
135 | public boolean onScale(ScaleGestureDetector detector) {
136 | postScale(detector.getScaleFactor(), mMidPntX, mMidPntY);
137 | return true;
138 | }
139 | }
140 |
141 | private class GestureListener extends GestureDetector.SimpleOnGestureListener {
142 |
143 | @Override
144 | public boolean onDoubleTap(MotionEvent e) {
145 | zoomImageToPosition(getDoubleTapTargetScale(), e.getX(), e.getY(), DOUBLE_TAP_ZOOM_DURATION);
146 | return super.onDoubleTap(e);
147 | }
148 |
149 | @Override
150 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
151 | postTranslate(-distanceX, -distanceY);
152 | return true;
153 | }
154 |
155 | }
156 |
157 | private class RotateListener extends RotationGestureDetector.SimpleOnRotationGestureListener {
158 |
159 | @Override
160 | public boolean onRotation(RotationGestureDetector rotationDetector) {
161 | postRotate(rotationDetector.getAngle(), mMidPntX, mMidPntY);
162 | return true;
163 | }
164 |
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/sample/src/main/java/com/yanzhenjie/durban/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.sample;
17 |
18 | import android.content.Intent;
19 | import android.graphics.drawable.Drawable;
20 | import android.os.Bundle;
21 | import android.support.v4.content.ContextCompat;
22 | import android.support.v4.content.res.ResourcesCompat;
23 | import android.support.v7.app.AppCompatActivity;
24 | import android.support.v7.widget.GridLayoutManager;
25 | import android.support.v7.widget.RecyclerView;
26 | import android.support.v7.widget.Toolbar;
27 | import android.util.Log;
28 | import android.view.Menu;
29 | import android.view.MenuItem;
30 |
31 | import com.yanzhenjie.album.Album;
32 | import com.yanzhenjie.album.util.DisplayUtils;
33 | import com.yanzhenjie.album.widget.recyclerview.AlbumVerticalGirdDecoration;
34 | import com.yanzhenjie.durban.Controller;
35 | import com.yanzhenjie.durban.Durban;
36 |
37 | import java.util.ArrayList;
38 |
39 | /**
40 | * Created by Yan Zhenjie on 2017/5/22.
41 | */
42 | public class MainActivity extends AppCompatActivity {
43 |
44 | RecyclerView mRecyclerView;
45 | GridAdapter mGridAdapter;
46 | ArrayList mImageList;
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 | DisplayUtils.initScreen(this);
52 | setContentView(R.layout.activity_main);
53 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
54 | setSupportActionBar(toolbar);
55 |
56 | mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
57 | mRecyclerView.setLayoutManager(new GridLayoutManager(this, 3));
58 |
59 | Drawable drawable = ResourcesCompat.getDrawable(getResources(), R.drawable.decoration_white, null);
60 | mRecyclerView.addItemDecoration(new AlbumVerticalGirdDecoration(drawable));
61 |
62 | assert drawable != null;
63 | int itemSize = (DisplayUtils.screenWidth - (drawable.getIntrinsicWidth() * 4)) / 3;
64 | mGridAdapter = new GridAdapter(this, itemSize);
65 | mRecyclerView.setAdapter(mGridAdapter);
66 |
67 | mImageList = new ArrayList<>();
68 | }
69 |
70 | /**
71 | * Select images.
72 | */
73 | private void selectImage() {
74 | Album.album(this)
75 | .statusBarColor(ContextCompat.getColor(this, R.color.colorPrimary))
76 | .toolBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark))
77 | .navigationBarColor(ContextCompat.getColor(this, R.color.colorPrimaryBlack))
78 | .selectCount(3)
79 | .requestCode(100)
80 | .start();
81 | }
82 |
83 | /**
84 | * Crop images.
85 | */
86 | private void cropImage(ArrayList imagePathList) {
87 | String cropDirectory = Utils.getAppRootPath(this).getAbsolutePath();
88 |
89 | Log.i("CropSample", "Save directory: " + cropDirectory);
90 |
91 | Durban.with(MainActivity.this)
92 | .statusBarColor(ContextCompat.getColor(this, R.color.colorPrimaryDark))
93 | .toolBarColor(ContextCompat.getColor(this, R.color.colorPrimary))
94 | .navigationBarColor(ContextCompat.getColor(this, R.color.colorPrimaryBlack))
95 | // Image path list/array.
96 | .inputImagePaths(imagePathList)
97 | // Image output directory.
98 | .outputDirectory(cropDirectory)
99 | // Image size limit.
100 | .maxWidthHeight(500, 500)
101 | // Aspect ratio.
102 | .aspectRatio(1, 1)
103 | // Output format: JPEG, PNG.
104 | .compressFormat(Durban.COMPRESS_JPEG)
105 | // Compress quality, see Bitmap#compress(Bitmap.CompressFormat, int, OutputStream)
106 | .compressQuality(90)
107 | // Gesture: ROTATE, SCALE, ALL, NONE.
108 | .gesture(Durban.GESTURE_ALL)
109 | .controller(Controller.newBuilder()
110 | .enable(false)
111 | .rotation(true)
112 | .rotationTitle(true)
113 | .scale(true)
114 | .scaleTitle(true)
115 | .build())
116 | .requestCode(200)
117 | .start();
118 | }
119 |
120 | @Override
121 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
122 | if (resultCode != RESULT_OK) return;
123 | switch (requestCode) {
124 | case 100: {
125 | // Analyze the selection results of the album.
126 | ArrayList imagePathList = Album.parseResult(data);
127 | cropImage(imagePathList);
128 | break;
129 | }
130 | case 200: {
131 | // Analyze the list of paths after cropping.
132 | mImageList = Durban.parseResult(data);
133 | mGridAdapter.notifyDataSetChanged(mImageList);
134 | break;
135 | }
136 | }
137 | }
138 |
139 | @Override
140 | public boolean onCreateOptionsMenu(Menu menu) {
141 | getMenuInflater().inflate(R.menu.menu_main, menu);
142 | return true;
143 | }
144 |
145 | @Override
146 | public boolean onOptionsItemSelected(MenuItem item) {
147 | int id = item.getItemId();
148 | switch (id) {
149 | case R.id.action_album: {
150 | selectImage();
151 | break;
152 | }
153 | }
154 | return true;
155 | }
156 | }
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/task/BitmapCropTask.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.task;
17 |
18 | import android.content.Context;
19 | import android.graphics.Bitmap;
20 | import android.graphics.Matrix;
21 | import android.graphics.RectF;
22 | import android.media.ExifInterface;
23 | import android.os.AsyncTask;
24 | import android.support.annotation.NonNull;
25 | import android.support.annotation.Nullable;
26 |
27 | import com.yanzhenjie.durban.callback.BitmapCropCallback;
28 | import com.yanzhenjie.durban.error.StorageError;
29 | import com.yanzhenjie.durban.model.CropParameters;
30 | import com.yanzhenjie.durban.model.ImageState;
31 | import com.yanzhenjie.durban.util.FileUtils;
32 | import com.yanzhenjie.durban.util.ImageHeaderParser;
33 | import com.yanzhenjie.loading.dialog.LoadingDialog;
34 |
35 | import java.io.File;
36 | import java.io.FileOutputStream;
37 | import java.io.OutputStream;
38 |
39 | /**
40 | * Cut and save the image asynchronously.
41 | * Create by Yan Zhenjie on 2017/5/22.
42 | */
43 | public class BitmapCropTask extends AsyncTask {
44 |
45 | static class PathWorkerResult {
46 |
47 | final String path;
48 | final Exception exception;
49 |
50 | PathWorkerResult(String path, Exception exception) {
51 | this.path = path;
52 | this.exception = exception;
53 | }
54 | }
55 |
56 | private LoadingDialog mDialog;
57 |
58 | private Bitmap mViewBitmap;
59 |
60 | private final RectF mCropRect;
61 | private final RectF mCurrentImageRect;
62 |
63 | private float mCurrentScale, mCurrentAngle;
64 | private final int mMaxResultImageSizeX, mMaxResultImageSizeY;
65 |
66 | private final Bitmap.CompressFormat mCompressFormat;
67 | private final int mCompressQuality;
68 | private final String mInputImagePath;
69 | private final String mOutputDirectory;
70 | private final BitmapCropCallback mCallback;
71 |
72 | private int mCroppedImageWidth, mCroppedImageHeight;
73 |
74 | public BitmapCropTask(@NonNull Context context,
75 | @Nullable Bitmap viewBitmap,
76 | @NonNull ImageState imageState,
77 | @NonNull CropParameters cropParameters,
78 | @Nullable BitmapCropCallback cropCallback) {
79 | mDialog = new LoadingDialog(context);
80 |
81 | mViewBitmap = viewBitmap;
82 | mCropRect = imageState.getCropRect();
83 | mCurrentImageRect = imageState.getCurrentImageRect();
84 |
85 | mCurrentScale = imageState.getCurrentScale();
86 | mCurrentAngle = imageState.getCurrentAngle();
87 | mMaxResultImageSizeX = cropParameters.getMaxResultImageSizeX();
88 | mMaxResultImageSizeY = cropParameters.getMaxResultImageSizeY();
89 |
90 | mCompressFormat = cropParameters.getCompressFormat();
91 | mCompressQuality = cropParameters.getCompressQuality();
92 |
93 | mInputImagePath = cropParameters.getImagePath();
94 | mOutputDirectory = cropParameters.getImageOutputPath();
95 | mCallback = cropCallback;
96 | }
97 |
98 | @Override
99 | protected void onPreExecute() {
100 | if (!mDialog.isShowing()) mDialog.show();
101 | }
102 |
103 | @Override
104 | protected void onPostExecute(BitmapCropTask.PathWorkerResult result) {
105 | if (mDialog.isShowing()) mDialog.dismiss();
106 |
107 | if (mCallback != null) {
108 | if (result.exception == null) {
109 | mCallback.onBitmapCropped(
110 | result.path,
111 | mCroppedImageWidth,
112 | mCroppedImageHeight);
113 | } else {
114 | mCallback.onCropFailure(result.exception);
115 | }
116 | }
117 | }
118 |
119 | @Override
120 | protected BitmapCropTask.PathWorkerResult doInBackground(Void... params) {
121 | try {
122 | String imagePath = crop();
123 | return new PathWorkerResult(imagePath, null);
124 | } catch (Exception e) {
125 | return new PathWorkerResult(null, e);
126 | }
127 | }
128 |
129 | private String crop() throws Exception {
130 | FileUtils.validateDirectory(mOutputDirectory);
131 |
132 | String fileName = FileUtils.randomImageName(mCompressFormat);
133 | String outputImagePath = new File(mOutputDirectory, fileName).getAbsolutePath();
134 |
135 | // Downsize if needed
136 | if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) {
137 | float cropWidth = mCropRect.width() / mCurrentScale;
138 | float cropHeight = mCropRect.height() / mCurrentScale;
139 |
140 | if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) {
141 |
142 | float scaleX = mMaxResultImageSizeX / cropWidth;
143 | float scaleY = mMaxResultImageSizeY / cropHeight;
144 | float resizeScale = Math.min(scaleX, scaleY);
145 |
146 | Bitmap resizedBitmap = Bitmap.createScaledBitmap(
147 | mViewBitmap,
148 | Math.round(mViewBitmap.getWidth() * resizeScale),
149 | Math.round(mViewBitmap.getHeight() * resizeScale),
150 | false);
151 |
152 | if (mViewBitmap != resizedBitmap)
153 | mViewBitmap.recycle();
154 | mViewBitmap = resizedBitmap;
155 | mCurrentScale /= resizeScale;
156 | }
157 | }
158 |
159 | // Rotate if needed
160 | if (mCurrentAngle != 0) {
161 | Matrix tempMatrix = new Matrix();
162 | tempMatrix.setRotate(mCurrentAngle, mViewBitmap.getWidth() / 2, mViewBitmap.getHeight() / 2);
163 |
164 | Bitmap rotatedBitmap = Bitmap.createBitmap(
165 | mViewBitmap,
166 | 0,
167 | 0,
168 | mViewBitmap.getWidth(),
169 | mViewBitmap.getHeight(),
170 | tempMatrix, true);
171 |
172 | if (mViewBitmap != rotatedBitmap)
173 | mViewBitmap.recycle();
174 | mViewBitmap = rotatedBitmap;
175 | }
176 |
177 | int cropOffsetX = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale);
178 | int cropOffsetY = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale);
179 | mCroppedImageWidth = Math.round(mCropRect.width() / mCurrentScale);
180 | mCroppedImageHeight = Math.round(mCropRect.height() / mCurrentScale);
181 |
182 | boolean shouldCrop = shouldCrop(mCroppedImageWidth, mCroppedImageHeight);
183 |
184 | if (shouldCrop) {
185 | Bitmap croppedBitmap = Bitmap.createBitmap(
186 | mViewBitmap,
187 | cropOffsetX,
188 | cropOffsetY,
189 | mCroppedImageWidth,
190 | mCroppedImageHeight);
191 |
192 | OutputStream outputStream = null;
193 | try {
194 | outputStream = new FileOutputStream(outputImagePath);
195 | croppedBitmap.compress(mCompressFormat, mCompressQuality, outputStream);
196 | } catch (Exception e) {
197 | throw new StorageError("");
198 | } finally {
199 | croppedBitmap.recycle();
200 | FileUtils.close(outputStream);
201 | }
202 |
203 | if (mCompressFormat.equals(Bitmap.CompressFormat.JPEG)) {
204 | ExifInterface originalExif = new ExifInterface(mInputImagePath);
205 | ImageHeaderParser.copyExif(originalExif, mCroppedImageWidth, mCroppedImageHeight, outputImagePath);
206 | }
207 | } else {
208 | FileUtils.copyFile(mInputImagePath, outputImagePath);
209 | }
210 | if (mViewBitmap != null && !mViewBitmap.isRecycled()) mViewBitmap.recycle();
211 |
212 | return outputImagePath;
213 | }
214 |
215 | /**
216 | * Check whether an image should be cropped at all or just file can be copied to the destination path.
217 | * For each 1000 pixels there is one pixel of error due to matrix calculations etc.
218 | *
219 | * @param width - crop area width
220 | * @param height - crop area height
221 | * @return - true if image must be cropped, false - if original image fits requirements
222 | */
223 | private boolean shouldCrop(int width, int height) {
224 | int pixelError = 1;
225 | pixelError += Math.round(Math.max(width, height) / 1000f);
226 | return (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0)
227 | || Math.abs(mCropRect.left - mCurrentImageRect.left) > pixelError
228 | || Math.abs(mCropRect.top - mCurrentImageRect.top) > pixelError
229 | || Math.abs(mCropRect.bottom - mCurrentImageRect.bottom) > pixelError
230 | || Math.abs(mCropRect.right - mCurrentImageRect.right) > pixelError;
231 | }
232 |
233 | }
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/Durban.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban;
17 |
18 | import android.app.Activity;
19 | import android.content.Context;
20 | import android.content.Intent;
21 | import android.graphics.Bitmap;
22 | import android.support.annotation.ColorInt;
23 | import android.support.annotation.IntDef;
24 | import android.support.annotation.IntRange;
25 | import android.support.annotation.NonNull;
26 | import android.support.v4.app.Fragment;
27 |
28 | import java.io.OutputStream;
29 | import java.lang.annotation.Retention;
30 | import java.lang.annotation.RetentionPolicy;
31 | import java.lang.reflect.Method;
32 | import java.util.ArrayList;
33 | import java.util.Collections;
34 | import java.util.Locale;
35 |
36 | /**
37 | * Entrance.
38 | * Create by Yan Zhenjie on 2017/5/23.
39 | */
40 | public class Durban {
41 |
42 | private static final String KEY_PREFIX = "AlbumCrop";
43 |
44 | static final String KEY_INPUT_STATUS_COLOR = KEY_PREFIX + ".KEY_INPUT_STATUS_COLOR";
45 | static final String KEY_INPUT_TOOLBAR_COLOR = KEY_PREFIX + ".KEY_INPUT_TOOLBAR_COLOR";
46 | static final String KEY_INPUT_NAVIGATION_COLOR = KEY_PREFIX + ".KEY_INPUT_NAVIGATION_COLOR";
47 | static final String KEY_INPUT_TITLE = KEY_PREFIX + ".KEY_INPUT_TITLE";
48 |
49 | static final String KEY_INPUT_GESTURE = KEY_PREFIX + ".KEY_INPUT_GESTURE";
50 | static final String KEY_INPUT_ASPECT_RATIO = KEY_PREFIX + ".KEY_INPUT_ASPECT_RATIO";
51 | static final String KEY_INPUT_MAX_WIDTH_HEIGHT = KEY_PREFIX + ".KEY_INPUT_MAX_WIDTH_HEIGHT";
52 |
53 | static final String KEY_INPUT_COMPRESS_FORMAT = KEY_PREFIX + ".KEY_INPUT_COMPRESS_FORMAT";
54 | static final String KEY_INPUT_COMPRESS_QUALITY = KEY_PREFIX + ".KEY_INPUT_COMPRESS_QUALITY";
55 |
56 | static final String KEY_INPUT_DIRECTORY = KEY_PREFIX + ".KEY_INPUT_DIRECTORY";
57 | static final String KEY_INPUT_PATH_ARRAY = KEY_PREFIX + ".KEY_INPUT_PATH_ARRAY";
58 |
59 | static final String KEY_INPUT_CONTROLLER = KEY_PREFIX + ".KEY_INPUT_CONTROLLER";
60 |
61 | static final String KEY_OUTPUT_IMAGE_LIST = KEY_PREFIX + ".KEY_OUTPUT_IMAGE_LIST";
62 |
63 | /**
64 | * Do not allow any gestures.
65 | */
66 | public static final int GESTURE_NONE = 0;
67 | /**
68 | * Allow scaling.
69 | */
70 | public static final int GESTURE_SCALE = 1;
71 | /**
72 | * Allow rotation.
73 | */
74 | public static final int GESTURE_ROTATE = 2;
75 | /**
76 | * Allow rotation and scaling.
77 | */
78 | public static final int GESTURE_ALL = 3;
79 |
80 | @IntDef({GESTURE_NONE, GESTURE_SCALE, GESTURE_ROTATE, GESTURE_ALL})
81 | @Retention(RetentionPolicy.SOURCE)
82 | public @interface GestureTypes {
83 | }
84 |
85 | /**
86 | * JPEG format.
87 | */
88 | public static final int COMPRESS_JPEG = 0;
89 | /**
90 | * PNG format.
91 | */
92 | public static final int COMPRESS_PNG = 1;
93 |
94 | @IntDef({COMPRESS_JPEG, COMPRESS_PNG})
95 | @Retention(RetentionPolicy.SOURCE)
96 | public @interface FormatTypes {
97 | }
98 |
99 | private static DurbanConfig sDurbanConfig;
100 |
101 | /**
102 | * Initialize Album.
103 | *
104 | * @param durbanConfig {@link DurbanConfig}.
105 | */
106 | public static void initialize(DurbanConfig durbanConfig) {
107 | sDurbanConfig = durbanConfig;
108 | }
109 |
110 | /**
111 | * Get the durban configuration.
112 | *
113 | * @return {@link DurbanConfig}.
114 | */
115 | public static DurbanConfig getDurbanConfig() {
116 | if (sDurbanConfig == null) {
117 | initialize(DurbanConfig.newBuilder(null)
118 | .setLocale(Locale.getDefault())
119 | .build()
120 | );
121 | }
122 | return sDurbanConfig;
123 | }
124 |
125 |
126 | private Object o;
127 | private Intent mCropIntent;
128 |
129 | public static Durban with(Activity activity) {
130 | return new Durban(activity);
131 | }
132 |
133 | public static Durban with(Fragment fragment) {
134 | return new Durban(fragment);
135 | }
136 |
137 | public static Durban with(android.app.Fragment fragment) {
138 | return new Durban(fragment);
139 | }
140 |
141 | private Durban(Object o) {
142 | this.o = o;
143 | mCropIntent = new Intent(getContext(o), DurbanActivity.class);
144 | }
145 |
146 | /**
147 | * The color of the StatusBar.
148 | */
149 | public Durban statusBarColor(@ColorInt int color) {
150 | mCropIntent.putExtra(KEY_INPUT_STATUS_COLOR, color);
151 | return this;
152 | }
153 |
154 | /**
155 | * The color of the Toolbar.
156 | */
157 | public Durban toolBarColor(@ColorInt int color) {
158 | mCropIntent.putExtra(KEY_INPUT_TOOLBAR_COLOR, color);
159 | return this;
160 | }
161 |
162 | /**
163 | * Set the color of the NavigationBar.
164 | */
165 | public Durban navigationBarColor(@ColorInt int color) {
166 | mCropIntent.putExtra(KEY_INPUT_NAVIGATION_COLOR, color);
167 | return this;
168 | }
169 |
170 | /**
171 | * The title of the interface.
172 | */
173 | public Durban title(String title) {
174 | mCropIntent.putExtra(KEY_INPUT_TITLE, title);
175 | return this;
176 | }
177 |
178 | /**
179 | * The gestures that allow operation.
180 | *
181 | * @param gesture gesture sign.
182 | * @see #GESTURE_NONE
183 | * @see #GESTURE_ALL
184 | * @see #GESTURE_ROTATE
185 | * @see #GESTURE_SCALE
186 | */
187 | public Durban gesture(@GestureTypes int gesture) {
188 | mCropIntent.putExtra(KEY_INPUT_GESTURE, gesture);
189 | return this;
190 | }
191 |
192 | /**
193 | * The aspect ratio column of the crop box.
194 | *
195 | * @param x aspect ratio X.
196 | * @param y aspect ratio Y.
197 | */
198 | public Durban aspectRatio(float x, float y) {
199 | mCropIntent.putExtra(KEY_INPUT_ASPECT_RATIO, new float[]{x, y});
200 | return this;
201 | }
202 |
203 | /**
204 | * Use the aspect ratio column of the original image.
205 | */
206 | public Durban aspectRatioWithSourceImage() {
207 | return aspectRatio(0F, 0F);
208 | }
209 |
210 | /**
211 | * Set maximum size for result cropped image.
212 | *
213 | * @param width max cropped image width.
214 | * @param height max cropped image height.
215 | */
216 | public Durban maxWidthHeight(@IntRange(from = 100) int width, @IntRange(from = 100) int height) {
217 | mCropIntent.putExtra(KEY_INPUT_MAX_WIDTH_HEIGHT, new int[]{width, height});
218 | return this;
219 | }
220 |
221 | /**
222 | * The compression format of the cropped image.
223 | *
224 | * @param format image format.
225 | * @see #COMPRESS_JPEG
226 | * @see #COMPRESS_PNG
227 | */
228 | public Durban compressFormat(@FormatTypes int format) {
229 | mCropIntent.putExtra(KEY_INPUT_COMPRESS_FORMAT, format);
230 | return this;
231 | }
232 |
233 | /**
234 | * The compression quality of the cropped image.
235 | *
236 | * @param quality see {@link Bitmap#compress(Bitmap.CompressFormat, int, OutputStream)}.
237 | * @see Bitmap#compress(Bitmap.CompressFormat, int, OutputStream)
238 | */
239 | public Durban compressQuality(int quality) {
240 | mCropIntent.putExtra(KEY_INPUT_COMPRESS_QUALITY, quality);
241 | return this;
242 | }
243 |
244 | /**
245 | * Set the output directory of the cropped picture.
246 | */
247 | public Durban outputDirectory(String folder) {
248 | mCropIntent.putExtra(KEY_INPUT_DIRECTORY, folder);
249 | return this;
250 | }
251 |
252 | /**
253 | * The pictures to be cropped.
254 | */
255 | public Durban inputImagePaths(String... imagePathArray) {
256 | ArrayList arrayList = new ArrayList<>();
257 | Collections.addAll(arrayList, imagePathArray);
258 | mCropIntent.putStringArrayListExtra(KEY_INPUT_PATH_ARRAY, arrayList);
259 | return this;
260 | }
261 |
262 | /**
263 | * The pictures to be cropped.
264 | */
265 | public Durban inputImagePaths(ArrayList imagePathList) {
266 | mCropIntent.putStringArrayListExtra(KEY_INPUT_PATH_ARRAY, imagePathList);
267 | return this;
268 | }
269 |
270 | /**
271 | * Control panel configuration.
272 | */
273 | public Durban controller(Controller controller) {
274 | mCropIntent.putExtra(KEY_INPUT_CONTROLLER, controller);
275 | return this;
276 | }
277 |
278 | /**
279 | * Request code, callback to {@code onActivityResult()}.
280 | */
281 | public Durban requestCode(int requestCode) {
282 | mCropIntent.putExtra("requestCode", requestCode);
283 | return this;
284 | }
285 |
286 | /**
287 | * Start cropping.
288 | */
289 | public void start() {
290 | try {
291 | Method method = o.getClass().getMethod("startActivityForResult", Intent.class, int.class);
292 | if (!method.isAccessible()) method.setAccessible(true);
293 | method.invoke(o, mCropIntent, mCropIntent.getIntExtra("requestCode", 1));
294 | } catch (Exception e) {
295 | e.printStackTrace();
296 | }
297 | }
298 |
299 | /**
300 | * Analyze the crop results.
301 | */
302 | public static ArrayList parseResult(@NonNull Intent intent) {
303 | return intent.getStringArrayListExtra(KEY_OUTPUT_IMAGE_LIST);
304 | }
305 |
306 | protected static
307 | @NonNull
308 | Context getContext(Object o) {
309 | if (o instanceof Activity) return (Context) o;
310 | else if (o instanceof Fragment) return ((Fragment) o).getContext();
311 | else if (o instanceof android.app.Fragment) ((android.app.Fragment) o).getActivity();
312 | throw new IllegalArgumentException(o.getClass() + " is not supported.");
313 | }
314 |
315 | }
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/view/TransformImageView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.view;
17 |
18 | import android.content.Context;
19 | import android.graphics.Bitmap;
20 | import android.graphics.Matrix;
21 | import android.graphics.RectF;
22 | import android.graphics.drawable.Drawable;
23 | import android.net.Uri;
24 | import android.support.annotation.IntRange;
25 | import android.support.annotation.NonNull;
26 | import android.support.annotation.Nullable;
27 | import android.util.AttributeSet;
28 | import android.util.Log;
29 |
30 | import com.yanzhenjie.durban.callback.BitmapLoadCallback;
31 | import com.yanzhenjie.durban.model.ExifInfo;
32 | import com.yanzhenjie.durban.task.BitmapLoadTask;
33 | import com.yanzhenjie.durban.util.BitmapLoadUtils;
34 | import com.yanzhenjie.durban.util.FastBitmapDrawable;
35 | import com.yanzhenjie.durban.util.RectUtils;
36 |
37 | /**
38 | *
39 | * This class provides base logic to setup the image, transform it with matrix (move, scale, rotate),
40 | * and methods to get current matrix state.
41 | *
42 | * Update by Yan Zhenjie on 2017/5/23.
43 | */
44 | public class TransformImageView extends android.support.v7.widget.AppCompatImageView {
45 |
46 | private static final String TAG = "TransformImageView";
47 |
48 | private static final int RECT_CORNER_POINTS_COORDS = 8;
49 | private static final int RECT_CENTER_POINT_COORDS = 2;
50 | private static final int MATRIX_VALUES_COUNT = 9;
51 |
52 | protected final float[] mCurrentImageCorners = new float[RECT_CORNER_POINTS_COORDS];
53 | protected final float[] mCurrentImageCenter = new float[RECT_CENTER_POINT_COORDS];
54 |
55 | private final float[] mMatrixValues = new float[MATRIX_VALUES_COUNT];
56 |
57 | protected Matrix mCurrentImageMatrix = new Matrix();
58 | protected int mThisWidth, mThisHeight;
59 |
60 | protected TransformImageListener mTransformImageListener;
61 |
62 | private float[] mInitialImageCorners;
63 | private float[] mInitialImageCenter;
64 |
65 | protected boolean mBitmapDecoded = false;
66 | protected boolean mBitmapLaidOut = false;
67 |
68 | private int mMaxBitmapSize = 0;
69 |
70 | private String mImagePath, mOutputDirectory;
71 | private ExifInfo mExifInfo;
72 |
73 | /**
74 | * Interface for rotation and scale change notifying.
75 | */
76 | public interface TransformImageListener {
77 |
78 | void onLoadComplete();
79 |
80 | void onLoadFailure();
81 |
82 | void onRotate(float currentAngle);
83 |
84 | void onScale(float currentScale);
85 | }
86 |
87 | public TransformImageView(Context context) {
88 | this(context, null);
89 | }
90 |
91 | public TransformImageView(Context context, AttributeSet attrs) {
92 | this(context, attrs, 0);
93 | }
94 |
95 | public TransformImageView(Context context, AttributeSet attrs, int defStyle) {
96 | super(context, attrs, defStyle);
97 | init();
98 | }
99 |
100 | public void setTransformImageListener(TransformImageListener transformImageListener) {
101 | mTransformImageListener = transformImageListener;
102 | }
103 |
104 | @Override
105 | public void setScaleType(ScaleType scaleType) {
106 | if (scaleType == ScaleType.MATRIX) {
107 | super.setScaleType(scaleType);
108 | } else {
109 | Log.w(TAG, "Invalid ScaleType. Only ScaleType.MATRIX can be used");
110 | }
111 | }
112 |
113 | /**
114 | * Setter for {@link #mMaxBitmapSize} value.
115 | * Be sure to call it before {@link #setImageURI(Uri)} or other image setters.
116 | *
117 | * @param maxBitmapSize - max size for both width and height of exception that will be used in the view.
118 | */
119 | public void setMaxBitmapSize(int maxBitmapSize) {
120 | mMaxBitmapSize = maxBitmapSize;
121 | }
122 |
123 | public int getMaxBitmapSize() {
124 | if (mMaxBitmapSize <= 0) {
125 | mMaxBitmapSize = BitmapLoadUtils.calculateMaxBitmapSize(getContext());
126 | }
127 | return mMaxBitmapSize;
128 | }
129 |
130 | @Override
131 | public void setImageBitmap(final Bitmap bitmap) {
132 | setImageDrawable(new FastBitmapDrawable(bitmap));
133 | }
134 |
135 | public String getImagePath() {
136 | return mImagePath;
137 | }
138 |
139 | public String getOutputDirectory() {
140 | return mOutputDirectory;
141 | }
142 |
143 | public ExifInfo getExifInfo() {
144 | return mExifInfo;
145 | }
146 |
147 | public void setOutputDirectory(String outputDirectory) {
148 | this.mOutputDirectory = outputDirectory;
149 | }
150 |
151 | /**
152 | * This method takes an Uri as a parameter, then calls method to decode it into Bitmap with specified size.
153 | *
154 | * @param inputImagePath - image Uri
155 | * @throws Exception - can throw exception if having problems with decoding Uri or OOM.
156 | */
157 | public void setImagePath(@NonNull String inputImagePath) throws Exception {
158 | this.mImagePath = inputImagePath;
159 | int maxBitmapSize = getMaxBitmapSize();
160 |
161 | new BitmapLoadTask(
162 | getContext(),
163 | maxBitmapSize,
164 | maxBitmapSize,
165 | new BitmapLoadCallback() {
166 | @Override
167 | public void onSuccessfully(@NonNull Bitmap bitmap, @NonNull ExifInfo exifInfo) {
168 | mExifInfo = exifInfo;
169 | mBitmapDecoded = true;
170 | setImageBitmap(bitmap);
171 | }
172 |
173 | @Override
174 | public void onFailure() {
175 | if (mTransformImageListener != null) {
176 | mTransformImageListener.onLoadFailure();
177 | }
178 | }
179 | }
180 | ).execute(inputImagePath);
181 | }
182 |
183 | /**
184 | * @return - current image scale value.
185 | * [1.0f - for original image, 2.0f - for 200% scaled image, etc.]
186 | */
187 | public float getCurrentScale() {
188 | return getMatrixScale(mCurrentImageMatrix);
189 | }
190 |
191 | /**
192 | * This method calculates scale value for given Matrix object.
193 | */
194 | public float getMatrixScale(@NonNull Matrix matrix) {
195 | return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2)
196 | + Math.pow(getMatrixValue(matrix, Matrix.MSKEW_Y), 2));
197 | }
198 |
199 | /**
200 | * @return - current image rotation angle.
201 | */
202 | public float getCurrentAngle() {
203 | return getMatrixAngle(mCurrentImageMatrix);
204 | }
205 |
206 | /**
207 | * This method calculates rotation angle for given Matrix object.
208 | */
209 | public float getMatrixAngle(@NonNull Matrix matrix) {
210 | return (float) -(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X),
211 | getMatrixValue(matrix, Matrix.MSCALE_X)) * (180 / Math.PI));
212 | }
213 |
214 | @Override
215 | public void setImageMatrix(Matrix matrix) {
216 | super.setImageMatrix(matrix);
217 | mCurrentImageMatrix.set(matrix);
218 | updateCurrentImagePoints();
219 | }
220 |
221 | @Nullable
222 | public Bitmap getViewBitmap() {
223 | if (getDrawable() == null || !(getDrawable() instanceof FastBitmapDrawable)) return null;
224 | else return ((FastBitmapDrawable) getDrawable()).getBitmap();
225 | }
226 |
227 | /**
228 | * This method translates current image.
229 | *
230 | * @param deltaX - horizontal shift
231 | * @param deltaY - vertical shift
232 | */
233 | public void postTranslate(float deltaX, float deltaY) {
234 | if (deltaX != 0 || deltaY != 0) {
235 | mCurrentImageMatrix.postTranslate(deltaX, deltaY);
236 | setImageMatrix(mCurrentImageMatrix);
237 | }
238 | }
239 |
240 | /**
241 | * This method scales current image.
242 | *
243 | * @param deltaScale - scale value
244 | * @param px - scale center X
245 | * @param py - scale center Y
246 | */
247 | public void postScale(float deltaScale, float px, float py) {
248 | if (deltaScale != 0) {
249 | mCurrentImageMatrix.postScale(deltaScale, deltaScale, px, py);
250 | setImageMatrix(mCurrentImageMatrix);
251 | if (mTransformImageListener != null) {
252 | mTransformImageListener.onScale(getMatrixScale(mCurrentImageMatrix));
253 | }
254 | }
255 | }
256 |
257 | /**
258 | * This method rotates current image.
259 | *
260 | * @param deltaAngle - rotation angle
261 | * @param px - rotation center X
262 | * @param py - rotation center Y
263 | */
264 | public void postRotate(float deltaAngle, float px, float py) {
265 | if (deltaAngle != 0) {
266 | mCurrentImageMatrix.postRotate(deltaAngle, px, py);
267 | setImageMatrix(mCurrentImageMatrix);
268 | if (mTransformImageListener != null) {
269 | mTransformImageListener.onRotate(getMatrixAngle(mCurrentImageMatrix));
270 | }
271 | }
272 | }
273 |
274 | protected void init() {
275 | setScaleType(ScaleType.MATRIX);
276 | }
277 |
278 | @Override
279 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
280 | super.onLayout(changed, left, top, right, bottom);
281 | if (changed || (mBitmapDecoded && !mBitmapLaidOut)) {
282 |
283 | left = getPaddingLeft();
284 | top = getPaddingTop();
285 | right = getWidth() - getPaddingRight();
286 | bottom = getHeight() - getPaddingBottom();
287 | mThisWidth = right - left;
288 | mThisHeight = bottom - top;
289 |
290 | onImageLaidOut();
291 | }
292 | }
293 |
294 | /**
295 | * When image is laid out {@link #mInitialImageCenter} and {@link #mInitialImageCenter}
296 | * must be set.
297 | */
298 | protected void onImageLaidOut() {
299 | final Drawable drawable = getDrawable();
300 | if (drawable == null) {
301 | return;
302 | }
303 |
304 | float w = drawable.getIntrinsicWidth();
305 | float h = drawable.getIntrinsicHeight();
306 |
307 | Log.d(TAG, String.format("Image size: [%d:%d]", (int) w, (int) h));
308 |
309 | RectF initialImageRect = new RectF(0, 0, w, h);
310 | mInitialImageCorners = RectUtils.getCornersFromRect(initialImageRect);
311 | mInitialImageCenter = RectUtils.getCenterFromRect(initialImageRect);
312 |
313 | mBitmapLaidOut = true;
314 |
315 | if (mTransformImageListener != null) {
316 | mTransformImageListener.onLoadComplete();
317 | }
318 | }
319 |
320 | /**
321 | * This method returns Matrix value for given index.
322 | *
323 | * @param matrix - valid Matrix object
324 | * @param valueIndex - index of needed value. See {@link Matrix#MSCALE_X} and others.
325 | * @return - matrix value for index
326 | */
327 | protected float getMatrixValue(@NonNull Matrix matrix, @IntRange(from = 0, to = MATRIX_VALUES_COUNT) int valueIndex) {
328 | matrix.getValues(mMatrixValues);
329 | return mMatrixValues[valueIndex];
330 | }
331 |
332 | /**
333 | * This method logs given matrix X, Y, scale, and angle values.
334 | * Can be used for debug.
335 | */
336 | @SuppressWarnings("unused")
337 | protected void printMatrix(@NonNull String logPrefix, @NonNull Matrix matrix) {
338 | float x = getMatrixValue(matrix, Matrix.MTRANS_X);
339 | float y = getMatrixValue(matrix, Matrix.MTRANS_Y);
340 | float rScale = getMatrixScale(matrix);
341 | float rAngle = getMatrixAngle(matrix);
342 | Log.d(TAG, logPrefix + ": matrix: { x: " + x + ", y: " + y + ", scale: " + rScale + ", angle: " + rAngle + " }");
343 | }
344 |
345 | /**
346 | * This method updates current image corners and center points that are stored in
347 | * {@link #mCurrentImageCorners} and {@link #mCurrentImageCenter} arrays.
348 | * Those are used for several calculations.
349 | */
350 | private void updateCurrentImagePoints() {
351 | mCurrentImageMatrix.mapPoints(mCurrentImageCorners, mInitialImageCorners);
352 | mCurrentImageMatrix.mapPoints(mCurrentImageCenter, mInitialImageCenter);
353 | }
354 |
355 | }
356 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/util/ImageHeaderParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban.util;
17 |
18 | import android.media.ExifInterface;
19 | import android.text.TextUtils;
20 | import android.util.Log;
21 |
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.nio.ByteBuffer;
25 | import java.nio.ByteOrder;
26 | import java.nio.charset.Charset;
27 |
28 | /**
29 | * Update by Yan Zhenjie on 2017/5/23.
30 | */
31 | public class ImageHeaderParser {
32 | private static final String TAG = "ImageHeaderParser";
33 | /**
34 | * A constant indicating we were unable to parse the orientation from the image either because
35 | * no exif segment containing orientation data existed, or because of an I/O error attempting to
36 | * read the exif segment.
37 | */
38 | public static final int UNKNOWN_ORIENTATION = -1;
39 |
40 | private static final int EXIF_MAGIC_NUMBER = 0xFFD8;
41 | // "MM".
42 | private static final int MOTOROLA_TIFF_MAGIC_NUMBER = 0x4D4D;
43 | // "II".
44 | private static final int INTEL_TIFF_MAGIC_NUMBER = 0x4949;
45 | private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
46 | private static final byte[] JPEG_EXIF_SEGMENT_PREAMBLE_BYTES =
47 | JPEG_EXIF_SEGMENT_PREAMBLE.getBytes(Charset.forName("UTF-8"));
48 | private static final int SEGMENT_SOS = 0xDA;
49 | private static final int MARKER_EOI = 0xD9;
50 | private static final int SEGMENT_START_ID = 0xFF;
51 | private static final int EXIF_SEGMENT_TYPE = 0xE1;
52 | private static final int ORIENTATION_TAG_TYPE = 0x0112;
53 | private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
54 |
55 | private final Reader reader;
56 |
57 | public ImageHeaderParser(InputStream is) {
58 | reader = new StreamReader(is);
59 | }
60 |
61 | /**
62 | * Parse the orientation from the image header. If it doesn't handle this image type (or this is
63 | * not an image) it will return a default value rather than throwing an exception.
64 | *
65 | * @return The exif orientation if present or -1 if the header couldn't be parsed or doesn't
66 | * contain an orientation
67 | * @throws IOException
68 | */
69 | public int getOrientation() throws IOException {
70 | final int magicNumber = reader.getUInt16();
71 |
72 | if (!handles(magicNumber)) {
73 | if (Log.isLoggable(TAG, Log.DEBUG)) {
74 | Log.d(TAG, "Parser doesn't handle magic number: " + magicNumber);
75 | }
76 | return UNKNOWN_ORIENTATION;
77 | } else {
78 | int exifSegmentLength = moveToExifSegmentAndGetLength();
79 | if (exifSegmentLength == -1) {
80 | if (Log.isLoggable(TAG, Log.DEBUG)) {
81 | Log.d(TAG, "Failed to parse exif segment length, or exif segment not found");
82 | }
83 | return UNKNOWN_ORIENTATION;
84 | }
85 |
86 | byte[] exifData = new byte[exifSegmentLength];
87 | return parseExifSegment(exifData, exifSegmentLength);
88 | }
89 | }
90 |
91 | private int parseExifSegment(byte[] tempArray, int exifSegmentLength) throws IOException {
92 | int read = reader.read(tempArray, exifSegmentLength);
93 | if (read != exifSegmentLength) {
94 | if (Log.isLoggable(TAG, Log.DEBUG)) {
95 | Log.d(TAG, "Unable to read exif segment data"
96 | + ", length: " + exifSegmentLength
97 | + ", actually read: " + read);
98 | }
99 | return UNKNOWN_ORIENTATION;
100 | }
101 |
102 | boolean hasJpegExifPreamble = hasJpegExifPreamble(tempArray, exifSegmentLength);
103 | if (hasJpegExifPreamble) {
104 | return parseExifSegment(new RandomAccessReader(tempArray, exifSegmentLength));
105 | } else {
106 | if (Log.isLoggable(TAG, Log.DEBUG)) {
107 | Log.d(TAG, "Missing jpeg exif preamble");
108 | }
109 | return UNKNOWN_ORIENTATION;
110 | }
111 | }
112 |
113 | private boolean hasJpegExifPreamble(byte[] exifData, int exifSegmentLength) {
114 | boolean result =
115 | exifData != null && exifSegmentLength > JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length;
116 | if (result) {
117 | for (int i = 0; i < JPEG_EXIF_SEGMENT_PREAMBLE_BYTES.length; i++) {
118 | if (exifData[i] != JPEG_EXIF_SEGMENT_PREAMBLE_BYTES[i]) {
119 | result = false;
120 | break;
121 | }
122 | }
123 | }
124 | return result;
125 | }
126 |
127 | /**
128 | * Moves reader to the start of the exif segment and returns the length of the exif segment or
129 | * {@code -1} if no exif segment is found.
130 | */
131 | private int moveToExifSegmentAndGetLength() throws IOException {
132 | short segmentId, segmentType;
133 | int segmentLength;
134 | while (true) {
135 | segmentId = reader.getUInt8();
136 | if (segmentId != SEGMENT_START_ID) {
137 | if (Log.isLoggable(TAG, Log.DEBUG)) {
138 | Log.d(TAG, "Unknown segmentId=" + segmentId);
139 | }
140 | return -1;
141 | }
142 |
143 | segmentType = reader.getUInt8();
144 |
145 | if (segmentType == SEGMENT_SOS) {
146 | return -1;
147 | } else if (segmentType == MARKER_EOI) {
148 | if (Log.isLoggable(TAG, Log.DEBUG)) {
149 | Log.d(TAG, "Found MARKER_EOI in exif segment");
150 | }
151 | return -1;
152 | }
153 |
154 | // Segment length includes bytes for segment length.
155 | segmentLength = reader.getUInt16() - 2;
156 |
157 | if (segmentType != EXIF_SEGMENT_TYPE) {
158 | long skipped = reader.skip(segmentLength);
159 | if (skipped != segmentLength) {
160 | if (Log.isLoggable(TAG, Log.DEBUG)) {
161 | Log.d(TAG, "Unable to skip enough data"
162 | + ", type: " + segmentType
163 | + ", wanted to skip: " + segmentLength
164 | + ", but actually skipped: " + skipped);
165 | }
166 | return -1;
167 | }
168 | } else {
169 | return segmentLength;
170 | }
171 | }
172 | }
173 |
174 | private static int parseExifSegment(RandomAccessReader segmentData) {
175 | final int headerOffsetSize = JPEG_EXIF_SEGMENT_PREAMBLE.length();
176 |
177 | short byteOrderIdentifier = segmentData.getInt16(headerOffsetSize);
178 | final ByteOrder byteOrder;
179 | if (byteOrderIdentifier == MOTOROLA_TIFF_MAGIC_NUMBER) {
180 | byteOrder = ByteOrder.BIG_ENDIAN;
181 | } else if (byteOrderIdentifier == INTEL_TIFF_MAGIC_NUMBER) {
182 | byteOrder = ByteOrder.LITTLE_ENDIAN;
183 | } else {
184 | if (Log.isLoggable(TAG, Log.DEBUG)) {
185 | Log.d(TAG, "Unknown endianness = " + byteOrderIdentifier);
186 | }
187 | byteOrder = ByteOrder.BIG_ENDIAN;
188 | }
189 |
190 | segmentData.order(byteOrder);
191 |
192 | int firstIfdOffset = segmentData.getInt32(headerOffsetSize + 4) + headerOffsetSize;
193 | int tagCount = segmentData.getInt16(firstIfdOffset);
194 |
195 | int tagOffset, tagType, formatCode, componentCount;
196 | for (int i = 0; i < tagCount; i++) {
197 | tagOffset = calcTagOffset(firstIfdOffset, i);
198 | tagType = segmentData.getInt16(tagOffset);
199 |
200 | // We only want orientation.
201 | if (tagType != ORIENTATION_TAG_TYPE) {
202 | continue;
203 | }
204 |
205 | formatCode = segmentData.getInt16(tagOffset + 2);
206 |
207 | // 12 is max format code.
208 | if (formatCode < 1 || formatCode > 12) {
209 | if (Log.isLoggable(TAG, Log.DEBUG)) {
210 | Log.d(TAG, "Got invalid format code = " + formatCode);
211 | }
212 | continue;
213 | }
214 |
215 | componentCount = segmentData.getInt32(tagOffset + 4);
216 |
217 | if (componentCount < 0) {
218 | if (Log.isLoggable(TAG, Log.DEBUG)) {
219 | Log.d(TAG, "Negative tiff component count");
220 | }
221 | continue;
222 | }
223 |
224 | if (Log.isLoggable(TAG, Log.DEBUG)) {
225 | Log.d(TAG, "Got tagIndex=" + i + " tagType=" + tagType + " formatCode=" + formatCode
226 | + " componentCount=" + componentCount);
227 | }
228 |
229 | final int byteCount = componentCount + BYTES_PER_FORMAT[formatCode];
230 |
231 | if (byteCount > 4) {
232 | if (Log.isLoggable(TAG, Log.DEBUG)) {
233 | Log.d(TAG, "Got byte count > 4, not orientation, continuing, formatCode=" + formatCode);
234 | }
235 | continue;
236 | }
237 |
238 | final int tagValueOffset = tagOffset + 8;
239 |
240 | if (tagValueOffset < 0 || tagValueOffset > segmentData.length()) {
241 | if (Log.isLoggable(TAG, Log.DEBUG)) {
242 | Log.d(TAG, "Illegal tagValueOffset=" + tagValueOffset + " tagType=" + tagType);
243 | }
244 | continue;
245 | }
246 |
247 | if (byteCount < 0 || tagValueOffset + byteCount > segmentData.length()) {
248 | if (Log.isLoggable(TAG, Log.DEBUG)) {
249 | Log.d(TAG, "Illegal number of bytes for TI tag data tagType=" + tagType);
250 | }
251 | continue;
252 | }
253 |
254 | //assume componentCount == 1 && fmtCode == 3
255 | return segmentData.getInt16(tagValueOffset);
256 | }
257 |
258 | return -1;
259 | }
260 |
261 | private static int calcTagOffset(int ifdOffset, int tagIndex) {
262 | return ifdOffset + 2 + 12 * tagIndex;
263 | }
264 |
265 | private static boolean handles(int imageMagicNumber) {
266 | return (imageMagicNumber & EXIF_MAGIC_NUMBER) == EXIF_MAGIC_NUMBER
267 | || imageMagicNumber == MOTOROLA_TIFF_MAGIC_NUMBER
268 | || imageMagicNumber == INTEL_TIFF_MAGIC_NUMBER;
269 | }
270 |
271 | private static class RandomAccessReader {
272 | private final ByteBuffer data;
273 |
274 | public RandomAccessReader(byte[] data, int length) {
275 | this.data = (ByteBuffer) ByteBuffer.wrap(data)
276 | .order(ByteOrder.BIG_ENDIAN)
277 | .limit(length);
278 | }
279 |
280 | public void order(ByteOrder byteOrder) {
281 | this.data.order(byteOrder);
282 | }
283 |
284 | public int length() {
285 | return data.remaining();
286 | }
287 |
288 | public int getInt32(int offset) {
289 | return data.getInt(offset);
290 | }
291 |
292 | public short getInt16(int offset) {
293 | return data.getShort(offset);
294 | }
295 | }
296 |
297 | private interface Reader {
298 | int getUInt16() throws IOException;
299 |
300 | short getUInt8() throws IOException;
301 |
302 | long skip(long total) throws IOException;
303 |
304 | int read(byte[] buffer, int byteCount) throws IOException;
305 | }
306 |
307 | private static class StreamReader implements Reader {
308 | private final InputStream is;
309 |
310 | // Motorola / big endian byte order.
311 | public StreamReader(InputStream is) {
312 | this.is = is;
313 | }
314 |
315 | @Override
316 | public int getUInt16() throws IOException {
317 | return (is.read() << 8 & 0xFF00) | (is.read() & 0xFF);
318 | }
319 |
320 | @Override
321 | public short getUInt8() throws IOException {
322 | return (short) (is.read() & 0xFF);
323 | }
324 |
325 | @Override
326 | public long skip(long total) throws IOException {
327 | if (total < 0) {
328 | return 0;
329 | }
330 |
331 | long toSkip = total;
332 | while (toSkip > 0) {
333 | long skipped = is.skip(toSkip);
334 | if (skipped > 0) {
335 | toSkip -= skipped;
336 | } else {
337 | // Skip has no specific contract as to what happens when you reach the end of
338 | // the stream. To differentiate between temporarily not having more data and
339 | // having finished the stream, we read a single byte when we fail to skip any
340 | // amount of data.
341 | int testEofByte = is.read();
342 | if (testEofByte == -1) {
343 | break;
344 | } else {
345 | toSkip--;
346 | }
347 | }
348 | }
349 | return total - toSkip;
350 | }
351 |
352 | @Override
353 | public int read(byte[] buffer, int byteCount) throws IOException {
354 | int toRead = byteCount;
355 | int read;
356 | while (toRead > 0 && ((read = is.read(buffer, byteCount - toRead, toRead)) != -1)) {
357 | toRead -= read;
358 | }
359 | return byteCount - toRead;
360 | }
361 | }
362 |
363 | public static void copyExif(ExifInterface originalExif, int width, int height, String imageOutputPath) {
364 | String[] attributes = new String[]{
365 | ExifInterface.TAG_APERTURE,
366 | ExifInterface.TAG_DATETIME,
367 | ExifInterface.TAG_DATETIME_DIGITIZED,
368 | ExifInterface.TAG_EXPOSURE_TIME,
369 | ExifInterface.TAG_FLASH,
370 | ExifInterface.TAG_FOCAL_LENGTH,
371 | ExifInterface.TAG_GPS_ALTITUDE,
372 | ExifInterface.TAG_GPS_ALTITUDE_REF,
373 | ExifInterface.TAG_GPS_DATESTAMP,
374 | ExifInterface.TAG_GPS_LATITUDE,
375 | ExifInterface.TAG_GPS_LATITUDE_REF,
376 | ExifInterface.TAG_GPS_LONGITUDE,
377 | ExifInterface.TAG_GPS_LONGITUDE_REF,
378 | ExifInterface.TAG_GPS_PROCESSING_METHOD,
379 | ExifInterface.TAG_GPS_TIMESTAMP,
380 | ExifInterface.TAG_ISO,
381 | ExifInterface.TAG_MAKE,
382 | ExifInterface.TAG_MODEL,
383 | ExifInterface.TAG_SUBSEC_TIME,
384 | ExifInterface.TAG_SUBSEC_TIME_DIG,
385 | ExifInterface.TAG_SUBSEC_TIME_ORIG,
386 | ExifInterface.TAG_WHITE_BALANCE
387 | };
388 |
389 | try {
390 | ExifInterface newExif = new ExifInterface(imageOutputPath);
391 | String value;
392 | for (String attribute : attributes) {
393 | value = originalExif.getAttribute(attribute);
394 | if (!TextUtils.isEmpty(value)) {
395 | newExif.setAttribute(attribute, value);
396 | }
397 | }
398 | newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width));
399 | newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height));
400 | newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "0");
401 |
402 | newExif.saveAttributes();
403 |
404 | } catch (IOException e) {
405 | Log.d(TAG, e.getMessage());
406 | }
407 | }
408 |
409 | }
410 |
411 |
--------------------------------------------------------------------------------
/durban/src/main/java/com/yanzhenjie/durban/DurbanActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright © Yan Zhenjie
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.yanzhenjie.durban;
17 |
18 | import android.Manifest;
19 | import android.content.Intent;
20 | import android.content.pm.PackageManager;
21 | import android.graphics.Bitmap;
22 | import android.os.Build;
23 | import android.os.Bundle;
24 | import android.support.annotation.NonNull;
25 | import android.support.v4.app.ActivityCompat;
26 | import android.support.v4.content.ContextCompat;
27 | import android.support.v4.view.ViewCompat;
28 | import android.support.v7.app.ActionBar;
29 | import android.support.v7.app.AppCompatActivity;
30 | import android.support.v7.widget.Toolbar;
31 | import android.text.TextUtils;
32 | import android.util.Log;
33 | import android.view.Menu;
34 | import android.view.MenuItem;
35 | import android.view.View;
36 | import android.view.Window;
37 | import android.view.WindowManager;
38 | import android.view.animation.AccelerateInterpolator;
39 |
40 | import com.yanzhenjie.durban.callback.BitmapCropCallback;
41 | import com.yanzhenjie.durban.util.DurbanUtils;
42 | import com.yanzhenjie.durban.view.CropView;
43 | import com.yanzhenjie.durban.view.GestureCropImageView;
44 | import com.yanzhenjie.durban.view.OverlayView;
45 | import com.yanzhenjie.durban.view.TransformImageView;
46 |
47 | import java.util.ArrayList;
48 | import java.util.Locale;
49 |
50 | /**
51 | * Update by Yan Zhenjie on 2017/5/23.
52 | */
53 | public class DurbanActivity extends AppCompatActivity {
54 |
55 | private static final int PERMISSION_CODE_STORAGE = 1;
56 |
57 | private int mStatusColor;
58 | private int mNavigationColor;
59 | private int mToolbarColor;
60 | private String mTitle;
61 |
62 | private int mGesture;
63 | private float[] mAspectRatio;
64 | private int[] mMaxWidthHeight;
65 |
66 | private Bitmap.CompressFormat mCompressFormat;
67 | private int mCompressQuality;
68 |
69 | private String mOutputDirectory;
70 | private ArrayList mInputPathList;
71 |
72 | private Controller mController;
73 |
74 | private CropView mCropView;
75 | private GestureCropImageView mCropImageView;
76 |
77 | private ArrayList mOutputPathList;
78 |
79 | @Override
80 | public void onCreate(Bundle savedInstanceState) {
81 | super.onCreate(savedInstanceState);
82 | Locale locale = Durban.getDurbanConfig().getLocale();
83 | DurbanUtils.applyLanguageForContext(this, locale);
84 |
85 | setContentView(R.layout.durban_activity_photobox);
86 | final Intent intent = getIntent();
87 |
88 | initArgument(intent);
89 | initFrameViews();
90 | initContentViews();
91 | initControllerViews();
92 | cropNextImage();
93 | }
94 |
95 | @Override
96 | protected void onStop() {
97 | super.onStop();
98 | if (mCropImageView != null) mCropImageView.cancelAllAnimations();
99 | }
100 |
101 | private void initArgument(Intent intent) {
102 | mStatusColor = ContextCompat.getColor(this, R.color.durban_ColorPrimaryDark);
103 | mToolbarColor = ContextCompat.getColor(this, R.color.durban_ColorPrimary);
104 | mNavigationColor = ContextCompat.getColor(this, R.color.durban_ColorPrimaryBlack);
105 |
106 | mStatusColor = intent.getIntExtra(Durban.KEY_INPUT_STATUS_COLOR, mStatusColor);
107 | mToolbarColor = intent.getIntExtra(Durban.KEY_INPUT_TOOLBAR_COLOR, mToolbarColor);
108 | mNavigationColor = intent.getIntExtra(Durban.KEY_INPUT_NAVIGATION_COLOR, mNavigationColor);
109 | mTitle = intent.getStringExtra(Durban.KEY_INPUT_TITLE);
110 | if (TextUtils.isEmpty(mTitle)) mTitle = getString(R.string.durban_title_crop);
111 |
112 | mGesture = intent.getIntExtra(Durban.KEY_INPUT_GESTURE, Durban.GESTURE_ALL);
113 | mAspectRatio = intent.getFloatArrayExtra(Durban.KEY_INPUT_ASPECT_RATIO);
114 | if (mAspectRatio == null) mAspectRatio = new float[]{0, 0};
115 | mMaxWidthHeight = intent.getIntArrayExtra(Durban.KEY_INPUT_MAX_WIDTH_HEIGHT);
116 | if (mMaxWidthHeight == null) mMaxWidthHeight = new int[]{500, 500};
117 |
118 | //noinspection JavacQuirks
119 | int compressFormat = intent.getIntExtra(Durban.KEY_INPUT_COMPRESS_FORMAT, 0);
120 | mCompressFormat = compressFormat == Durban.COMPRESS_PNG ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG;
121 | mCompressQuality = intent.getIntExtra(Durban.KEY_INPUT_COMPRESS_QUALITY, 90);
122 |
123 | mOutputDirectory = intent.getStringExtra(Durban.KEY_INPUT_DIRECTORY);
124 | if (TextUtils.isEmpty(mOutputDirectory)) mOutputDirectory = getFilesDir().getAbsolutePath();
125 | mInputPathList = intent.getStringArrayListExtra(Durban.KEY_INPUT_PATH_ARRAY);
126 |
127 | mController = intent.getParcelableExtra(Durban.KEY_INPUT_CONTROLLER);
128 | if (mController == null) mController = Controller.newBuilder().build();
129 |
130 | mOutputPathList = new ArrayList<>();
131 | }
132 |
133 | private void initFrameViews() {
134 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
135 | final Window window = getWindow();
136 | if (window != null) {
137 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
138 | window.setStatusBarColor(mStatusColor);
139 | window.setNavigationBarColor(mNavigationColor);
140 | }
141 | }
142 |
143 | final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
144 | toolbar.setBackgroundColor(mToolbarColor);
145 | setSupportActionBar(toolbar);
146 |
147 | final ActionBar actionBar = getSupportActionBar();
148 | assert actionBar != null;
149 | actionBar.setDefaultDisplayHomeAsUpEnabled(true);
150 | actionBar.setTitle(mTitle);
151 | }
152 |
153 | private void initContentViews() {
154 | mCropView = (CropView) findViewById(R.id.crop_view);
155 | mCropImageView = mCropView.getCropImageView();
156 | mCropImageView.setOutputDirectory(mOutputDirectory);
157 | mCropImageView.setTransformImageListener(mImageListener);
158 | mCropImageView.setScaleEnabled(mGesture == Durban.GESTURE_ALL || mGesture == Durban.GESTURE_SCALE);
159 | mCropImageView.setRotateEnabled(mGesture == Durban.GESTURE_ALL || mGesture == Durban.GESTURE_ROTATE);
160 | // Durban image view options
161 | mCropImageView.setMaxBitmapSize(GestureCropImageView.DEFAULT_MAX_BITMAP_SIZE);
162 | mCropImageView.setMaxScaleMultiplier(GestureCropImageView.DEFAULT_MAX_SCALE_MULTIPLIER);
163 | mCropImageView.setImageToWrapCropBoundsAnimDuration(GestureCropImageView.DEFAULT_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION);
164 |
165 | // Overlay view options
166 | OverlayView overlayView = mCropView.getOverlayView();
167 | overlayView.setFreestyleCropMode(OverlayView.FREESTYLE_CROP_MODE_DISABLE);
168 | overlayView.setDimmedColor(ContextCompat.getColor(this, R.color.durban_CropDimmed));
169 | overlayView.setCircleDimmedLayer(false);
170 | overlayView.setShowCropFrame(true);
171 | overlayView.setCropFrameColor(ContextCompat.getColor(this, R.color.durban_CropFrameLine));
172 | overlayView.setCropFrameStrokeWidth(getResources().getDimensionPixelSize(R.dimen.durban_dp_1));
173 | overlayView.setShowCropGrid(true);
174 | overlayView.setCropGridRowCount(2);
175 | overlayView.setCropGridColumnCount(2);
176 | overlayView.setCropGridColor(ContextCompat.getColor(this, R.color.durban_CropGridLine));
177 | overlayView.setCropGridStrokeWidth(getResources().getDimensionPixelSize(R.dimen.durban_dp_1));
178 |
179 | // Aspect ratio options
180 | if (mAspectRatio[0] > 0 && mAspectRatio[1] > 0) mCropImageView.setTargetAspectRatio(mAspectRatio[0] / mAspectRatio[1]);
181 | else mCropImageView.setTargetAspectRatio(GestureCropImageView.SOURCE_IMAGE_ASPECT_RATIO);
182 |
183 | // Result exception max size options
184 | if (mMaxWidthHeight[0] > 0 && mMaxWidthHeight[1] > 0) {
185 | mCropImageView.setMaxResultImageSizeX(mMaxWidthHeight[0]);
186 | mCropImageView.setMaxResultImageSizeY(mMaxWidthHeight[1]);
187 | }
188 | }
189 |
190 | private TransformImageView.TransformImageListener mImageListener = new TransformImageView.TransformImageListener() {
191 | @Override
192 | public void onRotate(float currentAngle) {
193 | }
194 |
195 | @Override
196 | public void onScale(float currentScale) {
197 | }
198 |
199 | @Override
200 | public void onLoadComplete() {
201 | ViewCompat.animate(mCropView)
202 | .alpha(1)
203 | .setDuration(300)
204 | .setInterpolator(new AccelerateInterpolator());
205 | }
206 |
207 | @Override
208 | public void onLoadFailure() {
209 | cropNextImage();
210 | }
211 | };
212 |
213 | private void initControllerViews() {
214 | View controllerRoot = findViewById(R.id.iv_controller_root);
215 |
216 | View rotationTitle = findViewById(R.id.tv_controller_title_rotation);
217 | View rotationLeft = findViewById(R.id.layout_controller_rotation_left);
218 | View rotationRight = findViewById(R.id.layout_controller_rotation_right);
219 | View scaleTitle = findViewById(R.id.tv_controller_title_scale);
220 | View scaleBig = findViewById(R.id.layout_controller_scale_big);
221 | View scaleSmall = findViewById(R.id.layout_controller_scale_small);
222 |
223 | controllerRoot.setVisibility(mController.isEnable() ? View.VISIBLE : View.GONE);
224 |
225 | rotationTitle.setVisibility(mController.isRotationTitle() ? View.VISIBLE : View.INVISIBLE);
226 | rotationLeft.setVisibility(mController.isRotation() ? View.VISIBLE : View.GONE);
227 | rotationRight.setVisibility(mController.isRotation() ? View.VISIBLE : View.GONE);
228 | scaleTitle.setVisibility(mController.isScaleTitle() ? View.VISIBLE : View.INVISIBLE);
229 | scaleBig.setVisibility(mController.isScale() ? View.VISIBLE : View.GONE);
230 | scaleSmall.setVisibility(mController.isScale() ? View.VISIBLE : View.GONE);
231 |
232 | if (!mController.isRotationTitle() && !mController.isScaleTitle())
233 | findViewById(R.id.layout_controller_title_root).setVisibility(View.GONE);
234 | if (!mController.isRotation())
235 | rotationTitle.setVisibility(View.GONE);
236 | if (!mController.isScale())
237 | scaleTitle.setVisibility(View.GONE);
238 |
239 | rotationLeft.setOnClickListener(mControllerClick);
240 | rotationRight.setOnClickListener(mControllerClick);
241 | scaleBig.setOnClickListener(mControllerClick);
242 | scaleSmall.setOnClickListener(mControllerClick);
243 | }
244 |
245 | private View.OnClickListener mControllerClick = new View.OnClickListener() {
246 | @Override
247 | public void onClick(View v) {
248 | int id = v.getId();
249 | if (id == R.id.layout_controller_rotation_left) {
250 | mCropImageView.postRotate(-90);
251 | mCropImageView.setImageToWrapCropBounds();
252 | } else if (id == R.id.layout_controller_rotation_right) {
253 | mCropImageView.postRotate(90);
254 | mCropImageView.setImageToWrapCropBounds();
255 | } else if (id == R.id.layout_controller_scale_big) {
256 | mCropImageView.zoomOutImage(mCropImageView.getCurrentScale()
257 | + ((mCropImageView.getMaxScale() - mCropImageView.getMinScale()) / 10));
258 | mCropImageView.setImageToWrapCropBounds();
259 | } else if (id == R.id.layout_controller_scale_small) {
260 | mCropImageView.zoomInImage(mCropImageView.getCurrentScale()
261 | - ((mCropImageView.getMaxScale() - mCropImageView.getMinScale()) / 10));
262 | mCropImageView.setImageToWrapCropBounds();
263 | }
264 | }
265 | };
266 |
267 | /**
268 | * Start cropping and request permission if there is no permission.
269 | */
270 | private void cropNextImage() {
271 | resetRotation();
272 | requestStoragePermission(PERMISSION_CODE_STORAGE);
273 | }
274 |
275 | /**
276 | * Restore the rotation angle.
277 | */
278 | private void resetRotation() {
279 | mCropImageView.postRotate(-mCropImageView.getCurrentAngle());
280 | mCropImageView.setImageToWrapCropBounds();
281 | }
282 |
283 | private void requestStoragePermission(int requestCode) {
284 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
285 | int permissionResult = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
286 | if (permissionResult == PackageManager.PERMISSION_GRANTED) {
287 | onRequestPermissionsResult(
288 | requestCode,
289 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
290 | new int[]{PackageManager.PERMISSION_GRANTED});
291 | } else {
292 | ActivityCompat.requestPermissions(
293 | this,
294 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE},
295 | requestCode);
296 | }
297 | } else {
298 | onRequestPermissionsResult(
299 | requestCode,
300 | new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
301 | new int[]{PackageManager.PERMISSION_GRANTED});
302 | }
303 | }
304 |
305 | @Override
306 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
307 | switch (requestCode) {
308 | case PERMISSION_CODE_STORAGE: {
309 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
310 | cropNextImageWithPermission();
311 | } else {
312 | Log.e("Durban", "Storage device permission is denied.");
313 | setResultFailure();
314 | }
315 | break;
316 | }
317 | }
318 | }
319 |
320 | private void cropNextImageWithPermission() {
321 | if (mInputPathList != null) {
322 | if (mInputPathList.size() > 0) {
323 | String currentPath = mInputPathList.remove(0);
324 | try {
325 | mCropImageView.setImagePath(currentPath);
326 | } catch (Exception e) {
327 | cropNextImage();
328 | }
329 | } else if (mOutputPathList.size() > 0) setResultSuccessful();
330 | else setResultFailure();
331 | } else {
332 | Log.e("Durban", "The file list is empty.");
333 | setResultFailure();
334 | }
335 | }
336 |
337 | @Override
338 | public boolean onCreateOptionsMenu(final Menu menu) {
339 | getMenuInflater().inflate(R.menu.durban_menu_activity, menu);
340 | return true;
341 | }
342 |
343 | @Override
344 | public boolean onOptionsItemSelected(MenuItem item) {
345 | if (item.getItemId() == R.id.menu_action_ok) {
346 | cropAndSaveImage();
347 | } else if (item.getItemId() == android.R.id.home) {
348 | setResultFailure();
349 | }
350 | return true;
351 | }
352 |
353 | private void cropAndSaveImage() {
354 | mCropImageView.cropAndSaveImage(mCompressFormat, mCompressQuality, cropCallback);
355 | }
356 |
357 | private BitmapCropCallback cropCallback = new BitmapCropCallback() {
358 | @Override
359 | public void onBitmapCropped(@NonNull String imagePath, int imageWidth, int imageHeight) {
360 | mOutputPathList.add(imagePath);
361 | cropNextImage();
362 | }
363 |
364 | @Override
365 | public void onCropFailure(@NonNull Throwable t) {
366 | cropNextImage();
367 | }
368 | };
369 |
370 | private void setResultSuccessful() {
371 | Intent intent = new Intent();
372 | intent.putStringArrayListExtra(Durban.KEY_OUTPUT_IMAGE_LIST, mOutputPathList);
373 | setResult(RESULT_OK, intent);
374 | finish();
375 | }
376 |
377 | private void setResultFailure() {
378 | Intent intent = new Intent();
379 | intent.putStringArrayListExtra(Durban.KEY_OUTPUT_IMAGE_LIST, mOutputPathList);
380 | setResult(RESULT_CANCELED, intent);
381 | finish();
382 | }
383 |
384 | }
--------------------------------------------------------------------------------