├── .gitignore
├── .idea
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── misc.xml
├── modules.xml
└── runConfigurations.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── qiaomu
│ │ └── cropimage
│ │ └── MainActivity.java
│ └── res
│ ├── drawable-hdpi
│ ├── camera_crop_height.png
│ ├── camera_crop_width.png
│ ├── detail_photo_border.9.png
│ ├── ic_menu_3d_globe.png
│ ├── ic_menu_camera_video_view.png
│ ├── ic_menu_view_details.png
│ └── indicator_autocrop.png
│ ├── drawable-xhdpi
│ ├── camera_crop_height.png
│ ├── camera_crop_width.png
│ ├── detail_photo_border.9.png
│ ├── ic_menu_3d_globe.png
│ ├── ic_menu_camera_video_view.png
│ ├── ic_menu_view_details.png
│ └── indicator_autocrop.png
│ ├── layout
│ ├── activity_main.xml
│ ├── cropimage.xml
│ └── detailsview.xml
│ ├── 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
│ ├── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── file_paths.xml
├── art
├── 1.gif
└── 2.gif
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── libcrop
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── android
│ │ └── gallery3d
│ │ └── crop
│ │ ├── BoundedRect.java
│ │ ├── CropActivity.java
│ │ ├── CropDrawingUtils.java
│ │ ├── CropExtras.java
│ │ ├── CropMath.java
│ │ ├── CropObject.java
│ │ ├── CropView.java
│ │ ├── GeometryMathUtils.java
│ │ ├── ImageLoader.java
│ │ ├── SaveImage.java
│ │ └── Utils.java
│ └── res
│ ├── drawable-xhdpi
│ ├── camera_crop.png
│ ├── ic_crop_cancel.png
│ ├── ic_crop_ok.png
│ ├── ic_menu_savephoto.png
│ └── ic_menu_savephoto_disabled.png
│ ├── drawable
│ ├── filtershow_button_background.xml
│ ├── filtershow_button_selected_background.9.png
│ ├── geometry_shadow.9.png
│ └── menu_save_photo.xml
│ ├── layout
│ ├── crop_activity.xml
│ └── filtershow_actionbar.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ └── strings.xml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 mrme2014
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # imageCrop
2 | # android6.0原生系统相册裁剪源码抽取改进
3 |
4 | [](https://996.icu)
5 | [-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE)
6 |
7 | ### 由于安卓手机各个ROM从4.0到7.0 相册功能各有差异,从4.0以后原生都不在支持圆形裁剪,而各个厂家可能,注意可能会把自己ROM中相册增加圆形裁剪功能。
8 | ### 这样的话,调用原生系统裁剪就会有兼容问题。
9 |
10 | ### 于是乎,我把android6.0原生系统相册裁剪源码抽取了出来并改进,使用方式沿用Intent意图。
11 |
12 | - 支持圆形裁剪
13 | - 支持裁剪框宽高最小值的设定
14 | - 支持裁剪框网格是否显示
15 | - 优化裁剪框缩放到很小的时候,拖动不灵敏
16 |
17 | ### 矩形裁剪
18 | 
19 |
20 | ### 圆形裁剪
21 | 
22 |
23 |
24 | ### 添加依赖
25 | ```java
26 | dependencies{
27 |
28 | compile 'com.qiaomu.library:imagecrop:1.0.1'
29 | }
30 | ```
31 |
32 | ### 使用方式
33 |
34 | ```java
35 | Intent intent = new Intent(this, CropActivity.class);//替换成“com.android.camera.action.CROP” 模拟器运行可查看原生裁剪是什么样子的
36 | intent.setDataAndType(getUri("/sdcard/download/1.png"), "image/*");
37 | intent.putExtra("crop", "true");
38 | intent.putExtra("aspectX", 1);
39 | intent.putExtra("aspectY", 1);
40 | intent.putExtra("outputX", 300);
41 | intent.putExtra("outputY", 300);
42 | intent.putExtra("scale", false);//看源码,加不加问题不大,不加还会快一些,默认false
43 | intent.putExtra("return-data", false);//是否返回bitmap,建议不要用true,图片过大会崩溃的,默认false
44 | intent.putExtra("scaleUpIfNeeded", false); // 可避免莫名的黑边,加不加其实无所谓,默认false
45 | //intent.putExtra(CropActivity.MIN_CROP_WIDTH, 1080); //矩形裁剪情况下的 最下宽度度值px ,默认是40px
46 | //intent.putExtra(CropActivity.MIN_CROP_HEIGHT, 300);//矩形裁剪情况下的 最下高度值px,默认是40px
47 | intent.putExtra(CropActivity.CIRCLE_CROP, true); //是否是圆形裁剪,默认false
48 | intent.putExtra(CropActivity.DRAW_GRID, true); //是否显示裁剪网格,默认false
49 | intent.putExtra(MediaStore.EXTRA_OUTPUT, getUri("/sdcard/output.png"));
50 |
51 | if (Build.VERSION.SDK_INT > 23) {
52 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
53 | }
54 |
55 | startActivityForResult(intent, 1);
56 | ```
57 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "26.0.1"
6 | defaultConfig {
7 | applicationId "com.qiaomu.cropimage"
8 | minSdkVersion 15
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 |
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | compile 'com.android.support:appcompat-v7:25.3.1'
25 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
26 | compile project(':libcrop')
27 | }
28 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\mrs\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
31 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/qiaomu/cropimage/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.qiaomu.cropimage;
2 |
3 | import android.Manifest;
4 | import android.content.Intent;
5 | import android.content.pm.PackageManager;
6 | import android.graphics.Bitmap;
7 | import android.graphics.BitmapFactory;
8 | import android.net.Uri;
9 | import android.os.Build;
10 | import android.provider.MediaStore;
11 | import android.support.annotation.NonNull;
12 | import android.support.v4.app.ActivityCompat;
13 | import android.support.v4.content.FileProvider;
14 | import android.support.v7.app.AppCompatActivity;
15 | import android.os.Bundle;
16 | import android.widget.ImageView;
17 |
18 | import com.android.gallery3d.crop.CropActivity;
19 |
20 | import java.io.File;
21 | import java.io.FileNotFoundException;
22 |
23 | public class MainActivity extends AppCompatActivity {
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.activity_main);
29 |
30 |
31 | int check = ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
32 | if (check != PackageManager.PERMISSION_GRANTED) {
33 | ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100);
34 | } else {
35 | crop();
36 | }
37 | }
38 |
39 | @Override
40 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
41 | super.onRequestPermissionsResult(requestCode, permissions, grantResults);
42 | crop();
43 | }
44 |
45 | private void crop() {
46 | //还是按照原生Intent的使用方式,【MIN_CROP_WIDTH】 【MIN_CROP_HEIGHT】 【CIRCLE_CROP】 【DRAW_GRID】 新加的功能。
47 | // 【set-as-wallpaper】支持裁剪完后设置成壁纸,需要权限-----android.permission.SET_WALLPAPER
48 | // 其他的配置都是原生。
49 |
50 | Intent intent = new Intent(this, CropActivity.class);//
51 | intent.setDataAndType(getUri("/sdcard/download/1.png"), "image/*");
52 | intent.putExtra("crop", "true");
53 | intent.putExtra("aspectX", 1);
54 | intent.putExtra("aspectY", 1);
55 | intent.putExtra("outputX", 300);
56 | intent.putExtra("outputY", 300);
57 | intent.putExtra("scale", false);//看源码,加不加问题不大,不加还会快一些,默认false
58 | intent.putExtra("return-data", false);//是否返回bitmap,建议不要用true,图片过大会崩溃的,默认false
59 | intent.putExtra("scaleUpIfNeeded", false); // 可避免莫名的黑边,加不加其实无所谓,默认false
60 | // intent.putExtra(CropActivity.MIN_CROP_WIDTH, 1080); //矩形裁剪情况下的 最下宽度度值px ,默认是40px
61 | // intent.putExtra(CropActivity.MIN_CROP_HEIGHT, 300);//矩形裁剪情况下的 最下高度值px,默认是40px
62 | intent.putExtra(CropActivity.CIRCLE_CROP, true); //是否是圆形裁剪,默认false
63 | intent.putExtra(CropActivity.DRAW_GRID, true); //是否显示裁剪网格,默认false
64 | intent.putExtra(MediaStore.EXTRA_OUTPUT, getUri("/sdcard/output.png"));
65 |
66 |
67 | if (Build.VERSION.SDK_INT > 23) {
68 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
69 | }
70 | startActivityForResult(intent, 1);
71 | }
72 |
73 | private Uri getUri(String filepath) {
74 | if (Build.VERSION.SDK_INT >= 24) {
75 | //7.0以上的读取文件uri要用这种方式了
76 | return FileProvider.getUriForFile(this, "com.qiaomu.fileprovider", new File(filepath));
77 | } else {
78 | return Uri.parse("file://" + filepath);
79 | }
80 | }
81 |
82 |
83 | @Override
84 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
85 | switch (requestCode) {
86 | case 1:
87 | // 从剪切图片返回的数据
88 | if (resultCode == RESULT_OK) {
89 | if (data != null) {
90 | Uri data1 = data.getData();
91 | try {
92 | Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(getUri("/sdcard/output.png")));
93 | if (bitmap != null) {
94 | ImageView image = (ImageView) findViewById(R.id.image);
95 | image.setImageBitmap(bitmap);
96 | }
97 | } catch (FileNotFoundException e) {
98 | e.printStackTrace();
99 | }
100 | }
101 |
102 | }
103 | break;
104 | }
105 |
106 | super.onActivityResult(requestCode, resultCode, data);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/camera_crop_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-hdpi/camera_crop_height.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/camera_crop_width.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-hdpi/camera_crop_width.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/detail_photo_border.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-hdpi/detail_photo_border.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_menu_3d_globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-hdpi/ic_menu_3d_globe.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_menu_camera_video_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-hdpi/ic_menu_camera_video_view.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_menu_view_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-hdpi/ic_menu_view_details.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/indicator_autocrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-hdpi/indicator_autocrop.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/camera_crop_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-xhdpi/camera_crop_height.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/camera_crop_width.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-xhdpi/camera_crop_width.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/detail_photo_border.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-xhdpi/detail_photo_border.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_menu_3d_globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-xhdpi/ic_menu_3d_globe.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_menu_camera_video_view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-xhdpi/ic_menu_camera_video_view.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_menu_view_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-xhdpi/ic_menu_view_details.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/indicator_autocrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/drawable-xhdpi/indicator_autocrop.png
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/cropimage.xml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
19 |
20 |
24 |
25 |
33 |
34 |
42 |
43 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/detailsview.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
21 |
22 |
28 |
29 |
33 |
34 |
39 |
42 |
43 |
49 |
50 |
51 |
55 |
56 |
57 |
60 |
64 |
65 |
66 |
67 |
70 |
76 |
77 |
78 |
81 |
87 |
88 |
89 |
92 |
98 |
99 |
100 |
103 |
109 |
110 |
111 |
114 |
120 |
121 |
125 |
131 |
132 |
133 |
136 |
142 |
143 |
144 |
147 |
151 |
152 |
153 |
156 |
160 |
161 |
162 |
163 |
166 |
170 |
171 |
172 |
175 |
179 |
180 |
181 |
184 |
188 |
189 |
190 |
193 |
197 |
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 | All pictures
22 |
23 |
24 | All videos
25 |
26 |
27 | Camera
28 |
29 |
30 | Gallery
31 |
32 |
34 | Gallery
35 |
36 |
37 | Camera pictures
38 |
39 |
40 | Camera videos
41 |
42 |
43 | Camera media
44 |
45 |
46 | Crop picture
47 |
48 |
49 | View picture
50 |
51 |
52 | Camera settings
53 |
54 |
55 | Please wait\u2026
56 |
57 |
58 | Please mount the shared storage before using the camera.
59 |
60 |
61 | Your shared storage is full.
62 |
63 |
64 | Preparing shared storage\u2026
65 |
66 |
67 | Setting wallpaper, please wait\u2026
68 |
69 |
70 |
71 |
72 | Saving picture\u2026
73 |
74 |
75 | Please wait\u2026
76 |
77 |
78 |
79 |
80 | View
81 |
82 |
83 | Details
84 |
85 |
86 | Show on Maps
87 |
88 |
89 | Rotate
90 |
91 |
92 | Rotate left
93 |
94 |
95 | Rotate right
96 |
97 |
98 | Slideshow
99 |
100 |
101 | Multiselect
102 |
103 |
104 | Capture picture
105 |
106 | Capture video
107 |
108 |
109 | Save
110 |
111 | Discard
112 |
113 |
114 | Delete
115 |
116 | The picture will be deleted.
117 |
118 | The video will be deleted.
119 |
120 |
121 | These media files will be deleted.
122 |
123 |
124 | Delete
125 |
126 |
127 | Share
128 |
129 |
130 | Set as
131 |
132 |
133 | Play
134 |
135 |
136 | Attach
137 |
138 |
139 | Cancel
140 |
141 |
142 | Crop
143 |
144 |
145 | No application available to share the picture.
146 |
147 |
148 | No application available to share the video.
149 |
150 |
151 | No application available to share the media file(s).
152 |
153 |
154 | Play
155 |
156 |
157 | Pictures
158 | Wallpaper
159 |
160 |
161 | General settings
162 |
163 |
164 | Slideshow settings
165 |
166 |
167 | Display size
168 |
169 |
170 | Select the display size of pictures and videos
171 |
172 | Picture size
173 |
174 |
175 |
176 | - Large
177 |
178 | - Small
179 |
180 |
181 |
182 | - 1
183 | - 0
184 |
185 |
186 | 1
187 |
188 | Sort order
189 |
190 | Select the sort order of pictures and videos
191 |
192 | Picture sort
193 |
194 |
195 |
196 | - Newest first
197 |
198 | - Newest last
199 |
200 |
201 |
202 | - descending
203 | - ascending
204 |
205 |
206 | descending
207 |
208 | Slideshow interval
209 |
210 | Select how long each slide displays in the show
211 |
212 | Slideshow interval
213 |
214 |
215 |
216 | - 2 seconds
217 |
218 | - 3 seconds
219 |
220 | - 4 seconds
221 |
222 |
223 |
224 | - "2"
225 | - "3"
226 | - "4"
227 |
228 |
229 | "2"
230 |
231 | Slideshow transition
232 |
233 | Select the effect used when moving from one slide to the next
234 |
235 | Slideshow transition
236 |
238 |
239 |
240 | - Fade in & out
241 |
242 | - Slide left - right
243 |
244 | - Slide up - down
245 |
246 | - Random selection
247 |
248 |
249 |
250 | - "0"
251 | - "1"
252 | - "2"
253 | - "-1"
254 |
255 |
256 | "0"
257 |
258 |
259 | Repeat slideshow
260 |
261 |
262 | Play slideshow more than once
263 |
264 |
265 | Shuffle slides
266 |
267 |
268 | Show pictures in random order
269 |
270 |
271 | Settings
272 |
273 |
275 | No media found.
276 |
277 |
278 | Confirm deletions
279 |
280 |
281 | Show confirmation before deleting a picture or video
282 |
283 |
284 | No Location information contained in this image.
285 |
286 | Details
287 |
288 | File size:
289 |
290 | Resolution:
291 |
292 | Manufacturer:
293 |
294 | Model:
295 |
296 | WhiteBalance:
297 |
298 | GPS Latitude:
299 |
300 | GPS Longitude:
301 |
302 | Location:
303 |
304 | Duration:
305 |
306 | Date taken:
307 |
308 | Frame rate:
309 |
310 | Bit rate:
311 |
312 | Codec:
313 |
314 | Format:
315 |
316 |
317 | %1$d x %2$d
318 |
319 | %1$02d:%2$02d
320 |
321 | %1$d:%2$02d:%3$02d
322 |
323 | %1$d fps
324 |
325 | %1$d Kbps
326 |
327 | %1$g Mbps
328 |
329 | OK
330 |
331 |
332 | Picture options
333 |
334 | Video options
335 |
336 | Tap a face to begin.
337 |
338 |
339 | Gallery
340 |
341 |
342 | Select picture
343 |
344 |
345 | Gallery
346 |
347 |
348 | Select video
349 |
350 |
352 | Share picture via
353 |
354 |
356 | Set picture as
357 |
358 |
360 | Share video via
361 |
362 |
364 | Share media files via
365 |
366 |
367 | Movies
368 |
369 | Loading video\u2026
370 |
371 |
372 | Resume video
373 |
374 |
375 | Resume playing from %s ?
376 |
377 |
378 | Resume playing
379 |
380 |
381 | Start over
382 |
383 |
384 | Picture frame
385 |
386 |
387 | File info:
388 |
389 |
390 | The video you recorded is too large to send via MMS. Try recording a shorter length clip.
391 |
392 |
393 | Share
394 |
395 |
396 | Delete
397 |
398 |
399 | Cancel
400 |
401 |
402 | Deleting images, please wait\u2026
403 | crop_image
404 |
405 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
--------------------------------------------------------------------------------
/art/1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/art/1.gif
--------------------------------------------------------------------------------
/art/2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/art/2.gif
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | jcenter()
7 | }
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:2.3.0'
10 |
11 | // NOTE: Do not place your application dependencies here; they belong
12 | // in the individual module build.gradle files
13 | }
14 | }
15 |
16 | allprojects {
17 | repositories {
18 | jcenter()
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Oct 24 11:52:30 CST 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 |
--------------------------------------------------------------------------------
/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 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/libcrop/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/libcrop/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | ext{
4 | PUBLISH_GROUP_ID = 'com.qiaomu.library'
5 | PUBLISH_ARTIFACT_ID = 'imagecrop'
6 | PUBLISH_VERSION = '1.0.1'
7 | }
8 | android {
9 | compileSdkVersion 23
10 | buildToolsVersion "25.0.0"
11 |
12 |
13 | defaultConfig {
14 | minSdkVersion 15
15 | targetSdkVersion 23
16 | versionCode 1
17 | versionName "1.0"
18 |
19 | }
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | }
27 |
28 | dependencies {
29 | compile fileTree(dir: 'libs', include: ['*.jar'])
30 | }
31 | apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle'
--------------------------------------------------------------------------------
/libcrop/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Users\mrs\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/libcrop/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/BoundedRect.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.android.gallery3d.crop;
17 |
18 | import android.graphics.Matrix;
19 | import android.graphics.Rect;
20 | import android.graphics.RectF;
21 |
22 |
23 | import java.util.Arrays;
24 |
25 | /**
26 | * Maintains invariant that inner rectangle is constrained to be within the
27 | * outer, rotated rectangle.
28 | */
29 | public class BoundedRect {
30 | private float rot;
31 | private RectF outer;
32 | private RectF inner;
33 | private float[] innerRotated;
34 |
35 | public BoundedRect(float rotation, Rect outerRect, Rect innerRect) {
36 | rot = rotation;
37 | outer = new RectF(outerRect);
38 | inner = new RectF(innerRect);
39 | innerRotated = CropMath.getCornersFromRect(inner);
40 | rotateInner();
41 | if (!isConstrained())
42 | reconstrain();
43 | }
44 |
45 | public BoundedRect(float rotation, RectF outerRect, RectF innerRect) {
46 | rot = rotation;
47 | outer = new RectF(outerRect);
48 | inner = new RectF(innerRect);
49 | innerRotated = CropMath.getCornersFromRect(inner);
50 | rotateInner();
51 | if (!isConstrained())
52 | reconstrain();
53 | }
54 |
55 | public void resetTo(float rotation, RectF outerRect, RectF innerRect) {
56 | rot = rotation;
57 | outer.set(outerRect);
58 | inner.set(innerRect);
59 | innerRotated = CropMath.getCornersFromRect(inner);
60 | rotateInner();
61 | if (!isConstrained())
62 | reconstrain();
63 | }
64 |
65 | /**
66 | * Sets inner, and re-constrains it to fit within the rotated bounding rect.
67 | */
68 | public void setInner(RectF newInner) {
69 | if (inner.equals(newInner))
70 | return;
71 | inner = newInner;
72 | innerRotated = CropMath.getCornersFromRect(inner);
73 | rotateInner();
74 | if (!isConstrained())
75 | reconstrain();
76 | }
77 |
78 | /**
79 | * Sets rotation, and re-constrains inner to fit within the rotated bounding rect.
80 | */
81 | public void setRotation(float rotation) {
82 | if (rotation == rot)
83 | return;
84 | rot = rotation;
85 | innerRotated = CropMath.getCornersFromRect(inner);
86 | rotateInner();
87 | if (!isConstrained())
88 | reconstrain();
89 | }
90 |
91 | public void setToInner(RectF r) {
92 | r.set(inner);
93 | }
94 |
95 | public void setToOuter(RectF r) {
96 | r.set(outer);
97 | }
98 |
99 | public RectF getInner() {
100 | return new RectF(inner);
101 | }
102 |
103 | public RectF getOuter() {
104 | return new RectF(outer);
105 | }
106 |
107 | /**
108 | * Tries to move the inner rectangle by (dx, dy). If this would cause it to leave
109 | * the bounding rectangle, snaps the inner rectangle to the edge of the bounding
110 | * rectangle.
111 | */
112 | public void moveInner(float dx, float dy) {
113 | Matrix m0 = getInverseRotMatrix();
114 |
115 | RectF translatedInner = new RectF(inner);
116 | translatedInner.offset(dx, dy);
117 |
118 | float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner);
119 | float[] outerCorners = CropMath.getCornersFromRect(outer);
120 |
121 | m0.mapPoints(translatedInnerCorners);
122 | float[] correction = {
123 | 0, 0
124 | };
125 |
126 | // find correction vectors for corners that have moved out of bounds
127 | for (int i = 0; i < translatedInnerCorners.length; i += 2) {
128 | float correctedInnerX = translatedInnerCorners[i] + correction[0];
129 | float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
130 | if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
131 | float[] badCorner = {
132 | correctedInnerX, correctedInnerY
133 | };
134 | float[] nearestSide = CropMath.closestSide(badCorner, outerCorners);
135 | float[] correctionVec =
136 | GeometryMathUtils.shortestVectorFromPointToLine(badCorner, nearestSide);
137 | correction[0] += correctionVec[0];
138 | correction[1] += correctionVec[1];
139 | }
140 | }
141 |
142 | for (int i = 0; i < translatedInnerCorners.length; i += 2) {
143 | float correctedInnerX = translatedInnerCorners[i] + correction[0];
144 | float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
145 | if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) {
146 | float[] correctionVec = {
147 | correctedInnerX, correctedInnerY
148 | };
149 | CropMath.getEdgePoints(outer, correctionVec);
150 | correctionVec[0] -= correctedInnerX;
151 | correctionVec[1] -= correctedInnerY;
152 | correction[0] += correctionVec[0];
153 | correction[1] += correctionVec[1];
154 | }
155 | }
156 |
157 | // Set correction
158 | for (int i = 0; i < translatedInnerCorners.length; i += 2) {
159 | float correctedInnerX = translatedInnerCorners[i] + correction[0];
160 | float correctedInnerY = translatedInnerCorners[i + 1] + correction[1];
161 | // update translated corners with correction vectors
162 | translatedInnerCorners[i] = correctedInnerX;
163 | translatedInnerCorners[i + 1] = correctedInnerY;
164 | }
165 |
166 | innerRotated = translatedInnerCorners;
167 | // reconstrain to update inner
168 | reconstrain();
169 | }
170 |
171 | /**
172 | * Attempts to resize the inner rectangle. If this would cause it to leave
173 | * the bounding rect, clips the inner rectangle to fit.
174 | */
175 | public void resizeInner(RectF newInner) {
176 | Matrix m = getRotMatrix();
177 | Matrix m0 = getInverseRotMatrix();
178 |
179 | float[] outerCorners = CropMath.getCornersFromRect(outer);
180 | m.mapPoints(outerCorners);
181 | float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
182 | float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
183 | RectF ret = new RectF(newInner);
184 |
185 | for (int i = 0; i < newInnerCorners.length; i += 2) {
186 | float[] c = {
187 | newInnerCorners[i], newInnerCorners[i + 1]
188 | };
189 | float[] c0 = Arrays.copyOf(c, 2);
190 | m0.mapPoints(c0);
191 | if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
192 | float[] outerSide = CropMath.closestSide(c, outerCorners);
193 | float[] pathOfCorner = {
194 | newInnerCorners[i], newInnerCorners[i + 1],
195 | oldInnerCorners[i], oldInnerCorners[i + 1]
196 | };
197 | float[] p = GeometryMathUtils.lineIntersect(pathOfCorner, outerSide);
198 | if (p == null) {
199 | // lines are parallel or not well defined, so don't resize
200 | p = new float[2];
201 | p[0] = oldInnerCorners[i];
202 | p[1] = oldInnerCorners[i + 1];
203 | }
204 | // relies on corners being in same order as method
205 | // getCornersFromRect
206 | switch (i) {
207 | case 0:
208 | case 1:
209 | ret.left = (p[0] > ret.left) ? p[0] : ret.left;
210 | ret.top = (p[1] > ret.top) ? p[1] : ret.top;
211 | break;
212 | case 2:
213 | case 3:
214 | ret.right = (p[0] < ret.right) ? p[0] : ret.right;
215 | ret.top = (p[1] > ret.top) ? p[1] : ret.top;
216 | break;
217 | case 4:
218 | case 5:
219 | ret.right = (p[0] < ret.right) ? p[0] : ret.right;
220 | ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
221 | break;
222 | case 6:
223 | case 7:
224 | ret.left = (p[0] > ret.left) ? p[0] : ret.left;
225 | ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom;
226 | break;
227 | default:
228 | break;
229 | }
230 | }
231 | }
232 | float[] retCorners = CropMath.getCornersFromRect(ret);
233 | m0.mapPoints(retCorners);
234 | innerRotated = retCorners;
235 | // reconstrain to update inner
236 | reconstrain();
237 | }
238 |
239 | /**
240 | * Attempts to resize the inner rectangle. If this would cause it to leave
241 | * the bounding rect, clips the inner rectangle to fit while maintaining
242 | * aspect ratio.
243 | */
244 | public void fixedAspectResizeInner(RectF newInner) {
245 | Matrix m = getRotMatrix();
246 | Matrix m0 = getInverseRotMatrix();
247 |
248 | float aspectW = inner.width();
249 | float aspectH = inner.height();
250 | float aspRatio = aspectW / aspectH;
251 | float[] corners = CropMath.getCornersFromRect(outer);
252 |
253 | m.mapPoints(corners);
254 | float[] oldInnerCorners = CropMath.getCornersFromRect(inner);
255 | float[] newInnerCorners = CropMath.getCornersFromRect(newInner);
256 |
257 | // find fixed corner
258 | int fixed = -1;
259 | if (inner.top == newInner.top) {
260 | if (inner.left == newInner.left)
261 | fixed = 0; // top left
262 | else if (inner.right == newInner.right)
263 | fixed = 2; // top right
264 | } else if (inner.bottom == newInner.bottom) {
265 | if (inner.right == newInner.right)
266 | fixed = 4; // bottom right
267 | else if (inner.left == newInner.left)
268 | fixed = 6; // bottom left
269 | }
270 | // no fixed corner, return without update
271 | if (fixed == -1)
272 | return;
273 | float widthSoFar = newInner.width();
274 | int moved = -1;
275 | for (int i = 0; i < newInnerCorners.length; i += 2) {
276 | float[] c = {
277 | newInnerCorners[i], newInnerCorners[i + 1]
278 | };
279 | float[] c0 = Arrays.copyOf(c, 2);
280 | m0.mapPoints(c0);
281 | if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) {
282 | moved = i;
283 | if (moved == fixed)
284 | continue;
285 | float[] l2 = CropMath.closestSide(c, corners);
286 | float[] l1 = {
287 | newInnerCorners[i], newInnerCorners[i + 1],
288 | oldInnerCorners[i], oldInnerCorners[i + 1]
289 | };
290 | float[] p = GeometryMathUtils.lineIntersect(l1, l2);
291 | if (p == null) {
292 | // lines are parallel or not well defined, so set to old
293 | // corner
294 | p = new float[2];
295 | p[0] = oldInnerCorners[i];
296 | p[1] = oldInnerCorners[i + 1];
297 | }
298 | // relies on corners being in same order as method
299 | // getCornersFromRect
300 | float fixed_x = oldInnerCorners[fixed];
301 | float fixed_y = oldInnerCorners[fixed + 1];
302 | float newWidth = Math.abs(fixed_x - p[0]);
303 | float newHeight = Math.abs(fixed_y - p[1]);
304 | newWidth = Math.max(newWidth, aspRatio * newHeight);
305 | if (newWidth < widthSoFar)
306 | widthSoFar = newWidth;
307 | }
308 | }
309 |
310 | float heightSoFar = widthSoFar / aspRatio;
311 | RectF ret = new RectF(inner);
312 | if (fixed == 0) {
313 | ret.right = ret.left + widthSoFar;
314 | ret.bottom = ret.top + heightSoFar;
315 | } else if (fixed == 2) {
316 | ret.left = ret.right - widthSoFar;
317 | ret.bottom = ret.top + heightSoFar;
318 | } else if (fixed == 4) {
319 | ret.left = ret.right - widthSoFar;
320 | ret.top = ret.bottom - heightSoFar;
321 | } else if (fixed == 6) {
322 | ret.right = ret.left + widthSoFar;
323 | ret.top = ret.bottom - heightSoFar;
324 | }
325 | float[] retCorners = CropMath.getCornersFromRect(ret);
326 | m0.mapPoints(retCorners);
327 | innerRotated = retCorners;
328 | // reconstrain to update inner
329 | reconstrain();
330 | }
331 |
332 | // internal methods
333 |
334 | private boolean isConstrained() {
335 | for (int i = 0; i < 8; i += 2) {
336 | if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1]))
337 | return false;
338 | }
339 | return true;
340 | }
341 |
342 | private void reconstrain() {
343 | // innerRotated has been changed to have incorrect values
344 | CropMath.getEdgePoints(outer, innerRotated);
345 | Matrix m = getRotMatrix();
346 | float[] unrotated = Arrays.copyOf(innerRotated, 8);
347 | m.mapPoints(unrotated);
348 | inner = CropMath.trapToRect(unrotated);
349 | }
350 |
351 | private void rotateInner() {
352 | Matrix m = getInverseRotMatrix();
353 | m.mapPoints(innerRotated);
354 | }
355 |
356 | private Matrix getRotMatrix() {
357 | Matrix m = new Matrix();
358 | m.setRotate(rot, outer.centerX(), outer.centerY());
359 | return m;
360 | }
361 |
362 | private Matrix getInverseRotMatrix() {
363 | Matrix m = new Matrix();
364 | m.setRotate(-rot, outer.centerX(), outer.centerY());
365 | return m;
366 | }
367 | }
368 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/CropActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.gallery3d.crop;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.app.Activity;
21 | import android.app.WallpaperManager;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.content.res.Configuration;
25 | import android.graphics.Bitmap;
26 | import android.graphics.Bitmap.CompressFormat;
27 | import android.graphics.BitmapFactory;
28 | import android.graphics.BitmapRegionDecoder;
29 | import android.graphics.Canvas;
30 | import android.graphics.Color;
31 | import android.graphics.Matrix;
32 | import android.graphics.Paint;
33 | import android.graphics.PorterDuff;
34 | import android.graphics.PorterDuffXfermode;
35 | import android.graphics.Rect;
36 | import android.graphics.RectF;
37 | import android.net.Uri;
38 | import android.os.AsyncTask;
39 | import android.os.Bundle;
40 | import android.provider.MediaStore;
41 | import android.util.DisplayMetrics;
42 | import android.util.Log;
43 | import android.view.View;
44 | import android.view.View.OnClickListener;
45 | import android.view.Window;
46 | import android.view.WindowManager;
47 | import android.widget.Toast;
48 |
49 | import com.android.gallery3d.R;
50 |
51 | import java.io.ByteArrayInputStream;
52 | import java.io.ByteArrayOutputStream;
53 | import java.io.FileNotFoundException;
54 | import java.io.IOException;
55 | import java.io.InputStream;
56 | import java.io.OutputStream;
57 |
58 | /**
59 | * Activity for cropping an image.
60 | */
61 | public class CropActivity extends Activity {
62 | private static final String LOGTAG = "CropActivity";
63 | public static final String CROP_ACTION = "com.android.camera.action.CROP";
64 | private CropExtras mCropExtras = null;
65 | private LoadBitmapTask mLoadBitmapTask = null;
66 |
67 | private int mOutputX = 0;
68 | private int mOutputY = 0;
69 | private Bitmap mOriginalBitmap = null;
70 | private RectF mOriginalBounds = null;
71 | private int mOriginalRotation = 0;
72 | private Uri mSourceUri = null;
73 | private CropView mCropView = null;
74 | private View mSaveButton = null;
75 | private boolean finalIOGuard = false;
76 |
77 | private static final int SELECT_PICTURE = 1; // request code for picker
78 |
79 | private static final int DEFAULT_COMPRESS_QUALITY = 100;
80 | /**
81 | * The maximum bitmap size we allow to be returned through the intent.
82 | * Intents have a maximum of 1MB in total size. However, the Bitmap seems to
83 | * have some overhead to hit so that we go way below the limit here to make
84 | * sure the intent stays below 1MB.We should consider just returning a byte
85 | * array instead of a Bitmap instance to avoid overhead.
86 | */
87 | public static final int MAX_BMAP_IN_INTENT = 750000;
88 |
89 | // Flags
90 | private static final int DO_SET_WALLPAPER = 1;
91 | private static final int DO_RETURN_DATA = 1 << 1;
92 | private static final int DO_EXTRA_OUTPUT = 1 << 2;
93 |
94 | private static final int FLAG_CHECK = DO_SET_WALLPAPER | DO_RETURN_DATA | DO_EXTRA_OUTPUT;
95 |
96 |
97 | public static final String CIRCLE_CROP = "circleCrop";
98 | public static final String MIN_CROP_WIDTH = "minCropWidth";
99 | public static final String MIN_CROP_HEIGHT = "minCropHeight";
100 | public static final String DRAW_GRID = "draw_grid";
101 | private boolean asCircle;
102 | private int minCropWidth, minCropHeight;
103 | private boolean drawCropGrid;
104 |
105 | @SuppressLint("WrongConstant")
106 | @Override
107 | public void onCreate(Bundle savedInstanceState) {
108 | super.onCreate(savedInstanceState);
109 | Intent intent = getIntent();
110 | requestWindowFeature(Window.FEATURE_NO_TITLE);
111 | setResult(RESULT_CANCELED, new Intent());
112 | mCropExtras = getExtrasFromIntent(intent);
113 | if (mCropExtras != null && mCropExtras.getShowWhenLocked()) {
114 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
115 | }
116 |
117 | setContentView(R.layout.crop_activity);
118 | mCropView = (CropView) findViewById(R.id.cropView);
119 | findViewById(R.id.cancel_crop).setOnClickListener(new OnClickListener() {
120 | @Override
121 | public void onClick(View v) {
122 | setResult(RESULT_CANCELED, new Intent());
123 | done();
124 | }
125 | });
126 |
127 | findViewById(R.id.cancel_ok).setOnClickListener(new OnClickListener() {
128 | @Override
129 | public void onClick(View v) {
130 | startFinishOutput();
131 | }
132 | });
133 |
134 | if (intent.getData() != null) {
135 | mSourceUri = intent.getData();
136 | startLoadBitmap(mSourceUri);
137 | } else {
138 | pickImage();
139 | }
140 | }
141 |
142 | private void enableSave(boolean enable) {
143 | if (mSaveButton != null) {
144 | mSaveButton.setEnabled(enable);
145 | }
146 | }
147 |
148 | @Override
149 | protected void onDestroy() {
150 | if (mLoadBitmapTask != null) {
151 | mLoadBitmapTask.cancel(false);
152 | }
153 | super.onDestroy();
154 | }
155 |
156 | @Override
157 | public void onConfigurationChanged(Configuration newConfig) {
158 | super.onConfigurationChanged(newConfig);
159 | mCropView.configChanged();
160 | }
161 |
162 | /**
163 | * Opens a selector in Gallery to chose an image for use when none was given
164 | * in the CROP intent.
165 | */
166 | private void pickImage() {
167 | Intent intent = new Intent();
168 | intent.setType("image/*");
169 | intent.setAction(Intent.ACTION_GET_CONTENT);
170 | startActivityForResult(Intent.createChooser(intent, getString(R.string.select_image)),
171 | SELECT_PICTURE);
172 | }
173 |
174 | /**
175 | * Callback for pickImage().
176 | */
177 | @Override
178 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
179 | if (resultCode == RESULT_OK && requestCode == SELECT_PICTURE) {
180 | mSourceUri = data.getData();
181 | startLoadBitmap(mSourceUri);
182 | }
183 | }
184 |
185 | /**
186 | * Gets screen size metric.
187 | */
188 | private int getScreenImageSize() {
189 | DisplayMetrics outMetrics = new DisplayMetrics();
190 | getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
191 | return (int) Math.max(outMetrics.heightPixels, outMetrics.widthPixels);
192 | }
193 |
194 | /**
195 | * Method that loads a bitmap in an async task.
196 | */
197 | @SuppressLint("WrongConstant")
198 | private void startLoadBitmap(Uri uri) {
199 | if (uri != null) {
200 | enableSave(false);
201 | final View loading = findViewById(R.id.loading);
202 | loading.setVisibility(View.VISIBLE);
203 | mLoadBitmapTask = new LoadBitmapTask();
204 | mLoadBitmapTask.execute(uri);
205 | } else {
206 | cannotLoadImage();
207 | done();
208 | }
209 | }
210 |
211 | /**
212 | * Method called on UI thread with loaded bitmap.
213 | */
214 | @SuppressLint("WrongConstant")
215 | private void doneLoadBitmap(Bitmap bitmap, RectF bounds, int orientation) {
216 | final View loading = findViewById(R.id.loading);
217 | loading.setVisibility(View.GONE);
218 | mOriginalBitmap = bitmap;
219 | mOriginalBounds = bounds;
220 | mOriginalRotation = orientation;
221 | if (bitmap != null && bitmap.getWidth() != 0 && bitmap.getHeight() != 0) {
222 | RectF imgBounds = new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight());
223 | mCropView.initialize(bitmap, imgBounds, imgBounds, orientation, asCircle, drawCropGrid, minCropWidth, minCropHeight);
224 | if (mCropExtras != null) {
225 | int aspectX = mCropExtras.getAspectX();
226 | int aspectY = mCropExtras.getAspectY();
227 | mOutputX = mCropExtras.getOutputX();
228 | mOutputY = mCropExtras.getOutputY();
229 | if (mOutputX > 0 && mOutputY > 0) {
230 | mCropView.applyAspect(mOutputX, mOutputY);
231 |
232 | }
233 | float spotX = mCropExtras.getSpotlightX();
234 | float spotY = mCropExtras.getSpotlightY();
235 | if (spotX > 0 && spotY > 0) {
236 | mCropView.setWallpaperSpotlight(spotX, spotY);
237 | }
238 | if (aspectX > 0 && aspectY > 0) {
239 | mCropView.applyAspect(aspectX, aspectY);
240 | }
241 | }
242 | enableSave(true);
243 | } else {
244 | Log.w(LOGTAG, "could not load image for cropping");
245 | cannotLoadImage();
246 | setResult(RESULT_CANCELED, new Intent());
247 | done();
248 | }
249 | }
250 |
251 | /**
252 | * Display toast for image loading failure.
253 | */
254 | @SuppressLint("WrongConstant")
255 | private void cannotLoadImage() {
256 | CharSequence text = getString(R.string.cannot_load_image);
257 | Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
258 | toast.show();
259 | }
260 |
261 | /**
262 | * AsyncTask for loading a bitmap into memory.
263 | *
264 | * @see #startLoadBitmap(Uri)
265 | */
266 | private class LoadBitmapTask extends AsyncTask {
267 | int mBitmapSize;
268 | Context mContext;
269 | Rect mOriginalBounds;
270 | int mOrientation;
271 |
272 | public LoadBitmapTask() {
273 | mBitmapSize = getScreenImageSize();
274 | mContext = getApplicationContext();
275 | mOriginalBounds = new Rect();
276 | mOrientation = 0;
277 | }
278 |
279 | @Override
280 | protected Bitmap doInBackground(Uri... params) {
281 | Uri uri = params[0];
282 | Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize,
283 | mOriginalBounds, false);
284 | mOrientation = ImageLoader.getMetadataRotation(mContext, uri);
285 | return bmap;
286 | }
287 |
288 | @Override
289 | protected void onPostExecute(Bitmap result) {
290 | doneLoadBitmap(result, new RectF(mOriginalBounds), mOrientation);
291 | }
292 | }
293 |
294 | protected void startFinishOutput() {
295 | if (finalIOGuard) {
296 | return;
297 | } else {
298 | finalIOGuard = true;
299 | }
300 | enableSave(false);
301 | Uri destinationUri = null;
302 | int flags = 0;
303 | if (mOriginalBitmap != null && mCropExtras != null) {
304 | if (mCropExtras.getExtraOutput() != null) {
305 | destinationUri = mCropExtras.getExtraOutput();
306 | if (destinationUri != null) {
307 | flags |= DO_EXTRA_OUTPUT;
308 | }
309 | }
310 | if (mCropExtras.getSetAsWallpaper()) {
311 | flags |= DO_SET_WALLPAPER;
312 | }
313 | if (mCropExtras.getReturnData()) {
314 | flags |= DO_RETURN_DATA;
315 | }
316 | }
317 | if (flags == 0) {
318 | destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri);
319 | if (destinationUri != null) {
320 | flags |= DO_EXTRA_OUTPUT;
321 | }
322 | }
323 | if ((flags & FLAG_CHECK) != 0 && mOriginalBitmap != null) {
324 | RectF photo = new RectF(0, 0, mOriginalBitmap.getWidth(), mOriginalBitmap.getHeight());
325 | RectF crop = getBitmapCrop(photo);
326 | startBitmapIO(flags, mOriginalBitmap, mSourceUri, destinationUri, crop,
327 | photo, mOriginalBounds,
328 | (mCropExtras == null) ? null : mCropExtras.getOutputFormat(), mOriginalRotation);
329 | return;
330 | }
331 | setResult(RESULT_CANCELED, new Intent());
332 | done();
333 | return;
334 | }
335 |
336 | @SuppressLint("WrongConstant")
337 | private void startBitmapIO(int flags, Bitmap currentBitmap, Uri sourceUri, Uri destUri,
338 | RectF cropBounds, RectF photoBounds, RectF currentBitmapBounds, String format,
339 | int rotation) {
340 | if (cropBounds == null || photoBounds == null || currentBitmap == null
341 | || currentBitmap.getWidth() == 0 || currentBitmap.getHeight() == 0
342 | || cropBounds.width() == 0 || cropBounds.height() == 0 || photoBounds.width() == 0
343 | || photoBounds.height() == 0) {
344 | return; // fail fast
345 | }
346 | if ((flags & FLAG_CHECK) == 0) {
347 | return; // no output options
348 | }
349 | if ((flags & DO_SET_WALLPAPER) != 0) {
350 | Toast.makeText(this, R.string.setting_wallpaper, Toast.LENGTH_LONG).show();
351 | }
352 |
353 | final View loading = findViewById(R.id.loading);
354 | loading.setVisibility(View.VISIBLE);
355 | BitmapIOTask ioTask = new BitmapIOTask(sourceUri, destUri, format, flags, cropBounds,
356 | photoBounds, currentBitmapBounds, rotation, mOutputX, mOutputY);
357 | ioTask.execute(currentBitmap);
358 | }
359 |
360 | @SuppressLint("WrongConstant")
361 | private void doneBitmapIO(boolean success, Intent intent) {
362 | final View loading = findViewById(R.id.loading);
363 | loading.setVisibility(View.GONE);
364 | if (success) {
365 | setResult(RESULT_OK, intent);
366 | } else {
367 | setResult(RESULT_CANCELED, intent);
368 | }
369 | done();
370 | }
371 |
372 | private class BitmapIOTask extends AsyncTask {
373 |
374 | private final WallpaperManager mWPManager;
375 | InputStream mInStream = null;
376 | OutputStream mOutStream = null;
377 | String mOutputFormat = null;
378 | Uri mOutUri = null;
379 | Uri mInUri = null;
380 | int mFlags = 0;
381 | RectF mCrop = null;
382 | RectF mPhoto = null;
383 | RectF mOrig = null;
384 | Intent mResultIntent = null;
385 | int mRotation = 0;
386 |
387 | // Helper to setup input stream
388 | private void regenerateInputStream() {
389 | if (mInUri == null) {
390 | Log.w(LOGTAG, "cannot read original file, no input URI given");
391 | } else {
392 | Utils.closeSilently(mInStream);
393 | try {
394 | mInStream = getContentResolver().openInputStream(mInUri);
395 | } catch (FileNotFoundException e) {
396 | Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
397 | }
398 | }
399 | }
400 |
401 | public BitmapIOTask(Uri sourceUri, Uri destUri, String outputFormat, int flags,
402 | RectF cropBounds, RectF photoBounds, RectF originalBitmapBounds, int rotation,
403 | int outputX, int outputY) {
404 | mOutputFormat = outputFormat;
405 | mOutStream = null;
406 | mOutUri = destUri;
407 | mInUri = sourceUri;
408 | mFlags = flags;
409 | mCrop = cropBounds;
410 | mPhoto = photoBounds;
411 | mOrig = originalBitmapBounds;
412 | mWPManager = WallpaperManager.getInstance(getApplicationContext());
413 | mResultIntent = new Intent();
414 | mRotation = (rotation < 0) ? -rotation : rotation;
415 | mRotation %= 360;
416 | mRotation = 90 * (int) (mRotation / 90); // now mRotation is a multiple of 90
417 | mOutputX = outputX;
418 | mOutputY = outputY;
419 |
420 | if ((flags & DO_EXTRA_OUTPUT) != 0) {
421 | if (mOutUri == null) {
422 | Log.w(LOGTAG, "cannot write file, no output URI given");
423 | } else {
424 | try {
425 | mOutStream = getContentResolver().openOutputStream(mOutUri);
426 | } catch (FileNotFoundException e) {
427 | Log.w(LOGTAG, "cannot write file: " + mOutUri.toString(), e);
428 | }
429 | }
430 | }
431 |
432 | if ((flags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0) {
433 | regenerateInputStream();
434 | }
435 | }
436 |
437 | @Override
438 | protected Boolean doInBackground(Bitmap... params) {
439 | boolean failure = false;
440 | Bitmap img = params[0];
441 |
442 | // Set extra for crop bounds
443 | if (mCrop != null && mPhoto != null && mOrig != null) {
444 | RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
445 | Matrix m = new Matrix();
446 | m.setRotate(mRotation);
447 | m.mapRect(trueCrop);
448 | if (trueCrop != null) {
449 | Rect rounded = new Rect();
450 | trueCrop.roundOut(rounded);
451 | mResultIntent.putExtra(CropExtras.KEY_CROPPED_RECT, rounded);
452 | }
453 | }
454 |
455 | // Find the small cropped bitmap that is returned in the intent
456 | if ((mFlags & DO_RETURN_DATA) != 0) {
457 | assert (img != null);
458 | Bitmap ret = getCroppedImage(img, mCrop, mPhoto);
459 | if (ret != null) {
460 | ret = getDownsampledBitmap(ret, MAX_BMAP_IN_INTENT);
461 | }
462 | if (ret == null) {
463 | Log.w(LOGTAG, "could not downsample bitmap to return in data");
464 | failure = true;
465 | } else {
466 | if (mRotation > 0) {
467 | Matrix m = new Matrix();
468 | m.setRotate(mRotation);
469 | Bitmap tmp = Bitmap.createBitmap(ret, 0, 0, ret.getWidth(),
470 | ret.getHeight(), m, true);
471 | if (tmp != null) {
472 | ret = tmp;
473 | }
474 | }
475 | mResultIntent.putExtra(CropExtras.KEY_DATA, ret);
476 | }
477 | }
478 |
479 | // Do the large cropped bitmap and/or set the wallpaper
480 | if ((mFlags & (DO_EXTRA_OUTPUT | DO_SET_WALLPAPER)) != 0 && mInStream != null) {
481 | // Find crop bounds (scaled to original image size)
482 | RectF trueCrop = CropMath.getScaledCropBounds(mCrop, mPhoto, mOrig);
483 | if (trueCrop == null) {
484 | Log.w(LOGTAG, "cannot find crop for full size image");
485 | failure = true;
486 | return false;
487 | }
488 | Rect roundedTrueCrop = new Rect();
489 | trueCrop.roundOut(roundedTrueCrop);
490 |
491 | if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
492 | Log.w(LOGTAG, "crop has bad values for full size image");
493 | failure = true;
494 | return false;
495 | }
496 |
497 | // Attempt to open a region decoder
498 | BitmapRegionDecoder decoder = null;
499 | try {
500 | decoder = BitmapRegionDecoder.newInstance(mInStream, true);
501 | } catch (IOException e) {
502 | Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
503 | }
504 |
505 | Bitmap crop = null;
506 | if (decoder != null) {
507 | // Do region decoding to get crop bitmap
508 | BitmapFactory.Options options = new BitmapFactory.Options();
509 | options.inMutable = true;
510 | crop = decoder.decodeRegion(roundedTrueCrop, options);
511 | decoder.recycle();
512 | }
513 |
514 | if (crop == null) {
515 | // BitmapRegionDecoder has failed, try to crop in-memory
516 | regenerateInputStream();
517 | Bitmap fullSize = null;
518 | if (mInStream != null) {
519 | fullSize = BitmapFactory.decodeStream(mInStream);
520 | }
521 | if (fullSize != null) {
522 | crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
523 | roundedTrueCrop.top, roundedTrueCrop.width(),
524 | roundedTrueCrop.height());
525 | }
526 | }
527 |
528 | if (crop == null) {
529 | Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
530 | failure = true;
531 | return false;
532 | }
533 | if (mOutputX > 0 && mOutputY > 0) {
534 | Matrix m = new Matrix();
535 | RectF cropRect = new RectF(0, 0, crop.getWidth(), crop.getHeight());
536 | if (mRotation > 0) {
537 | m.setRotate(mRotation);
538 | m.mapRect(cropRect);
539 | }
540 | RectF returnRect = new RectF(0, 0, mOutputX, mOutputY);
541 | m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
542 | m.preRotate(mRotation);
543 | Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
544 | (int) returnRect.height(), Bitmap.Config.ARGB_8888);
545 | if (tmp != null) {
546 | Canvas c = new Canvas(tmp);
547 | c.drawBitmap(crop, m, new Paint());
548 | crop = tmp;
549 | }
550 | } else if (mRotation > 0) {
551 | Matrix m = new Matrix();
552 | m.setRotate(mRotation);
553 | Bitmap tmp = Bitmap.createBitmap(crop, 0, 0, crop.getWidth(),
554 | crop.getHeight(), m, true);
555 | if (tmp != null) {
556 | crop = tmp;
557 | }
558 | }
559 |
560 | if (asCircle) {
561 | crop = getCircleBitmap(crop);
562 | }
563 | // Get output compression format
564 | CompressFormat cf = asCircle ? CompressFormat.PNG : convertExtensionToCompressFormat(getFileExtension(mOutputFormat));
565 |
566 | // If we only need to output to a URI, compress straight to file
567 | if (mFlags == DO_EXTRA_OUTPUT) {
568 | if (mOutStream == null || !crop.compress(cf, DEFAULT_COMPRESS_QUALITY, mOutStream)) {
569 | failure = true;
570 | } else {
571 | mResultIntent.setData(mOutUri);
572 | }
573 | } else {
574 | // Compress to byte array
575 | ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
576 | if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
577 |
578 | // If we need to output to a Uri, write compressed
579 | // bitmap out
580 | if ((mFlags & DO_EXTRA_OUTPUT) != 0) {
581 | if (mOutStream == null) {
582 | Log.w(LOGTAG,
583 | "failed to compress bitmap to file: " + mOutUri.toString());
584 | failure = true;
585 | } else {
586 | try {
587 | mOutStream.write(tmpOut.toByteArray());
588 | mResultIntent.setData(mOutUri);
589 | } catch (IOException e) {
590 | Log.w(LOGTAG,
591 | "failed to compress bitmap to file: "
592 | + mOutUri.toString(), e);
593 | failure = true;
594 | }
595 | }
596 | }
597 |
598 | // If we need to set to the wallpaper, set it
599 | if ((mFlags & DO_SET_WALLPAPER) != 0 && mWPManager != null) {
600 | if (mWPManager == null) {
601 | Log.w(LOGTAG, "no wallpaper manager");
602 | failure = true;
603 | } else {
604 | try {
605 | mWPManager.setStream(new ByteArrayInputStream(tmpOut
606 | .toByteArray()));
607 | } catch (IOException e) {
608 | Log.w(LOGTAG, "cannot write stream to wallpaper", e);
609 | failure = true;
610 | }
611 | }
612 | }
613 | } else {
614 | Log.w(LOGTAG, "cannot compress bitmap");
615 | failure = true;
616 | }
617 | }
618 | }
619 | return !failure; // True if any of the operations failed
620 | }
621 |
622 | @Override
623 | protected void onPostExecute(Boolean result) {
624 | Utils.closeSilently(mOutStream);
625 | Utils.closeSilently(mInStream);
626 | doneBitmapIO(result.booleanValue(), mResultIntent);
627 | }
628 |
629 | }
630 |
631 | public static Bitmap getCircleBitmap(Bitmap bit) {
632 | Bitmap bitmap = Bitmap.createBitmap(bit.getWidth(), bit.getHeight(), Bitmap.Config.ARGB_8888);
633 | Canvas canvas = new Canvas(bitmap);//传入参数后canvas的操作会影响图片
634 | Paint paint = new Paint();
635 | paint.setAntiAlias(true);// 设置抗锯齿
636 | paint.setColor(Color.YELLOW);
637 |
638 | int radius = bitmap.getWidth() > bitmap.getHeight() ? bitmap.getHeight() : bitmap.getWidth();// 将较短的那一条边作为直径
639 | canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, radius / 2, paint);// 在图片中心画以radius/2为半径的圆形
640 |
641 | // 设置相交保留且图片部分显示
642 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
643 |
644 | // 绘制图片
645 | canvas.drawBitmap(bit, 0, 0, paint);
646 | return bitmap;
647 | }
648 |
649 |
650 | private void done() {
651 | finish();
652 | }
653 |
654 | protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
655 | RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
656 | RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
657 | if (crop == null) {
658 | return null;
659 | }
660 | Rect intCrop = new Rect();
661 | crop.roundOut(intCrop);
662 | return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
663 | intCrop.height());
664 | }
665 |
666 | protected static Bitmap getDownsampledBitmap(Bitmap image, int max_size) {
667 | if (image == null || image.getWidth() == 0 || image.getHeight() == 0 || max_size < 16) {
668 | throw new IllegalArgumentException("Bad argument to getDownsampledBitmap()");
669 | }
670 | int shifts = 0;
671 | int size = CropMath.getBitmapSize(image);
672 | while (size > max_size) {
673 | shifts++;
674 | size /= 4;
675 | }
676 | Bitmap ret = Bitmap.createScaledBitmap(image, image.getWidth() >> shifts,
677 | image.getHeight() >> shifts, true);
678 | if (ret == null) {
679 | return null;
680 | }
681 | // Handle edge case for rounding.
682 | if (CropMath.getBitmapSize(ret) > max_size) {
683 | return Bitmap.createScaledBitmap(ret, ret.getWidth() >> 1, ret.getHeight() >> 1, true);
684 | }
685 | return ret;
686 | }
687 |
688 | /**
689 | * Gets the crop extras from the intent, or null if none exist.
690 | */
691 | protected CropExtras getExtrasFromIntent(Intent intent) {
692 | Bundle extras = intent.getExtras();
693 | if (extras != null) {
694 | asCircle = extras.getBoolean(CIRCLE_CROP);
695 | minCropWidth = extras.getInt(MIN_CROP_WIDTH);
696 | minCropHeight = extras.getInt(MIN_CROP_HEIGHT);
697 | drawCropGrid = extras.getBoolean(DRAW_GRID);
698 | int aspect_x = extras.getInt(CropExtras.KEY_ASPECT_X, 0);
699 | int aspect_y = extras.getInt(CropExtras.KEY_ASPECT_Y, 0);
700 | if (asCircle) {
701 | aspect_x = aspect_y = 1;
702 | int min = Math.min(minCropWidth, minCropHeight);
703 | minCropWidth = min;
704 | minCropHeight = min;
705 | }
706 |
707 | return new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0),
708 | extras.getInt(CropExtras.KEY_OUTPUT_Y, 0),
709 | extras.getBoolean(CropExtras.KEY_SCALE, true) &&
710 | extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false),
711 | aspect_x,
712 | aspect_y,
713 | extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false),
714 | extras.getBoolean(CropExtras.KEY_RETURN_DATA, false),
715 | (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT),
716 | extras.getString(CropExtras.KEY_OUTPUT_FORMAT),
717 | extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false),
718 | extras.getFloat(CropExtras.KEY_SPOTLIGHT_X),
719 | extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y));
720 | }
721 | return null;
722 | }
723 |
724 | protected static CompressFormat convertExtensionToCompressFormat(String extension) {
725 | return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG;
726 | }
727 |
728 | protected static String getFileExtension(String requestFormat) {
729 | String outputFormat = (requestFormat == null)
730 | ? "jpg"
731 | : requestFormat;
732 | outputFormat = outputFormat.toLowerCase();
733 | return (outputFormat.equals("png") || outputFormat.equals("gif"))
734 | ? "png" // We don't support gif compression.
735 | : "jpg";
736 | }
737 |
738 | private RectF getBitmapCrop(RectF imageBounds) {
739 | RectF crop = mCropView.getCrop();
740 | RectF photo = mCropView.getPhoto();
741 | if (crop == null || photo == null) {
742 | Log.w(LOGTAG, "could not get crop");
743 | return null;
744 | }
745 | RectF scaledCrop = CropMath.getScaledCropBounds(crop, photo, imageBounds);
746 | return scaledCrop;
747 | }
748 | }
749 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/CropDrawingUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.gallery3d.crop;
18 |
19 | import android.annotation.SuppressLint;
20 | import android.graphics.Bitmap;
21 | import android.graphics.Canvas;
22 | import android.graphics.Color;
23 | import android.graphics.CornerPathEffect;
24 | import android.graphics.Matrix;
25 | import android.graphics.Paint;
26 | import android.graphics.PaintFlagsDrawFilter;
27 | import android.graphics.Path;
28 | import android.graphics.PorterDuff;
29 | import android.graphics.PorterDuffXfermode;
30 | import android.graphics.RectF;
31 | import android.graphics.Region;
32 | import android.graphics.drawable.Drawable;
33 | import android.os.Build;
34 |
35 | public abstract class CropDrawingUtils {
36 |
37 | public static void drawRuleOfThird(Canvas canvas, RectF bounds) {
38 | Paint p = new Paint();
39 | p.setStyle(Paint.Style.STROKE);
40 | p.setColor(Color.argb(128, 255, 255, 255));
41 | p.setStrokeWidth(2);
42 | float stepX = bounds.width() / 3.0f;
43 | float stepY = bounds.height() / 3.0f;
44 | float x = bounds.left + stepX;
45 | float y = bounds.top + stepY;
46 | for (int i = 0; i < 2; i++) {
47 | canvas.drawLine(x, bounds.top, x, bounds.bottom, p);
48 | x += stepX;
49 | }
50 | for (int j = 0; j < 2; j++) {
51 | canvas.drawLine(bounds.left, y, bounds.right, y, p);
52 | y += stepY;
53 | }
54 | }
55 |
56 | public static void drawCropRect(Canvas canvas, RectF bounds) {
57 | Paint p = new Paint();
58 | p.setStyle(Paint.Style.STROKE);
59 | p.setColor(Color.WHITE);
60 | p.setStrokeWidth(3);
61 | canvas.drawRect(bounds, p);
62 | }
63 |
64 | public static void drawShade(Canvas canvas, RectF bounds) {
65 | int w = canvas.getWidth();
66 | int h = canvas.getHeight();
67 | Paint p = new Paint();
68 | p.setStyle(Paint.Style.FILL);
69 | p.setColor(Color.BLACK & 0x88000000);
70 |
71 | RectF r = new RectF();
72 | r.set(0, 0, w, bounds.top);
73 | canvas.drawRect(r, p);
74 | r.set(0, bounds.top, bounds.left, h);
75 | canvas.drawRect(r, p);
76 | r.set(bounds.left, bounds.bottom, w, h);
77 | canvas.drawRect(r, p);
78 | r.set(bounds.right, bounds.top, w, bounds.bottom);
79 | canvas.drawRect(r, p);
80 | }
81 |
82 | public static void drawIndicator(Canvas canvas, Drawable indicator, int indicatorSize,
83 | float centerX, float centerY) {
84 | int left = (int) centerX - indicatorSize / 2;
85 | int top = (int) centerY - indicatorSize / 2;
86 | indicator.setBounds(left, top, left + indicatorSize, top + indicatorSize);
87 | indicator.draw(canvas);
88 | }
89 |
90 | public static void drawIndicators(Canvas canvas, Drawable cropIndicator, int indicatorSize,
91 | RectF bounds, boolean fixedAspect, int selection) {
92 | boolean notMoving = (selection == CropObject.MOVE_NONE);
93 | if (fixedAspect) {
94 | if ((selection == CropObject.TOP_LEFT) || notMoving) {
95 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.top);
96 | }
97 | if ((selection == CropObject.TOP_RIGHT) || notMoving) {
98 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.top);
99 | }
100 | if ((selection == CropObject.BOTTOM_LEFT) || notMoving) {
101 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.bottom);
102 | }
103 | if ((selection == CropObject.BOTTOM_RIGHT) || notMoving) {
104 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.bottom);
105 | }
106 | } else {
107 | if (((selection & CropObject.MOVE_TOP) != 0) || notMoving) {
108 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.top);
109 | }
110 | if (((selection & CropObject.MOVE_BOTTOM) != 0) || notMoving) {
111 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.centerX(), bounds.bottom);
112 | }
113 | if (((selection & CropObject.MOVE_LEFT) != 0) || notMoving) {
114 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.left, bounds.centerY());
115 | }
116 | if (((selection & CropObject.MOVE_RIGHT) != 0) || notMoving) {
117 | drawIndicator(canvas, cropIndicator, indicatorSize, bounds.right, bounds.centerY());
118 | }
119 | }
120 | }
121 |
122 | public static void drawWallpaperSelectionFrame(Canvas canvas, RectF cropBounds, float spotX,
123 | float spotY, Paint p, Paint shadowPaint) {
124 | float sx = cropBounds.width() * spotX;
125 | float sy = cropBounds.height() * spotY;
126 | float cx = cropBounds.centerX();
127 | float cy = cropBounds.centerY();
128 | RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
129 | float temp = sx;
130 | sx = sy;
131 | sy = temp;
132 | RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2);
133 | canvas.save();
134 | canvas.clipRect(cropBounds);
135 | canvas.clipRect(r1, Region.Op.DIFFERENCE);
136 | canvas.clipRect(r2, Region.Op.DIFFERENCE);
137 | canvas.drawPaint(shadowPaint);
138 | canvas.restore();
139 | Path path = new Path();
140 | path.moveTo(r1.left, r1.top);
141 | path.lineTo(r1.right, r1.top);
142 | path.moveTo(r1.left, r1.top);
143 | path.lineTo(r1.left, r1.bottom);
144 | path.moveTo(r1.left, r1.bottom);
145 | path.lineTo(r1.right, r1.bottom);
146 | path.moveTo(r1.right, r1.top);
147 | path.lineTo(r1.right, r1.bottom);
148 | path.moveTo(r2.left, r2.top);
149 | path.lineTo(r2.right, r2.top);
150 | path.moveTo(r2.right, r2.top);
151 | path.lineTo(r2.right, r2.bottom);
152 | path.moveTo(r2.left, r2.bottom);
153 | path.lineTo(r2.right, r2.bottom);
154 | path.moveTo(r2.left, r2.top);
155 | path.lineTo(r2.left, r2.bottom);
156 | canvas.drawPath(path, p);
157 | }
158 |
159 | public static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds) {
160 | canvas.drawRect(outerBounds.left, outerBounds.top, innerBounds.right, innerBounds.top, p);
161 | canvas.drawRect(innerBounds.right, outerBounds.top, outerBounds.right, innerBounds.bottom,
162 | p);
163 | canvas.drawRect(innerBounds.left, innerBounds.bottom, outerBounds.right,
164 | outerBounds.bottom, p);
165 | canvas.drawRect(outerBounds.left, innerBounds.top, innerBounds.left, outerBounds.bottom, p);
166 | }
167 |
168 |
169 | public static void drawCircleShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds) {
170 |
171 | //绘制透明圆
172 | Path circlePath = new Path();
173 | circlePath.addCircle(innerBounds.centerX(), innerBounds.centerY(), Math.min(innerBounds.width() / 2 - 1, innerBounds.height() / 2 - 1), Path.Direction.CCW);
174 |
175 | //绘制矩形遮罩
176 | Path rectPath = new Path();
177 | rectPath.addRect(outerBounds, Path.Direction.CW);
178 | if (Build.VERSION.SDK_INT >= 19) {
179 | rectPath.op(circlePath, Path.Op.DIFFERENCE);
180 | canvas.drawPath(rectPath, p);
181 | } else {
182 | canvas.clipPath(circlePath, Region.Op.DIFFERENCE);
183 | canvas.drawPath(rectPath, p);
184 | }
185 |
186 | //绘制矩形边框
187 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
188 | paint.setColor(Color.WHITE);
189 | paint.setStrokeWidth(2);
190 | paint.setStyle(Paint.Style.STROKE);
191 | canvas.drawRect(innerBounds, paint);
192 |
193 | }
194 |
195 |
196 | public static Matrix getBitmapToDisplayMatrix(RectF imageBounds, RectF displayBounds) {
197 | Matrix m = new Matrix();
198 | CropDrawingUtils.setBitmapToDisplayMatrix(m, imageBounds, displayBounds);
199 | return m;
200 | }
201 |
202 | public static boolean setBitmapToDisplayMatrix(Matrix m, RectF imageBounds,
203 | RectF displayBounds) {
204 | m.reset();
205 | return m.setRectToRect(imageBounds, displayBounds, Matrix.ScaleToFit.CENTER);
206 | }
207 |
208 | public static boolean setImageToScreenMatrix(Matrix dst, RectF image,
209 | RectF screen, int rotation) {
210 | RectF rotatedImage = new RectF();
211 | dst.setRotate(rotation, image.centerX(), image.centerY());
212 | if (!dst.mapRect(rotatedImage, image)) {
213 | return false; // fails for rotations that are not multiples of 90
214 | // degrees
215 | }
216 | boolean rToR = dst.setRectToRect(rotatedImage, screen, Matrix.ScaleToFit.CENTER);
217 | boolean rot = dst.preRotate(rotation, image.centerX(), image.centerY());
218 | return rToR && rot;
219 | }
220 |
221 | }
222 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/CropExtras.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.gallery3d.crop;
18 |
19 | import android.net.Uri;
20 |
21 | public class CropExtras {
22 |
23 | public static final String KEY_CROPPED_RECT = "cropped-rect";
24 | public static final String KEY_OUTPUT_X = "outputX";
25 | public static final String KEY_OUTPUT_Y = "outputY";
26 | public static final String KEY_SCALE = "scale";
27 | public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
28 | public static final String KEY_ASPECT_X = "aspectX";
29 | public static final String KEY_ASPECT_Y = "aspectY";
30 | public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper";
31 | public static final String KEY_RETURN_DATA = "return-data";
32 | public static final String KEY_DATA = "data";
33 | public static final String KEY_SPOTLIGHT_X = "spotlightX";
34 | public static final String KEY_SPOTLIGHT_Y = "spotlightY";
35 | public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked";
36 | public static final String KEY_OUTPUT_FORMAT = "outputFormat";
37 |
38 | private int mOutputX = 0;
39 | private int mOutputY = 0;
40 | private boolean mScaleUp = true;
41 | private int mAspectX = 0;
42 | private int mAspectY = 0;
43 | private boolean mSetAsWallpaper = false;
44 | private boolean mReturnData = false;
45 | private Uri mExtraOutput = null;
46 | private String mOutputFormat = null;
47 | private boolean mShowWhenLocked = false;
48 | private float mSpotlightX = 0;
49 | private float mSpotlightY = 0;
50 |
51 | public CropExtras(int outputX, int outputY, boolean scaleUp, int aspectX, int aspectY,
52 | boolean setAsWallpaper, boolean returnData, Uri extraOutput, String outputFormat,
53 | boolean showWhenLocked, float spotlightX, float spotlightY) {
54 | mOutputX = outputX;
55 | mOutputY = outputY;
56 | mScaleUp = scaleUp;
57 | mAspectX = aspectX;
58 | mAspectY = aspectY;
59 | mSetAsWallpaper = setAsWallpaper;
60 | mReturnData = returnData;
61 | mExtraOutput = extraOutput;
62 | mOutputFormat = outputFormat;
63 | mShowWhenLocked = showWhenLocked;
64 | mSpotlightX = spotlightX;
65 | mSpotlightY = spotlightY;
66 | }
67 |
68 | public CropExtras(CropExtras c) {
69 | this(c.mOutputX, c.mOutputY, c.mScaleUp, c.mAspectX, c.mAspectY, c.mSetAsWallpaper,
70 | c.mReturnData, c.mExtraOutput, c.mOutputFormat, c.mShowWhenLocked,
71 | c.mSpotlightX, c.mSpotlightY);
72 | }
73 |
74 | public int getOutputX() {
75 | return mOutputX;
76 | }
77 |
78 | public int getOutputY() {
79 | return mOutputY;
80 | }
81 |
82 | public boolean getScaleUp() {
83 | return mScaleUp;
84 | }
85 |
86 | public int getAspectX() {
87 | return mAspectX;
88 | }
89 |
90 | public int getAspectY() {
91 | return mAspectY;
92 | }
93 |
94 | public boolean getSetAsWallpaper() {
95 | return mSetAsWallpaper;
96 | }
97 |
98 | public boolean getReturnData() {
99 | return mReturnData;
100 | }
101 |
102 | public Uri getExtraOutput() {
103 | return mExtraOutput;
104 | }
105 |
106 | public String getOutputFormat() {
107 | return mOutputFormat;
108 | }
109 |
110 | public boolean getShowWhenLocked() {
111 | return mShowWhenLocked;
112 | }
113 |
114 | public float getSpotlightX() {
115 | return mSpotlightX;
116 | }
117 |
118 | public float getSpotlightY() {
119 | return mSpotlightY;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/CropMath.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.gallery3d.crop;
18 |
19 | import android.graphics.Bitmap;
20 | import android.graphics.Matrix;
21 | import android.graphics.RectF;
22 |
23 |
24 | import java.util.Arrays;
25 |
26 | public class CropMath {
27 |
28 | /**
29 | * Gets a float array of the 2D coordinates representing a rectangles
30 | * corners.
31 | * The order of the corners in the float array is:
32 | * 0------->1
33 | * ^ |
34 | * | v
35 | * 3<-------2
36 | *
37 | * @param r the rectangle to get the corners of
38 | * @return the float array of corners (8 floats)
39 | */
40 |
41 | public static float[] getCornersFromRect(RectF r) {
42 | float[] corners = {
43 | r.left, r.top,
44 | r.right, r.top,
45 | r.right, r.bottom,
46 | r.left, r.bottom
47 | };
48 | return corners;
49 | }
50 |
51 | /**
52 | * Returns true iff point (x, y) is within or on the rectangle's bounds.
53 | * RectF's "contains" function treats points on the bottom and right bound
54 | * as not being contained.
55 | *
56 | * @param r the rectangle
57 | * @param x the x value of the point
58 | * @param y the y value of the point
59 | * @return
60 | */
61 | public static boolean inclusiveContains(RectF r, float x, float y) {
62 | return !(x > r.right || x < r.left || y > r.bottom || y < r.top);
63 | }
64 |
65 | /**
66 | * Takes an array of 2D coordinates representing corners and returns the
67 | * smallest rectangle containing those coordinates.
68 | *
69 | * @param array array of 2D coordinates
70 | * @return smallest rectangle containing coordinates
71 | */
72 | public static RectF trapToRect(float[] array) {
73 | RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
74 | Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);
75 | for (int i = 1; i < array.length; i += 2) {
76 | float x = array[i - 1];
77 | float y = array[i];
78 | r.left = (x < r.left) ? x : r.left;
79 | r.top = (y < r.top) ? y : r.top;
80 | r.right = (x > r.right) ? x : r.right;
81 | r.bottom = (y > r.bottom) ? y : r.bottom;
82 | }
83 | r.sort();
84 | return r;
85 | }
86 |
87 | /**
88 | * If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the
89 | * image bound rectangle, clamps it to the edge of the rectangle.
90 | *
91 | * @param imageBound the rectangle to clamp edge points to.
92 | * @param array an array of points to clamp to the rectangle, gets set to
93 | * the clamped values.
94 | */
95 | public static void getEdgePoints(RectF imageBound, float[] array) {
96 | if (array.length < 2)
97 | return;
98 | for (int x = 0; x < array.length; x += 2) {
99 | array[x] = GeometryMathUtils.clamp(array[x], imageBound.left, imageBound.right);
100 | array[x + 1] = GeometryMathUtils.clamp(array[x + 1], imageBound.top, imageBound.bottom);
101 | }
102 | }
103 |
104 | /**
105 | * Takes a point and the corners of a rectangle and returns the two corners
106 | * representing the side of the rectangle closest to the point.
107 | *
108 | * @param point the point which is being checked
109 | * @param corners the corners of the rectangle
110 | * @return two corners representing the side of the rectangle
111 | */
112 | public static float[] closestSide(float[] point, float[] corners) {
113 | int len = corners.length;
114 | float oldMag = Float.POSITIVE_INFINITY;
115 | float[] bestLine = null;
116 | for (int i = 0; i < len; i += 2) {
117 | float[] line = {
118 | corners[i], corners[(i + 1) % len],
119 | corners[(i + 2) % len], corners[(i + 3) % len]
120 | };
121 | float mag = GeometryMathUtils.vectorLength(
122 | GeometryMathUtils.shortestVectorFromPointToLine(point, line));
123 | if (mag < oldMag) {
124 | oldMag = mag;
125 | bestLine = line;
126 | }
127 | }
128 | return bestLine;
129 | }
130 |
131 | /**
132 | * Checks if a given point is within a rotated rectangle.
133 | *
134 | * @param point 2D point to check
135 | * @param bound rectangle to rotate
136 | * @param rot angle of rotation about rectangle center
137 | * @return true if point is within rotated rectangle
138 | */
139 | public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) {
140 | Matrix m = new Matrix();
141 | float[] p = Arrays.copyOf(point, 2);
142 | m.setRotate(rot, bound.centerX(), bound.centerY());
143 | Matrix m0 = new Matrix();
144 | if (!m.invert(m0))
145 | return false;
146 | m0.mapPoints(p);
147 | return inclusiveContains(bound, p[0], p[1]);
148 | }
149 |
150 | /**
151 | * Checks if a given point is within a rotated rectangle.
152 | *
153 | * @param point 2D point to check
154 | * @param rotatedRect corners of a rotated rectangle
155 | * @param center center of the rotated rectangle
156 | * @return true if point is within rotated rectangle
157 | */
158 | public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) {
159 | RectF unrotated = new RectF();
160 | float angle = getUnrotated(rotatedRect, center, unrotated);
161 | return pointInRotatedRect(point, unrotated, angle);
162 | }
163 |
164 | /**
165 | * Resizes rectangle to have a certain aspect ratio (center remains
166 | * stationary).
167 | *
168 | * @param r rectangle to resize
169 | * @param w new width aspect
170 | * @param h new height aspect
171 | */
172 | public static void fixAspectRatio(RectF r, float w, float h) {
173 | float scale = Math.min(r.width() / w, r.height() / h);
174 | float centX = r.centerX();
175 | float centY = r.centerY();
176 | float hw = scale * w / 2;
177 | float hh = scale * h / 2;
178 | r.set(centX - hw, centY - hh, centX + hw, centY + hh);
179 | }
180 |
181 | /**
182 | * Resizes rectangle to have a certain aspect ratio (center remains
183 | * stationary) while constraining it to remain within the original rect.
184 | *
185 | * @param r rectangle to resize
186 | * @param w new width aspect
187 | * @param h new height aspect
188 | */
189 | public static void fixAspectRatioContained(RectF r, float w, float h) {
190 | float origW = r.width();
191 | float origH = r.height();
192 | float origA = origW / origH;
193 | float a = w / h;
194 | float finalW = origW;
195 | float finalH = origH;
196 | if (origA < a) {
197 | finalH = origW / a;
198 | r.top = r.centerY() - finalH / 2;
199 | r.bottom = r.top + finalH;
200 | } else {
201 | finalW = origH * a;
202 | r.left = r.centerX() - finalW / 2;
203 | r.right = r.left + finalW;
204 | }
205 | }
206 |
207 | /**
208 | * Stretches/Scales/Translates photoBounds to match displayBounds, and
209 | * and returns an equivalent stretched/scaled/translated cropBounds or null
210 | * if the mapping is invalid.
211 | * @param cropBounds cropBounds to transform
212 | * @param photoBounds original bounds containing crop bounds
213 | * @param displayBounds final bounds for crop
214 | * @return the stretched/scaled/translated crop bounds that fit within displayBounds
215 | */
216 | public static RectF getScaledCropBounds(RectF cropBounds, RectF photoBounds,
217 | RectF displayBounds) {
218 | Matrix m = new Matrix();
219 | m.setRectToRect(photoBounds, displayBounds, Matrix.ScaleToFit.FILL);
220 | RectF trueCrop = new RectF(cropBounds);
221 | if (!m.mapRect(trueCrop)) {
222 | return null;
223 | }
224 | return trueCrop;
225 | }
226 |
227 | /**
228 | * Returns the size of a bitmap in bytes.
229 | * @param bmap bitmap whose size to check
230 | * @return bitmap size in bytes
231 | */
232 | public static int getBitmapSize(Bitmap bmap) {
233 | return bmap.getRowBytes() * bmap.getHeight();
234 | }
235 |
236 | /**
237 | * Constrains rotation to be in [0, 90, 180, 270] rounding down.
238 | * @param rotation any rotation value, in degrees
239 | * @return integer rotation in [0, 90, 180, 270]
240 | */
241 | public static int constrainedRotation(float rotation) {
242 | int r = (int) ((rotation % 360) / 90);
243 | r = (r < 0) ? (r + 4) : r;
244 | return r * 90;
245 | }
246 |
247 | private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) {
248 | float dy = rotatedRect[1] - rotatedRect[3];
249 | float dx = rotatedRect[0] - rotatedRect[2];
250 | float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI);
251 | Matrix m = new Matrix();
252 | m.setRotate(-angle, center[0], center[1]);
253 | float[] unrotatedRect = new float[rotatedRect.length];
254 | m.mapPoints(unrotatedRect, rotatedRect);
255 | unrotated.set(trapToRect(unrotatedRect));
256 | return angle;
257 | }
258 |
259 | }
260 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/CropObject.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.gallery3d.crop;
18 |
19 | import android.graphics.Rect;
20 | import android.graphics.RectF;
21 |
22 |
23 | public class CropObject {
24 | private BoundedRect mBoundedRect;
25 | private float mAspectWidth = 1;
26 | private float mAspectHeight = 1;
27 | private boolean mFixAspectRatio = false;
28 | private float mRotation = 0;
29 | private float mTouchTolerance = 45;
30 | private float mMinSideSize = 20;
31 |
32 | public static final int MOVE_NONE = 0;
33 | // Sides
34 | public static final int MOVE_LEFT = 1;
35 | public static final int MOVE_TOP = 2;
36 | public static final int MOVE_RIGHT = 4;
37 | public static final int MOVE_BOTTOM = 8;
38 | public static final int MOVE_BLOCK = 16;
39 |
40 | // Corners
41 | public static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT;
42 | public static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT;
43 | public static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT;
44 | public static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT;
45 |
46 | private int mMovingEdges = MOVE_NONE;
47 | private float mMinSideHeight;
48 |
49 | public CropObject(Rect outerBound, Rect innerBound, int outerAngle) {
50 | mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
51 | }
52 |
53 | public CropObject(RectF outerBound, RectF innerBound, int outerAngle) {
54 | mBoundedRect = new BoundedRect(outerAngle % 360, outerBound, innerBound);
55 | }
56 |
57 | public void resetBoundsTo(RectF inner, RectF outer) {
58 | mBoundedRect.resetTo(0, outer, inner);
59 | }
60 |
61 | public void getInnerBounds(RectF r) {
62 | mBoundedRect.setToInner(r);
63 | }
64 |
65 | public void getOuterBounds(RectF r) {
66 | mBoundedRect.setToOuter(r);
67 | }
68 |
69 | public RectF getInnerBounds() {
70 | return mBoundedRect.getInner();
71 | }
72 |
73 | public RectF getOuterBounds() {
74 | return mBoundedRect.getOuter();
75 | }
76 |
77 | public int getSelectState() {
78 | return mMovingEdges;
79 | }
80 |
81 | public boolean isFixedAspect() {
82 | return mFixAspectRatio;
83 | }
84 |
85 | public void rotateOuter(int angle) {
86 | mRotation = angle % 360;
87 | mBoundedRect.setRotation(mRotation);
88 | clearSelectState();
89 | }
90 |
91 | public boolean setInnerAspectRatio(float width, float height) {
92 | if (width <= 0 || height <= 0) {
93 | throw new IllegalArgumentException("Width and Height must be greater than zero");
94 | }
95 | RectF inner = mBoundedRect.getInner();
96 | CropMath.fixAspectRatioContained(inner, width, height);
97 | if (inner.width() < mMinSideSize || inner.height() < mMinSideSize) {
98 | return false;
99 | }
100 | mAspectWidth = width;
101 | mAspectHeight = height;
102 | mFixAspectRatio = true;
103 | mBoundedRect.setInner(inner);
104 | clearSelectState();
105 | return true;
106 | }
107 |
108 | public void setTouchTolerance(float tolerance) {
109 | if (tolerance <= 0) {
110 | throw new IllegalArgumentException("Tolerance must be greater than zero");
111 | }
112 | mTouchTolerance = tolerance;
113 | }
114 |
115 | public void setMinInnerSideSize(float minSideWidth, float minSideHeight) {
116 | if (minSideWidth <= 0 || minSideHeight < 0) {
117 | throw new IllegalArgumentException("Min dide must be greater than zero");
118 | }
119 | mMinSideSize = minSideWidth;
120 | mMinSideHeight = minSideHeight;
121 | }
122 |
123 | public void unsetAspectRatio() {
124 | mFixAspectRatio = false;
125 | clearSelectState();
126 | }
127 |
128 | public boolean hasSelectedEdge() {
129 | return mMovingEdges != MOVE_NONE;
130 | }
131 |
132 | public static boolean checkCorner(int selected) {
133 | return selected == TOP_LEFT || selected == TOP_RIGHT || selected == BOTTOM_RIGHT
134 | || selected == BOTTOM_LEFT;
135 | }
136 |
137 | public static boolean checkEdge(int selected) {
138 | return selected == MOVE_LEFT || selected == MOVE_TOP || selected == MOVE_RIGHT
139 | || selected == MOVE_BOTTOM;
140 | }
141 |
142 | public static boolean checkBlock(int selected) {
143 | return selected == MOVE_BLOCK;
144 | }
145 |
146 | public static boolean checkValid(int selected) {
147 | return selected == MOVE_NONE || checkBlock(selected) || checkEdge(selected)
148 | || checkCorner(selected);
149 | }
150 |
151 | public void clearSelectState() {
152 | mMovingEdges = MOVE_NONE;
153 | }
154 |
155 | public int wouldSelectEdge(float x, float y) {
156 | int edgeSelected = calculateSelectedEdge(x, y);
157 | if (edgeSelected != MOVE_NONE && edgeSelected != MOVE_BLOCK) {
158 | return edgeSelected;
159 | }
160 | return MOVE_NONE;
161 | }
162 |
163 | public boolean selectEdge(int edge) {
164 | if (!checkValid(edge)) {
165 | // temporary
166 | throw new IllegalArgumentException("bad edge selected");
167 | // return false;
168 | }
169 | if ((mFixAspectRatio && !checkCorner(edge)) && !checkBlock(edge) && edge != MOVE_NONE) {
170 | // temporary
171 | throw new IllegalArgumentException("bad corner selected");
172 | // return false;
173 | }
174 | mMovingEdges = edge;
175 | return true;
176 | }
177 |
178 | public boolean selectEdge(float x, float y) {
179 | int edgeSelected = calculateSelectedEdge(x, y);
180 | if (mFixAspectRatio) {
181 | edgeSelected = fixEdgeToCorner(edgeSelected);
182 | }
183 | if (edgeSelected == MOVE_NONE) {
184 | return false;
185 | }
186 | return selectEdge(edgeSelected);
187 | }
188 |
189 | public boolean moveCurrentSelection(float dX, float dY) {
190 | if (mMovingEdges == MOVE_NONE) {
191 | return false;
192 | }
193 | RectF crop = mBoundedRect.getInner();
194 |
195 | float minWidthHeight = mMinSideSize;
196 |
197 | int movingEdges = mMovingEdges;
198 | if (movingEdges == MOVE_BLOCK) {
199 | mBoundedRect.moveInner(dX, dY);
200 | return true;
201 | } else {
202 | float dx = 0;
203 | float dy = 0;
204 |
205 | if ((movingEdges & MOVE_LEFT) != 0) {
206 | dx = Math.min(crop.left + dX, crop.right - mMinSideSize) - crop.left;
207 | }
208 | if ((movingEdges & MOVE_TOP) != 0) {
209 | dy = Math.min(crop.top + dY, crop.bottom - mMinSideHeight) - crop.top;
210 | }
211 | if ((movingEdges & MOVE_RIGHT) != 0) {
212 | dx = Math.max(crop.right + dX, crop.left + mMinSideSize)
213 | - crop.right;
214 | }
215 | if ((movingEdges & MOVE_BOTTOM) != 0) {
216 | dy = Math.max(crop.bottom + dY, crop.top + mMinSideHeight)
217 | - crop.bottom;
218 | }
219 |
220 | if (mFixAspectRatio) {
221 | float[] l1 = {
222 | crop.left, crop.bottom
223 | };
224 | float[] l2 = {
225 | crop.right, crop.top
226 | };
227 | if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) {
228 | l1[1] = crop.top;
229 | l2[1] = crop.bottom;
230 | }
231 | float[] b = {
232 | l1[0] - l2[0], l1[1] - l2[1]
233 | };
234 | float[] disp = {
235 | dx, dy
236 | };
237 | float[] bUnit = GeometryMathUtils.normalize(b);
238 | float sp = GeometryMathUtils.scalarProjection(disp, bUnit);
239 | dx = sp * bUnit[0];
240 | dy = sp * bUnit[1];
241 | RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy);
242 |
243 | mBoundedRect.fixedAspectResizeInner(newCrop);
244 | } else {
245 | if ((movingEdges & MOVE_LEFT) != 0) {
246 | crop.left += dx;
247 | }
248 | if ((movingEdges & MOVE_TOP) != 0) {
249 | crop.top += dy;
250 | }
251 | if ((movingEdges & MOVE_RIGHT) != 0) {
252 | crop.right += dx;
253 | }
254 | if ((movingEdges & MOVE_BOTTOM) != 0) {
255 | crop.bottom += dy;
256 | }
257 | mBoundedRect.resizeInner(crop);
258 | }
259 | }
260 | return true;
261 | }
262 |
263 | // Helper methods
264 |
265 | private int calculateSelectedEdge(float x, float y) {
266 | RectF cropped = mBoundedRect.getInner();
267 |
268 | float left = Math.abs(x - cropped.left);
269 | float right = Math.abs(x - cropped.right);
270 | float top = Math.abs(y - cropped.top);
271 | float bottom = Math.abs(y - cropped.bottom);
272 |
273 | int edgeSelected = MOVE_NONE;
274 | // Check left or right.
275 | if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
276 | && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) {
277 | edgeSelected |= MOVE_LEFT;
278 | } else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top)
279 | && ((y - mTouchTolerance) <= cropped.bottom)) {
280 | edgeSelected |= MOVE_RIGHT;
281 | }
282 |
283 | // Check top or bottom.
284 | if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
285 | && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) {
286 | edgeSelected |= MOVE_TOP;
287 | } else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left)
288 | && ((x - mTouchTolerance) <= cropped.right)) {
289 | edgeSelected |= MOVE_BOTTOM;
290 | }
291 | return edgeSelected;
292 | }
293 |
294 | private static RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) {
295 | RectF newCrop = null;
296 | // Fix opposite corner in place and move sides
297 | if (moving_corner == BOTTOM_RIGHT) {
298 | newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height()
299 | + dy);
300 | } else if (moving_corner == BOTTOM_LEFT) {
301 | newCrop = new RectF(r.right - r.width() + dx, r.top, r.right, r.top + r.height()
302 | + dy);
303 | } else if (moving_corner == TOP_LEFT) {
304 | newCrop = new RectF(r.right - r.width() + dx, r.bottom - r.height() + dy,
305 | r.right, r.bottom);
306 | } else if (moving_corner == TOP_RIGHT) {
307 | newCrop = new RectF(r.left, r.bottom - r.height() + dy, r.left
308 | + r.width() + dx, r.bottom);
309 | }
310 | return newCrop;
311 | }
312 |
313 | private static int fixEdgeToCorner(int moving_edges) {
314 | if (moving_edges == MOVE_LEFT) {
315 | moving_edges |= MOVE_TOP;
316 | }
317 | if (moving_edges == MOVE_TOP) {
318 | moving_edges |= MOVE_LEFT;
319 | }
320 | if (moving_edges == MOVE_RIGHT) {
321 | moving_edges |= MOVE_BOTTOM;
322 | }
323 | if (moving_edges == MOVE_BOTTOM) {
324 | moving_edges |= MOVE_RIGHT;
325 | }
326 | return moving_edges;
327 | }
328 |
329 | }
330 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/CropView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2013 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.gallery3d.crop;
18 |
19 | import android.content.Context;
20 | import android.content.res.Resources;
21 | import android.graphics.Bitmap;
22 | import android.graphics.Canvas;
23 | import android.graphics.DashPathEffect;
24 | import android.graphics.Matrix;
25 | import android.graphics.Paint;
26 | import android.graphics.Rect;
27 | import android.graphics.RectF;
28 | import android.graphics.drawable.Drawable;
29 | import android.graphics.drawable.NinePatchDrawable;
30 | import android.util.AttributeSet;
31 | import android.util.Log;
32 | import android.view.MotionEvent;
33 | import android.view.View;
34 |
35 | import com.android.gallery3d.R;
36 |
37 |
38 | public class CropView extends View {
39 | private static final String LOGTAG = "CropView";
40 |
41 | private RectF mImageBounds = new RectF();
42 | private RectF mScreenBounds = new RectF();
43 | private RectF mScreenImageBounds = new RectF();
44 | private RectF mScreenCropBounds = new RectF();
45 | private Rect mShadowBounds = new Rect();
46 |
47 | private Bitmap mBitmap;
48 | private boolean mAsCircle;
49 | private boolean mDrawCropGrid;
50 | private int mMinCropWidth;
51 | private int mMinCropHeight;
52 | private Paint mPaint = new Paint();
53 |
54 | private NinePatchDrawable mShadow;
55 | private CropObject mCropObj = null;
56 | private Drawable mCropIndicator;
57 | private int mIndicatorSize;
58 | private int mRotation = 0;
59 | private boolean mMovingBlock = false;
60 | private Matrix mDisplayMatrix = null;
61 | private Matrix mDisplayMatrixInverse = null;
62 | private boolean mDirty = false;
63 |
64 | private float mPrevX = 0;
65 | private float mPrevY = 0;
66 | private float mSpotX = 0;
67 | private float mSpotY = 0;
68 | private boolean mDoSpot = false;
69 |
70 | private int mShadowMargin = 15;
71 | private int mMargin = 32;
72 | private int mOverlayShadowColor = 0xCF000000;
73 | private int mOverlayWPShadowColor = 0x5F000000;
74 | private int mWPMarkerColor = 0x7FFFFFFF;
75 | private int mMinSideSize = 90;
76 | private int mTouchTolerance = 40;
77 | private float mDashOnLength = 20;
78 | private float mDashOffLength = 10;
79 |
80 | private enum Mode {
81 | NONE, MOVE
82 | }
83 |
84 | private Mode mState = Mode.NONE;
85 |
86 | public CropView(Context context) {
87 | super(context);
88 | setup(context);
89 | }
90 |
91 | public CropView(Context context, AttributeSet attrs) {
92 | super(context, attrs);
93 | setup(context);
94 | }
95 |
96 | public CropView(Context context, AttributeSet attrs, int defStyle) {
97 | super(context, attrs, defStyle);
98 | setup(context);
99 | }
100 |
101 | private void setup(Context context) {
102 | setLayerType(View.LAYER_TYPE_SOFTWARE, null);
103 | Resources rsc = context.getResources();
104 | mShadow = (NinePatchDrawable) rsc.getDrawable(R.drawable.geometry_shadow);
105 | mCropIndicator = rsc.getDrawable(R.drawable.camera_crop);
106 | mIndicatorSize = (int) rsc.getDimension(R.dimen.crop_indicator_size);
107 | mShadowMargin = (int) rsc.getDimension(R.dimen.shadow_margin);
108 | mMargin = (int) rsc.getDimension(R.dimen.preview_margin);
109 | mMinSideSize = (int) rsc.getDimension(R.dimen.crop_min_side);
110 | mTouchTolerance = (int) rsc.getDimension(R.dimen.crop_touch_tolerance);
111 | mOverlayShadowColor = (int) rsc.getColor(R.color.crop_shadow_color);
112 | mOverlayWPShadowColor = (int) rsc.getColor(R.color.crop_shadow_wp_color);
113 | mWPMarkerColor = (int) rsc.getColor(R.color.crop_wp_markers);
114 | mDashOnLength = rsc.getDimension(R.dimen.wp_selector_dash_length);
115 | mDashOffLength = rsc.getDimension(R.dimen.wp_selector_off_length);
116 | }
117 |
118 | public void initialize(Bitmap image, RectF newCropBounds, RectF newPhotoBounds, int rotation, boolean asCircle, boolean drawCropGrid, int minCropWidth, int minCropHeight) {
119 | mBitmap = image;
120 | mAsCircle = asCircle;
121 | mDrawCropGrid = drawCropGrid;
122 | mMinCropWidth = minCropWidth;
123 | mMinCropHeight = minCropHeight;
124 |
125 | if (mMinCropWidth <= 0)
126 | mMinCropWidth = mMinSideSize;
127 | if (mMinCropHeight <= 0)
128 | mMinCropHeight = mMinSideSize;
129 |
130 | if (mCropObj != null) {
131 | RectF crop = mCropObj.getInnerBounds();
132 | RectF containing = mCropObj.getOuterBounds();
133 | if (crop != newCropBounds || containing != newPhotoBounds
134 | || mRotation != rotation) {
135 | mRotation = rotation;
136 | mCropObj.resetBoundsTo(newCropBounds, newPhotoBounds);
137 | clearDisplay();
138 | }
139 | } else {
140 | mRotation = rotation;
141 | mCropObj = new CropObject(newPhotoBounds, newCropBounds, 0);
142 | clearDisplay();
143 | }
144 | }
145 |
146 | public RectF getCrop() {
147 | return mCropObj.getInnerBounds();
148 | }
149 |
150 | public RectF getPhoto() {
151 | return mCropObj.getOuterBounds();
152 | }
153 |
154 | @Override
155 | public boolean onTouchEvent(MotionEvent event) {
156 | float x = event.getX();
157 | float y = event.getY();
158 | if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
159 | return true;
160 | }
161 | float[] touchPoint = {
162 | x, y
163 | };
164 | mDisplayMatrixInverse.mapPoints(touchPoint);
165 | x = touchPoint[0];
166 | y = touchPoint[1];
167 | switch (event.getActionMasked()) {
168 | case (MotionEvent.ACTION_DOWN):
169 | if (mState == Mode.NONE) {
170 | if (!mCropObj.selectEdge(x, y)) {
171 | mMovingBlock = mCropObj.selectEdge(CropObject.MOVE_BLOCK);
172 | }
173 | mPrevX = x;
174 | mPrevY = y;
175 | mState = Mode.MOVE;
176 | }
177 | break;
178 | case (MotionEvent.ACTION_UP):
179 | if (mState == Mode.MOVE) {
180 | mCropObj.selectEdge(CropObject.MOVE_NONE);
181 | mMovingBlock = false;
182 | mPrevX = x;
183 | mPrevY = y;
184 | mState = Mode.NONE;
185 | }
186 | break;
187 | case (MotionEvent.ACTION_MOVE):
188 | if (mState == Mode.MOVE) {
189 | float dx = x - mPrevX;
190 | float dy = y - mPrevY;
191 | mCropObj.moveCurrentSelection(dx, dy);
192 | mPrevX = x;
193 | mPrevY = y;
194 | }
195 | break;
196 | default:
197 | break;
198 | }
199 | invalidate();
200 | return true;
201 | }
202 |
203 | private void reset() {
204 | Log.w(LOGTAG, "crop reset called");
205 | mState = Mode.NONE;
206 | mCropObj = null;
207 | mRotation = 0;
208 | mMovingBlock = false;
209 | clearDisplay();
210 | }
211 |
212 | private void clearDisplay() {
213 | mDisplayMatrix = null;
214 | mDisplayMatrixInverse = null;
215 | invalidate();
216 | }
217 |
218 | protected void configChanged() {
219 | mDirty = true;
220 | }
221 |
222 | public void applyFreeAspect() {
223 | mCropObj.unsetAspectRatio();
224 | invalidate();
225 | }
226 |
227 | public void applyOriginalAspect() {
228 | RectF outer = mCropObj.getOuterBounds();
229 | float w = outer.width();
230 | float h = outer.height();
231 | if (w > 0 && h > 0) {
232 | applyAspect(w, h);
233 | mCropObj.resetBoundsTo(outer, outer);
234 | } else {
235 | Log.w(LOGTAG, "failed to set aspect ratio original");
236 | }
237 | }
238 |
239 | public void applySquareAspect() {
240 | applyAspect(1, 1);
241 | }
242 |
243 | public void applyAspect(float x, float y) {
244 | if (x <= 0 || y <= 0) {
245 | throw new IllegalArgumentException("Bad arguments to applyAspect");
246 | }
247 | // If we are rotated by 90 degrees from horizontal, swap x and y
248 | if (((mRotation < 0) ? -mRotation : mRotation) % 180 == 90) {
249 | float tmp = x;
250 | x = y;
251 | y = tmp;
252 | }
253 | if (!mCropObj.setInnerAspectRatio(x, y)) {
254 | Log.w(LOGTAG, "failed to set aspect ratio");
255 | }
256 | invalidate();
257 | }
258 |
259 | public void setWallpaperSpotlight(float spotlightX, float spotlightY) {
260 | mSpotX = spotlightX;
261 | mSpotY = spotlightY;
262 | if (mSpotX > 0 && mSpotY > 0) {
263 | mDoSpot = true;
264 | }
265 | }
266 |
267 | public void unsetWallpaperSpotlight() {
268 | mDoSpot = false;
269 | }
270 |
271 | /**
272 | * Rotates first d bits in integer x to the left some number of times.
273 | */
274 | private int bitCycleLeft(int x, int times, int d) {
275 | int mask = (1 << d) - 1;
276 | int mout = x & mask;
277 | times %= d;
278 | int hi = mout >> (d - times);
279 | int low = (mout << times) & mask;
280 | int ret = x & ~mask;
281 | ret |= low;
282 | ret |= hi;
283 | return ret;
284 | }
285 |
286 | /**
287 | * Find the selected edge or corner in screen coordinates.
288 | */
289 | private int decode(int movingEdges, float rotation) {
290 | int rot = CropMath.constrainedRotation(rotation);
291 | switch (rot) {
292 | case 90:
293 | return bitCycleLeft(movingEdges, 1, 4);
294 | case 180:
295 | return bitCycleLeft(movingEdges, 2, 4);
296 | case 270:
297 | return bitCycleLeft(movingEdges, 3, 4);
298 | default:
299 | return movingEdges;
300 | }
301 | }
302 |
303 | @Override
304 | public void onDraw(Canvas canvas) {
305 | if (mBitmap == null) {
306 | return;
307 | }
308 | if (mDirty) {
309 | mDirty = false;
310 | clearDisplay();
311 | }
312 |
313 | mImageBounds = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
314 | mScreenBounds = new RectF(0, 0, canvas.getWidth(), canvas.getHeight());
315 | mScreenBounds.inset(mMargin, mMargin);
316 |
317 | // If crop object doesn't exist, create it and update it from master
318 | // state
319 | if (mCropObj == null) {
320 | reset();
321 | mCropObj = new CropObject(mImageBounds, mImageBounds, 0);
322 | }
323 |
324 | // If display matrix doesn't exist, create it and its dependencies
325 | if (mDisplayMatrix == null || mDisplayMatrixInverse == null) {
326 | mDisplayMatrix = new Matrix();
327 | mDisplayMatrix.reset();
328 | if (!CropDrawingUtils.setImageToScreenMatrix(mDisplayMatrix, mImageBounds, mScreenBounds,
329 | mRotation)) {
330 | Log.w(LOGTAG, "failed to get screen matrix");
331 | mDisplayMatrix = null;
332 | return;
333 | }
334 | mDisplayMatrixInverse = new Matrix();
335 | mDisplayMatrixInverse.reset();
336 | if (!mDisplayMatrix.invert(mDisplayMatrixInverse)) {
337 | Log.w(LOGTAG, "could not invert display matrix");
338 | mDisplayMatrixInverse = null;
339 | return;
340 | }
341 | // Scale min side and tolerance by display matrix scale factor
342 | float mapRadiusWidth = mDisplayMatrixInverse.mapRadius(mMinCropWidth);
343 | float mapRadiusHeight = mDisplayMatrixInverse.mapRadius(mMinCropHeight);
344 | mCropObj.setMinInnerSideSize(mapRadiusWidth, mapRadiusHeight);//
345 | mCropObj.setTouchTolerance(mDisplayMatrixInverse.mapRadius(mTouchTolerance));
346 | }
347 |
348 | mScreenImageBounds.set(mImageBounds);
349 |
350 | // Draw background shadow
351 | if (mDisplayMatrix.mapRect(mScreenImageBounds)) {
352 | int margin = (int) mDisplayMatrix.mapRadius(mShadowMargin);
353 | mScreenImageBounds.roundOut(mShadowBounds);
354 | mShadowBounds.set(mShadowBounds.left - margin, mShadowBounds.top -
355 | margin, mShadowBounds.right + margin, mShadowBounds.bottom + margin);
356 | mShadow.setBounds(mShadowBounds);
357 | mShadow.draw(canvas);
358 | }
359 |
360 | mPaint.setAntiAlias(true);
361 | mPaint.setFilterBitmap(true);
362 | // Draw actual bitmap
363 | canvas.drawBitmap(mBitmap, mDisplayMatrix, mPaint);
364 |
365 | mCropObj.getInnerBounds(mScreenCropBounds);
366 |
367 | if (mDisplayMatrix.mapRect(mScreenCropBounds)) {
368 |
369 | // Draw overlay shadows
370 | Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
371 | p.setAntiAlias(true);
372 | p.setColor(mOverlayShadowColor);
373 | p.setStyle(Paint.Style.FILL);
374 | if (mAsCircle) {
375 | if (mDrawCropGrid)
376 | CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
377 | CropDrawingUtils.drawCircleShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
378 |
379 | } else {
380 | CropDrawingUtils.drawShadows(canvas, p, mScreenCropBounds, mScreenImageBounds);
381 | CropDrawingUtils.drawCropRect(canvas, mScreenCropBounds);
382 | }
383 | // Draw crop rect and markers
384 | //
385 |
386 | if (!mDoSpot) {
387 | if (!mAsCircle && mDrawCropGrid)
388 | CropDrawingUtils.drawRuleOfThird(canvas, mScreenCropBounds);
389 | } else {
390 | Paint wpPaint = new Paint();
391 | wpPaint.setColor(mWPMarkerColor);
392 | wpPaint.setStrokeWidth(3);
393 | wpPaint.setStyle(Paint.Style.STROKE);
394 | wpPaint.setPathEffect(new DashPathEffect(new float[]
395 | {mDashOnLength, mDashOnLength + mDashOffLength}, 0));
396 | p.setColor(mOverlayWPShadowColor);
397 | CropDrawingUtils.drawWallpaperSelectionFrame(canvas, mScreenCropBounds,
398 | mSpotX, mSpotY, wpPaint, p);
399 | }
400 | CropDrawingUtils.drawIndicators(canvas, mCropIndicator, mIndicatorSize,
401 | mScreenCropBounds, mCropObj.isFixedAspect(), decode(mCropObj.getSelectState(), mRotation));
402 | }
403 |
404 | }
405 | }
406 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/GeometryMathUtils.java:
--------------------------------------------------------------------------------
1 | package com.android.gallery3d.crop;
2 |
3 | /**
4 | * Created by qiaomu on 2017/10/24.
5 | */
6 |
7 | public class GeometryMathUtils {
8 |
9 | public static float clamp(float i, float low, float high) {
10 | return Math.max(Math.min(i, high), low);
11 | }
12 |
13 | public static float vectorLength(float[] a) {
14 | return (float) Math.hypot(a[0], a[1]);
15 | }
16 |
17 | public static float[] shortestVectorFromPointToLine(float[] point, float[] line) {
18 | float x1 = line[0];
19 | float x2 = line[2];
20 | float y1 = line[1];
21 | float y2 = line[3];
22 | float xdelt = x2 - x1;
23 | float ydelt = y2 - y1;
24 | if (xdelt == 0 && ydelt == 0)
25 | return null;
26 | float u = ((point[0] - x1) * xdelt + (point[1] - y1) * ydelt)
27 | / (xdelt * xdelt + ydelt * ydelt);
28 | float[] ret = {
29 | (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1))
30 | };
31 | float[] vec = {
32 | ret[0] - point[0], ret[1] - point[1]
33 | };
34 | return vec;
35 | }
36 |
37 |
38 | public static float[] normalize(float[] a) {
39 | float length = (float) Math.hypot(a[0], a[1]);
40 | float[] b = {
41 | a[0] / length, a[1] / length
42 | };
43 | return b;
44 | }
45 |
46 | // A onto B
47 | public static float scalarProjection(float[] a, float[] b) {
48 | float length = (float) Math.hypot(b[0], b[1]);
49 | return dotProduct(a, b) / length;
50 | }
51 | // A . B
52 | public static float dotProduct(float[] a, float[] b) {
53 | return a[0] * b[0] + a[1] * b[1];
54 | }
55 |
56 |
57 | public static float[] lineIntersect(float[] line1, float[] line2) {
58 | float a0 = line1[0];
59 | float a1 = line1[1];
60 | float b0 = line1[2];
61 | float b1 = line1[3];
62 | float c0 = line2[0];
63 | float c1 = line2[1];
64 | float d0 = line2[2];
65 | float d1 = line2[3];
66 | float t0 = a0 - b0;
67 | float t1 = a1 - b1;
68 | float t2 = b0 - d0;
69 | float t3 = d1 - b1;
70 | float t4 = c0 - d0;
71 | float t5 = c1 - d1;
72 |
73 | float denom = t1 * t4 - t0 * t5;
74 | if (denom == 0)
75 | return null;
76 | float u = (t3 * t4 + t5 * t2) / denom;
77 | float[] intersect = {
78 | b0 + u * t0, b1 + u * t1
79 | };
80 | return intersect;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/ImageLoader.java:
--------------------------------------------------------------------------------
1 | package com.android.gallery3d.crop;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.Context;
5 | import android.database.Cursor;
6 | import android.database.sqlite.SQLiteException;
7 | import android.graphics.Bitmap;
8 | import android.graphics.BitmapFactory;
9 | import android.graphics.Rect;
10 | import android.media.ExifInterface;
11 | import android.net.Uri;
12 | import android.provider.MediaStore;
13 | import android.util.Log;
14 | import android.webkit.MimeTypeMap;
15 |
16 | import java.io.FileNotFoundException;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 |
20 | /**
21 | * Created by qiaomu on 2017/10/24.
22 | */
23 |
24 | public class ImageLoader {
25 | private static final String LOGTAG = "ImageLoader";
26 |
27 |
28 | public static final String JPEG_MIME_TYPE = "image/jpeg";
29 | public static final int DEFAULT_COMPRESS_QUALITY = 95;
30 |
31 | public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL;
32 | public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90;
33 | public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180;
34 | public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270;
35 | public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL;
36 | public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL;
37 | public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE;
38 | public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE;
39 |
40 | private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
41 | private static final float OVERDRAW_ZOOM = 1.2f;
42 |
43 | /**
44 | * Loads a bitmap at a given URI that is downsampled so that both sides are
45 | * smaller than maxSideLength. The Bitmap's original dimensions are stored
46 | * in the rect originalBounds.
47 | *
48 | * @param uri URI of image to open.
49 | * @param context context whose ContentResolver to use.
50 | * @param maxSideLength max side length of returned bitmap.
51 | * @param originalBounds If not null, set to the actual bounds of the stored bitmap.
52 | * @param useMin use min or max side of the original image
53 | * @return downsampled bitmap or null if this operation failed.
54 | */
55 | public static Bitmap loadConstrainedBitmap(Uri uri, Context context, int maxSideLength,
56 | Rect originalBounds, boolean useMin) {
57 | if (maxSideLength <= 0 || uri == null || context == null) {
58 | throw new IllegalArgumentException("bad argument to getScaledBitmap");
59 | }
60 | // Get width and height of stored bitmap
61 | Rect storedBounds = loadBitmapBounds(context, uri);
62 | if (originalBounds != null) {
63 | originalBounds.set(storedBounds);
64 | }
65 | int w = storedBounds.width();
66 | int h = storedBounds.height();
67 |
68 | // If bitmap cannot be decoded, return null
69 | if (w <= 0 || h <= 0) {
70 | return null;
71 | }
72 |
73 | // Find best downsampling size
74 | int imageSide = 0;
75 | if (useMin) {
76 | imageSide = Math.min(w, h);
77 | } else {
78 | imageSide = Math.max(w, h);
79 | }
80 | int sampleSize = 1;
81 | while (imageSide > maxSideLength) {
82 | imageSide >>>= 1;
83 | sampleSize <<= 1;
84 | }
85 |
86 | // Make sure sample size is reasonable
87 | if (sampleSize <= 0 ||
88 | 0 >= (int) (Math.min(w, h) / sampleSize)) {
89 | return null;
90 | }
91 | return loadDownsampledBitmap(context, uri, sampleSize);
92 | }
93 |
94 |
95 | /**
96 | * Returns the bounds of the bitmap stored at a given Url.
97 | */
98 | public static Rect loadBitmapBounds(Context context, Uri uri) {
99 | BitmapFactory.Options o = new BitmapFactory.Options();
100 | o.inJustDecodeBounds = true;
101 | loadBitmap(context, uri, o);
102 | return new Rect(0, 0, o.outWidth, o.outHeight);
103 | }
104 |
105 | /**
106 | * Loads a bitmap that has been downsampled using sampleSize from a given url.
107 | */
108 | public static Bitmap loadDownsampledBitmap(Context context, Uri uri, int sampleSize) {
109 | BitmapFactory.Options options = new BitmapFactory.Options();
110 | options.inMutable = true;
111 | options.inSampleSize = sampleSize;
112 | return loadBitmap(context, uri, options);
113 | }
114 |
115 | /**
116 | * Returns the bitmap from the given uri loaded using the given options.
117 | * Returns null on failure.
118 | */
119 | public static Bitmap loadBitmap(Context context, Uri uri, BitmapFactory.Options o) {
120 | if (uri == null || context == null) {
121 | throw new IllegalArgumentException("bad argument to loadBitmap");
122 | }
123 | InputStream is = null;
124 | try {
125 | is = context.getContentResolver().openInputStream(uri);
126 | return BitmapFactory.decodeStream(is, null, o);
127 | } catch (FileNotFoundException e) {
128 | Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
129 | } finally {
130 | Utils.closeSilently(is);
131 | }
132 | return null;
133 | }
134 |
135 |
136 | /**
137 | * Returns the rotation of image at the given URI as one of 0, 90, 180,
138 | * 270. Defaults to 0.
139 | */
140 | public static int getMetadataRotation(Context context, Uri uri) {
141 | int orientation = getMetadataOrientation(context, uri);
142 | switch (orientation) {
143 | case ORI_ROTATE_90:
144 | return 90;
145 | case ORI_ROTATE_180:
146 | return 180;
147 | case ORI_ROTATE_270:
148 | return 270;
149 | default:
150 | return 0;
151 | }
152 | }
153 |
154 | /**
155 | * Returns the image's orientation flag. Defaults to ORI_NORMAL if no valid
156 | * orientation was found.
157 | */
158 | public static int getMetadataOrientation(Context context, Uri uri) {
159 | if (uri == null || context == null) {
160 | throw new IllegalArgumentException("bad argument to getOrientation");
161 | }
162 |
163 | // First try to find orientation data in Gallery's ContentProvider.
164 | Cursor cursor = null;
165 | try {
166 | cursor = context.getContentResolver().query(uri,
167 | new String[]{MediaStore.Images.ImageColumns.ORIENTATION},
168 | null, null, null);
169 | if (cursor != null && cursor.moveToNext()) {
170 | int ori = cursor.getInt(0);
171 | switch (ori) {
172 | case 90:
173 | return ORI_ROTATE_90;
174 | case 270:
175 | return ORI_ROTATE_270;
176 | case 180:
177 | return ORI_ROTATE_180;
178 | default:
179 | return ORI_NORMAL;
180 | }
181 | }
182 | } catch (SQLiteException e) {
183 | // Do nothing
184 | } catch (IllegalArgumentException e) {
185 | // Do nothing
186 | } catch (IllegalStateException e) {
187 | // Do nothing
188 | } catch (Exception e) {
189 |
190 | } finally {
191 | if (cursor != null) cursor.close();
192 | }
193 | ExifInterface exif = null;
194 | InputStream is = null;
195 | // Fall back to checking EXIF tags in file or input stream.
196 | try {
197 | if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
198 | String mimeType = getMimeType(uri);
199 | if (!JPEG_MIME_TYPE.equals(mimeType)) {
200 | return ORI_NORMAL;
201 | }
202 | String path = uri.getPath();
203 | exif = new ExifInterface(path);
204 | return parseExif(exif);
205 | //exif.readExif(path);
206 | }
207 | } catch (IOException e) {
208 | Log.w(LOGTAG, "Failed to read EXIF orientation", e);
209 | } finally {
210 | try {
211 | if (is != null) {
212 | is.close();
213 | }
214 | } catch (IOException e) {
215 | Log.w(LOGTAG, "Failed to close InputStream", e);
216 | }
217 | }
218 | return ORI_NORMAL;
219 | }
220 |
221 | private static int parseExif(ExifInterface exif) {
222 | Integer tagval = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ORI_NORMAL);
223 | if (tagval != null) {
224 | int orientation = tagval;
225 | switch (orientation) {
226 | case ORI_NORMAL:
227 | case ORI_ROTATE_90:
228 | case ORI_ROTATE_180:
229 | case ORI_ROTATE_270:
230 | case ORI_FLIP_HOR:
231 | case ORI_FLIP_VERT:
232 | case ORI_TRANSPOSE:
233 | case ORI_TRANSVERSE:
234 | return orientation;
235 | default:
236 | return ORI_NORMAL;
237 | }
238 | }
239 | return ORI_NORMAL;
240 | }
241 |
242 | /**
243 | * Returns the Mime type for a Url. Safe to use with Urls that do not
244 | * come from Gallery's content provider.
245 | */
246 | public static String getMimeType(Uri src) {
247 | String postfix = MimeTypeMap.getFileExtensionFromUrl(src.toString());
248 | String ret = null;
249 | if (postfix != null) {
250 | ret = MimeTypeMap.getSingleton().getMimeTypeFromExtension(postfix);
251 | }
252 | return ret;
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/SaveImage.java:
--------------------------------------------------------------------------------
1 | package com.android.gallery3d.crop;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.ContentValues;
5 | import android.content.Context;
6 | import android.database.Cursor;
7 | import android.net.Uri;
8 | import android.os.Environment;
9 | import android.provider.MediaStore;
10 | import android.util.Log;
11 |
12 | import java.io.File;
13 | import java.text.SimpleDateFormat;
14 | import java.util.Date;
15 |
16 | /**
17 | * Created by qiaomu on 2017/10/24.
18 | */
19 |
20 | public class SaveImage {
21 | private static final String TIME_STAMP_NAME = "_yyyyMMdd_HHmmss";
22 | private static final String PREFIX_PANO = "PANO";
23 | private static final String PREFIX_IMG = "IMG";
24 | private static final String POSTFIX_JPG = ".jpg";
25 | private static final String AUX_DIR_NAME = ".aux";
26 | private static final String LOGTAG = "SaveImage";
27 |
28 | private int mCurrentProcessingStep = 1;
29 |
30 | public static final int MAX_PROCESSING_STEPS = 6;
31 | public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos";
32 |
33 | public static Uri makeAndInsertUri(Context context, Uri sourceUri) {
34 | long time = System.currentTimeMillis();
35 | String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time));
36 | File saveDirectory = getFinalSaveDirectory(context, sourceUri);
37 | File file = new File(saveDirectory, filename + ".JPG");
38 | return linkNewFileToUri(context, sourceUri, file, time, false);
39 | }
40 |
41 |
42 | public static File getFinalSaveDirectory(Context context, Uri sourceUri) {
43 | File saveDirectory = SaveImage.getSaveDirectory(context, sourceUri);
44 | if ((saveDirectory == null) || !saveDirectory.canWrite()) {
45 | saveDirectory = new File(Environment.getExternalStorageDirectory(),
46 | SaveImage.DEFAULT_SAVE_DIRECTORY);
47 | }
48 | // Create the directory if it doesn't exist
49 | if (!saveDirectory.exists())
50 | saveDirectory.mkdirs();
51 | return saveDirectory;
52 | }
53 |
54 | /**
55 | * If the sourceUri
is a local content Uri, update the
56 | * sourceUri
to point to the file
.
57 | * At the same time, the old file sourceUri
used to point to
58 | * will be removed if it is local.
59 | * If the sourceUri
is not a local content Uri, then the
60 | * file
will be inserted as a new content Uri.
61 | *
62 | * @return the final Uri referring to the file
.
63 | */
64 | public static Uri linkNewFileToUri(Context context, Uri sourceUri,
65 | File file, long time, boolean deleteOriginal) {
66 | File oldSelectedFile = getLocalFileFromUri(context, sourceUri);
67 | final ContentValues values = getContentValues(context, sourceUri, file, time);
68 |
69 | Uri result = sourceUri;
70 |
71 | // In the case of incoming Uri is just a local file Uri (like a cached
72 | // file), we can't just update the Uri. We have to create a new Uri.
73 | boolean fileUri = isFileUri(sourceUri);
74 |
75 | if (fileUri || oldSelectedFile == null || !deleteOriginal) {
76 | result = context.getContentResolver().insert(
77 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
78 | } else {
79 | context.getContentResolver().update(sourceUri, values, null, null);
80 | if (oldSelectedFile.exists()) {
81 | oldSelectedFile.delete();
82 | }
83 | }
84 | return result;
85 | }
86 |
87 | private static ContentValues getContentValues(Context context, Uri sourceUri,
88 | File file, long time) {
89 | final ContentValues values = new ContentValues();
90 |
91 | time /= 1000;
92 | values.put(MediaStore.Images.Media.TITLE, file.getName());
93 | values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName());
94 | values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
95 | values.put(MediaStore.Images.Media.DATE_TAKEN, time);
96 | values.put(MediaStore.Images.Media.DATE_MODIFIED, time);
97 | values.put(MediaStore.Images.Media.DATE_ADDED, time);
98 | values.put(MediaStore.Images.Media.ORIENTATION, 0);
99 | values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
100 | values.put(MediaStore.Images.Media.SIZE, file.length());
101 | // This is a workaround to trigger the MediaProvider to re-generate the
102 | // thumbnail.
103 | values.put(MediaStore.Images.Media.MINI_THUMB_MAGIC, 0);
104 |
105 | final String[] projection = new String[]{
106 | MediaStore.Images.ImageColumns.DATE_TAKEN,
107 | MediaStore.Images.ImageColumns.LATITUDE, MediaStore.Images.ImageColumns.LONGITUDE,
108 | };
109 |
110 | SaveImage.querySource(context, sourceUri, projection,
111 | new ContentResolverQueryCallback() {
112 |
113 | @Override
114 | public void onCursorResult(Cursor cursor) {
115 | values.put(MediaStore.Images.Media.DATE_TAKEN, cursor.getLong(0));
116 |
117 | double latitude = cursor.getDouble(1);
118 | double longitude = cursor.getDouble(2);
119 | // TODO: Change || to && after the default location
120 | // issue is fixed.
121 | if ((latitude != 0f) || (longitude != 0f)) {
122 | values.put(MediaStore.Images.Media.LATITUDE, latitude);
123 | values.put(MediaStore.Images.Media.LONGITUDE, longitude);
124 | }
125 | }
126 | });
127 | return values;
128 | }
129 |
130 |
131 | public static void querySource(Context context, Uri sourceUri, String[] projection,
132 | ContentResolverQueryCallback callback) {
133 | ContentResolver contentResolver = context.getContentResolver();
134 | querySourceFromContentResolver(contentResolver, sourceUri, projection, callback);
135 | }
136 |
137 | private static void querySourceFromContentResolver(
138 | ContentResolver contentResolver, Uri sourceUri, String[] projection,
139 | ContentResolverQueryCallback callback) {
140 | Cursor cursor = null;
141 | try {
142 | cursor = contentResolver.query(sourceUri, projection, null, null,
143 | null);
144 | if ((cursor != null) && cursor.moveToNext()) {
145 | callback.onCursorResult(cursor);
146 | }
147 | } catch (Exception e) {
148 | // Ignore error for lacking the data column from the source.
149 | } finally {
150 | if (cursor != null) {
151 | cursor.close();
152 | }
153 | }
154 | }
155 |
156 | /**
157 | * @param sourceUri
158 | * @return true if the sourceUri is a local file Uri.
159 | */
160 | private static boolean isFileUri(Uri sourceUri) {
161 | String scheme = sourceUri.getScheme();
162 | if (scheme != null && scheme.equals(ContentResolver.SCHEME_FILE)) {
163 | return true;
164 | }
165 | return false;
166 | }
167 |
168 | private static File getSaveDirectory(Context context, Uri sourceUri) {
169 | File file = getLocalFileFromUri(context, sourceUri);
170 | if (file != null) {
171 | return file.getParentFile();
172 | } else {
173 | return null;
174 | }
175 | }
176 |
177 |
178 | private static File getLocalFileFromUri(Context context, Uri srcUri) {
179 | if (srcUri == null) {
180 | Log.e(LOGTAG, "srcUri is null.");
181 | return null;
182 | }
183 |
184 | String scheme = srcUri.getScheme();
185 | if (scheme == null) {
186 | Log.e(LOGTAG, "scheme is null.");
187 | return null;
188 | }
189 |
190 | final File[] file = new File[1];
191 | // sourceUri can be a file path or a content Uri, it need to be handled
192 | // differently.
193 | if (scheme.equals(ContentResolver.SCHEME_CONTENT)) {
194 | if (srcUri.getAuthority().equals(MediaStore.AUTHORITY)) {
195 | querySource(context, srcUri, new String[]{
196 | MediaStore.Images.ImageColumns.DATA
197 | },
198 | new ContentResolverQueryCallback() {
199 |
200 | @Override
201 | public void onCursorResult(Cursor cursor) {
202 | file[0] = new File(cursor.getString(0));
203 | }
204 | });
205 | }
206 | } else if (scheme.equals(ContentResolver.SCHEME_FILE)) {
207 | file[0] = new File(srcUri.getPath());
208 | }
209 | return file[0];
210 | }
211 |
212 | public interface ContentResolverQueryCallback {
213 | void onCursorResult(Cursor cursor);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/libcrop/src/main/java/com/android/gallery3d/crop/Utils.java:
--------------------------------------------------------------------------------
1 | package com.android.gallery3d.crop;
2 |
3 | import android.os.ParcelFileDescriptor;
4 |
5 | import java.io.Closeable;
6 |
7 | /**
8 | * Created by qiaomu on 2017/10/24.
9 | */
10 |
11 | public class Utils {
12 | public static void closeSilently(Closeable c) {
13 | if (c == null) return;
14 | try {
15 | c.close();
16 | } catch (Throwable t) {
17 | // do nothing
18 | }
19 | }
20 |
21 | public static void closeSilently(ParcelFileDescriptor c) {
22 | if (c == null) return;
23 | try {
24 | c.close();
25 | } catch (Throwable t) {
26 | // do nothing
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable-xhdpi/camera_crop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/libcrop/src/main/res/drawable-xhdpi/camera_crop.png
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable-xhdpi/ic_crop_cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/libcrop/src/main/res/drawable-xhdpi/ic_crop_cancel.png
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable-xhdpi/ic_crop_ok.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/libcrop/src/main/res/drawable-xhdpi/ic_crop_ok.png
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable-xhdpi/ic_menu_savephoto.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/libcrop/src/main/res/drawable-xhdpi/ic_menu_savephoto.png
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable-xhdpi/ic_menu_savephoto_disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/libcrop/src/main/res/drawable-xhdpi/ic_menu_savephoto_disabled.png
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable/filtershow_button_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable/filtershow_button_selected_background.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/libcrop/src/main/res/drawable/filtershow_button_selected_background.9.png
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable/geometry_shadow.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrme2014/imageCrop/a8b3490dd9a0352c3f0eea9e0f0c0eacd5aa6c7d/libcrop/src/main/res/drawable/geometry_shadow.9.png
--------------------------------------------------------------------------------
/libcrop/src/main/res/drawable/menu_save_photo.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/libcrop/src/main/res/layout/crop_activity.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
23 |
27 |
28 |
35 |
36 |
40 |
41 |
42 |
48 |
49 |
50 |
54 |
55 |
63 |
64 |
65 |
72 |
73 |
77 |
78 |
82 |
83 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/libcrop/src/main/res/layout/filtershow_actionbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/libcrop/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4F000000
4 | #4F000000
5 | #7FFFFFFF
6 |
--------------------------------------------------------------------------------
/libcrop/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 2dp
5 | 5dp
6 | 45dp
7 | 5dp
8 | 4dp
9 | 4dp
10 | 20dp
11 |
12 |
--------------------------------------------------------------------------------
/libcrop/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | libcrop
3 | 保存
4 | 选择照片
5 | 不能否加载的图片
6 | 设置壁纸
7 | 图片裁剪
8 |
9 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':libcrop'
2 |
--------------------------------------------------------------------------------