├── .gitignore
├── README.md
├── build.gradle
├── settings.gradle
├── simple-crop-image-example
├── AndroidManifest.xml
├── ant.properties
├── build.xml
├── local.properties
├── proguard-project.txt
├── project.properties
├── res
│ ├── drawable-hdpi
│ │ └── ic_launcher.png
│ ├── drawable-ldpi
│ │ └── ic_launcher.png
│ ├── drawable-mdpi
│ │ └── ic_launcher.png
│ ├── drawable-xhdpi
│ │ └── ic_launcher.png
│ ├── layout
│ │ └── main.xml
│ └── values
│ │ └── strings.xml
└── src
│ └── eu
│ └── janmuller
│ └── android
│ └── simplecropimage
│ └── example
│ ├── InternalStorageContentProvider.java
│ └── MainActivity.java
└── simple-crop-image-lib
├── AndroidManifest.xml
├── ant.properties
├── build.gradle
├── build.xml
├── local.properties
├── proguard-project.txt
├── project.properties
├── res
├── drawable-xhdpi
│ ├── btn_crop_operator.9.png
│ ├── btn_crop_pressed.9.png
│ ├── camera_crop_height.png
│ ├── camera_crop_width.png
│ ├── ic_rotate_left.png
│ ├── ic_rotate_right.png
│ └── indicator_autocrop.png
├── drawable
│ └── selector_crop_button.xml
├── layout
│ ├── cropimage.xml
│ └── main.xml
├── values-cs
│ └── strings.xml
└── values
│ ├── strings.xml
│ └── styles.xml
└── src
└── eu
└── janmuller
└── android
└── simplecropimage
├── BitmapManager.java
├── CropImage.java
├── CropImageView.java
├── HighlightView.java
├── ImageViewTouchBase.java
├── MonitoredActivity.java
├── RotateBitmap.java
└── Util.java
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | gen/
3 | png/
4 | target/
5 | .settings/
6 | .classpath
7 | *.class
8 | *.log
9 | *.iml
10 | .DS_Store
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2012 Jan Muller
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
22 | Cropimage
23 | =========
24 |
25 | - Added support for building with Gradle
26 | - Replacement for deprecated official Android crop image function
27 | - > 2.2 API
28 | - Easy to integrate to your app.
29 | - Enjoy ;-)
30 |
31 |
32 | Call this method to run CropImage activity
33 | ```java
34 | private void runCropImage() {
35 |
36 | // create explicit intent
37 | Intent intent = new Intent(this, CropImage.class);
38 |
39 | // tell CropImage activity to look for image to crop
40 | String filePath = ...;
41 | intent.putExtra(CropImage.IMAGE_PATH, filePath);
42 |
43 | // allow CropImage activity to rescale image
44 | intent.putExtra(CropImage.SCALE, true);
45 |
46 | // if the aspect ratio is fixed to ratio 3/2
47 | intent.putExtra(CropImage.ASPECT_X, 3);
48 | intent.putExtra(CropImage.ASPECT_Y, 2);
49 |
50 | // start activity CropImage with certain request code and listen
51 | // for result
52 | startActivityForResult(intent, REQUEST_CODE_CROP_IMAGE);
53 | }
54 | ```
55 |
56 | Waiting for result
57 | ```java
58 | @Override
59 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
60 |
61 | if (resultCode != RESULT_OK) {
62 |
63 | return;
64 | }
65 |
66 | switch (requestCode) {
67 |
68 | case REQUEST_CODE_CROP_IMAGE:
69 |
70 | String path = data.getStringExtra(CropImage.IMAGE_PATH);
71 |
72 | // if nothing received
73 | if (path == null) {
74 |
75 | return;
76 | }
77 |
78 | // cropped bitmap
79 | Bitmap bitmap = BitmapFactory.decodeFile(mFileTemp.getPath());
80 |
81 | break;
82 | }
83 | super.onActivityResult(requestCode, resultCode, data);
84 | }
85 | ```
86 |
87 | Building with Gradle
88 | --------------------
89 |
90 | To build with gradle, make sure you have installed the gradle wrapper in the top level directory.
91 | On my computer this is typically done (from the root of this project) with a:
92 |
93 | cp -Rv /opt/android-studio/sdk/tools/templates/gradle/wrapper/* .
94 |
95 | Make sure to adjust the path to whereever you installed Android Studio.
96 |
97 | After doing this, to build issue the following command (again from the root of this project):
98 |
99 | ./gradlew assembleDebug
100 |
101 | To install the example a running emulator or device, do a:
102 |
103 | adb install -r ./simple-crop-image-example/build/apk/simple-crop-image-example-debug-unaligned.apk
104 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/build.gradle
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':simple-crop-image-example', ':simple-crop-image-lib'
2 |
3 |
--------------------------------------------------------------------------------
/simple-crop-image-example/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/simple-crop-image-example/ant.properties:
--------------------------------------------------------------------------------
1 | # This file is used to override default values used by the Ant build system.
2 | #
3 | # This file must be checked into Version Control Systems, as it is
4 | # integral to the build system of your project.
5 |
6 | # This file is only used by the Ant script.
7 |
8 | # You can use this to override default values such as
9 | # 'source.dir' for the location of your java source folder and
10 | # 'out.dir' for the location of your output folder.
11 |
12 | # You can also use it define how the release builds are signed by declaring
13 | # the following properties:
14 | # 'key.store' for the location of your keystore and
15 | # 'key.alias' for the name of the key to use.
16 | # The password will be asked during the build when you use the 'release' target.
17 |
18 |
--------------------------------------------------------------------------------
/simple-crop-image-example/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/simple-crop-image-example/local.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 |
7 | # location of the SDK. This is only used by Ant
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=/Users/muller10/Development/android-sdk-macosx
11 |
--------------------------------------------------------------------------------
/simple-crop-image-example/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/simple-crop-image-example/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=Google Inc.:Google APIs:16
15 | android.library.reference.1=../simple-crop-image-lib
16 |
--------------------------------------------------------------------------------
/simple-crop-image-example/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-example/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple-crop-image-example/res/drawable-ldpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-example/res/drawable-ldpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple-crop-image-example/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-example/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple-crop-image-example/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-example/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/simple-crop-image-example/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
20 |
21 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/simple-crop-image-example/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | simple-crop-image-example
4 |
5 |
--------------------------------------------------------------------------------
/simple-crop-image-example/src/eu/janmuller/android/simplecropimage/example/InternalStorageContentProvider.java:
--------------------------------------------------------------------------------
1 | package eu.janmuller.android.simplecropimage.example;
2 |
3 | import java.io.File;
4 | import java.io.FileNotFoundException;
5 | import java.util.HashMap;
6 |
7 | import android.content.ContentProvider;
8 | import android.content.ContentValues;
9 | import android.database.Cursor;
10 | import android.net.Uri;
11 | import android.os.ParcelFileDescriptor;
12 |
13 | /*
14 | * The solution is taken from here: http://stackoverflow.com/questions/10042695/how-to-get-camera-result-as-a-uri-in-data-folder
15 | */
16 |
17 | public class InternalStorageContentProvider extends ContentProvider {
18 | public static final Uri CONTENT_URI = Uri.parse("content://eu.janmuller.android.simplecropimage.example/");
19 | private static final HashMap MIME_TYPES = new HashMap();
20 |
21 | static {
22 | MIME_TYPES.put(".jpg", "image/jpeg");
23 | MIME_TYPES.put(".jpeg", "image/jpeg");
24 | }
25 |
26 | @Override
27 | public boolean onCreate() {
28 | try {
29 | File mFile = new File(getContext().getFilesDir(), MainActivity.TEMP_PHOTO_FILE_NAME);
30 | if(!mFile.exists()) {
31 | mFile.createNewFile();
32 | getContext().getContentResolver().notifyChange(CONTENT_URI, null);
33 | }
34 | return (true);
35 | } catch (Exception e) {
36 | e.printStackTrace();
37 | return false;
38 | }
39 | }
40 |
41 | @Override
42 | public String getType(Uri uri) {
43 | String path = uri.toString();
44 | for (String extension : MIME_TYPES.keySet()) {
45 | if (path.endsWith(extension)) {
46 | return (MIME_TYPES.get(extension));
47 | }
48 | }
49 | return (null);
50 | }
51 |
52 | @Override
53 | public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
54 | File f = new File(getContext().getFilesDir(), MainActivity.TEMP_PHOTO_FILE_NAME);
55 | if (f.exists()) {
56 | return (ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_WRITE));
57 | }
58 | throw new FileNotFoundException(uri.getPath());
59 | }
60 |
61 | @Override
62 | public int delete(Uri uri, String selection, String[] selectionArgs) {
63 | return 0;
64 | }
65 |
66 | @Override
67 | public Uri insert(Uri uri, ContentValues values) {
68 | return null;
69 | }
70 |
71 | @Override
72 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
73 | return null;
74 | }
75 |
76 | @Override
77 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
78 | return 0;
79 | }
80 | }
--------------------------------------------------------------------------------
/simple-crop-image-example/src/eu/janmuller/android/simplecropimage/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package eu.janmuller.android.simplecropimage.example;
2 |
3 | import java.io.File;
4 | import java.io.FileOutputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.OutputStream;
8 |
9 | import android.app.Activity;
10 | import android.content.ActivityNotFoundException;
11 | import android.content.Intent;
12 | import android.graphics.Bitmap;
13 | import android.graphics.BitmapFactory;
14 | import android.net.Uri;
15 | import android.os.Bundle;
16 | import android.os.Environment;
17 | import android.provider.MediaStore;
18 | import android.util.Log;
19 | import android.view.View;
20 | import android.widget.ImageView;
21 | import eu.janmuller.android.simplecropimage.CropImage;
22 |
23 | public class MainActivity extends Activity {
24 |
25 | public static final String TAG = "MainActivity";
26 |
27 | public static final String TEMP_PHOTO_FILE_NAME = "temp_photo.jpg";
28 |
29 | public static final int REQUEST_CODE_GALLERY = 0x1;
30 | public static final int REQUEST_CODE_TAKE_PICTURE = 0x2;
31 | public static final int REQUEST_CODE_CROP_IMAGE = 0x3;
32 |
33 | private ImageView mImageView;
34 | private File mFileTemp;
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 |
39 | super.onCreate(savedInstanceState); //To change body of overridden methods use File | Settings | File Templates.
40 | setContentView(R.layout.main);
41 |
42 | findViewById(R.id.gallery).setOnClickListener(new View.OnClickListener() {
43 | @Override
44 | public void onClick(View view) {
45 |
46 | openGallery();
47 | }
48 | });
49 |
50 | findViewById(R.id.take_picture).setOnClickListener(new View.OnClickListener() {
51 | @Override
52 | public void onClick(View view) {
53 |
54 | takePicture();
55 | }
56 | });
57 |
58 | mImageView = (ImageView) findViewById(R.id.image);
59 |
60 | String state = Environment.getExternalStorageState();
61 | if (Environment.MEDIA_MOUNTED.equals(state)) {
62 | mFileTemp = new File(Environment.getExternalStorageDirectory(), TEMP_PHOTO_FILE_NAME);
63 | }
64 | else {
65 | mFileTemp = new File(getFilesDir(), TEMP_PHOTO_FILE_NAME);
66 | }
67 |
68 | }
69 |
70 | private void takePicture() {
71 |
72 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
73 |
74 | try {
75 | Uri mImageCaptureUri = null;
76 | String state = Environment.getExternalStorageState();
77 | if (Environment.MEDIA_MOUNTED.equals(state)) {
78 | mImageCaptureUri = Uri.fromFile(mFileTemp);
79 | }
80 | else {
81 | /*
82 | * The solution is taken from here: http://stackoverflow.com/questions/10042695/how-to-get-camera-result-as-a-uri-in-data-folder
83 | */
84 | mImageCaptureUri = InternalStorageContentProvider.CONTENT_URI;
85 | }
86 | intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, mImageCaptureUri);
87 | intent.putExtra("return-data", true);
88 | startActivityForResult(intent, REQUEST_CODE_TAKE_PICTURE);
89 | } catch (ActivityNotFoundException e) {
90 |
91 | Log.d(TAG, "cannot take picture", e);
92 | }
93 | }
94 |
95 | private void openGallery() {
96 |
97 | Intent photoPickerIntent = new Intent(Intent.ACTION_PICK);
98 | photoPickerIntent.setType("image/*");
99 | startActivityForResult(photoPickerIntent, REQUEST_CODE_GALLERY);
100 | }
101 |
102 | private void startCropImage() {
103 |
104 | Intent intent = new Intent(this, CropImage.class);
105 | intent.putExtra(CropImage.IMAGE_PATH, mFileTemp.getPath());
106 | intent.putExtra(CropImage.SCALE, true);
107 |
108 | intent.putExtra(CropImage.ASPECT_X, 3);
109 | intent.putExtra(CropImage.ASPECT_Y, 2);
110 |
111 | startActivityForResult(intent, REQUEST_CODE_CROP_IMAGE);
112 | }
113 |
114 | @Override
115 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
116 |
117 | if (resultCode != RESULT_OK) {
118 |
119 | return;
120 | }
121 |
122 | Bitmap bitmap;
123 |
124 | switch (requestCode) {
125 |
126 | case REQUEST_CODE_GALLERY:
127 |
128 | try {
129 |
130 | InputStream inputStream = getContentResolver().openInputStream(data.getData());
131 | FileOutputStream fileOutputStream = new FileOutputStream(mFileTemp);
132 | copyStream(inputStream, fileOutputStream);
133 | fileOutputStream.close();
134 | inputStream.close();
135 |
136 | startCropImage();
137 |
138 | } catch (Exception e) {
139 |
140 | Log.e(TAG, "Error while creating temp file", e);
141 | }
142 |
143 | break;
144 | case REQUEST_CODE_TAKE_PICTURE:
145 |
146 | startCropImage();
147 | break;
148 | case REQUEST_CODE_CROP_IMAGE:
149 |
150 | String path = data.getStringExtra(CropImage.IMAGE_PATH);
151 | if (path == null) {
152 |
153 | return;
154 | }
155 |
156 | bitmap = BitmapFactory.decodeFile(mFileTemp.getPath());
157 | mImageView.setImageBitmap(bitmap);
158 | break;
159 | }
160 | super.onActivityResult(requestCode, resultCode, data);
161 | }
162 |
163 |
164 | public static void copyStream(InputStream input, OutputStream output)
165 | throws IOException {
166 |
167 | byte[] buffer = new byte[1024];
168 | int bytesRead;
169 | while ((bytesRead = input.read(buffer)) != -1) {
170 | output.write(buffer, 0, bytesRead);
171 | }
172 | }
173 |
174 | }
175 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/ant.properties:
--------------------------------------------------------------------------------
1 | # This file is used to override default values used by the Ant build system.
2 | #
3 | # This file must be checked into Version Control Systems, as it is
4 | # integral to the build system of your project.
5 |
6 | # This file is only used by the Ant script.
7 |
8 | # You can use this to override default values such as
9 | # 'source.dir' for the location of your java source folder and
10 | # 'out.dir' for the location of your output folder.
11 |
12 | # You can also use it define how the release builds are signed by declaring
13 | # the following properties:
14 | # 'key.store' for the location of your keystore and
15 | # 'key.alias' for the name of the key to use.
16 | # The password will be asked during the build when you use the 'release' target.
17 |
18 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | mavenCentral()
4 |
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:0.4.+'
9 | }
10 | }
11 |
12 | apply plugin: 'android-library'
13 |
14 | dependencies {
15 | }
16 |
17 | android {
18 | compileSdkVersion 17
19 | buildToolsVersion "17.0"
20 | defaultConfig {
21 | minSdkVersion 15
22 | targetSdkVersion 15
23 | }
24 | sourceSets {
25 | main {
26 | manifest {
27 | srcFile 'AndroidManifest.xml'
28 | }
29 | java {
30 | srcDir 'src'
31 | }
32 | res {
33 | srcDir 'res'
34 | }
35 | assets {
36 | srcDir 'assets'
37 | }
38 | resources {
39 | srcDir 'src'
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/local.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 |
7 | # location of the SDK. This is only used by Ant
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=/Users/muller10/Development/android-sdk-macosx
11 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | android.library=true
14 | # Project target.
15 | target=Google Inc.:Google APIs:16
16 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable-xhdpi/btn_crop_operator.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-lib/res/drawable-xhdpi/btn_crop_operator.9.png
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable-xhdpi/btn_crop_pressed.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-lib/res/drawable-xhdpi/btn_crop_pressed.9.png
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable-xhdpi/camera_crop_height.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-lib/res/drawable-xhdpi/camera_crop_height.png
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable-xhdpi/camera_crop_width.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-lib/res/drawable-xhdpi/camera_crop_width.png
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable-xhdpi/ic_rotate_left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-lib/res/drawable-xhdpi/ic_rotate_left.png
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable-xhdpi/ic_rotate_right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-lib/res/drawable-xhdpi/ic_rotate_right.png
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable-xhdpi/indicator_autocrop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/biokys/cropimage/64282e2634c03cfa377eb66b647722768d23372c/simple-crop-image-lib/res/drawable-xhdpi/indicator_autocrop.png
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/drawable/selector_crop_button.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/layout/cropimage.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
16 |
17 |
25 |
26 |
27 |
35 |
36 |
44 |
45 |
53 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/values-cs/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Zrušit
4 | Uložit
5 | Ukládám obrázek
6 | Připravuji kartu
7 | Žádná SD karta
8 | Na SD kartě není dostatek místa
9 |
10 |
11 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Cancel
4 | Save
5 | Saving image
6 | Preparing card
7 | No storage card
8 | Not enough space
9 |
10 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
15 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/BitmapManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 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 eu.janmuller.android.simplecropimage;
18 |
19 | import android.graphics.Bitmap;
20 | import android.graphics.BitmapFactory;
21 | import android.util.Log;
22 |
23 | import java.io.FileDescriptor;
24 | import java.util.Iterator;
25 | import java.util.Map;
26 | import java.util.WeakHashMap;
27 |
28 | /**
29 | * This class provides several utilities to cancel bitmap decoding.
30 | *
31 | * The function decodeFileDescriptor() is used to decode a bitmap. During
32 | * decoding if another thread wants to cancel it, it calls the function
33 | * cancelThreadDecoding() specifying the Thread which is in decoding.
34 | *
35 | * cancelThreadDecoding() is sticky until allowThreadDecoding() is called.
36 | *
37 | * You can also cancel decoding for a set of threads using ThreadSet as
38 | * the parameter for cancelThreadDecoding. To put a thread into a ThreadSet,
39 | * use the add() method. A ThreadSet holds (weak) references to the threads,
40 | * so you don't need to remove Thread from it if some thread dies.
41 | */
42 | public class BitmapManager {
43 |
44 | private static final String TAG = "BitmapManager";
45 |
46 | private static enum State {CANCEL, ALLOW}
47 |
48 | private static class ThreadStatus {
49 |
50 | public State mState = State.ALLOW;
51 | public BitmapFactory.Options mOptions;
52 |
53 | @Override
54 | public String toString() {
55 |
56 | String s;
57 | if (mState == State.CANCEL) {
58 | s = "Cancel";
59 | } else if (mState == State.ALLOW) {
60 | s = "Allow";
61 | } else {
62 | s = "?";
63 | }
64 | s = "thread state = " + s + ", options = " + mOptions;
65 | return s;
66 | }
67 | }
68 |
69 | public static class ThreadSet implements Iterable {
70 |
71 | private final WeakHashMap mWeakCollection =
72 | new WeakHashMap();
73 |
74 | public void add(Thread t) {
75 |
76 | mWeakCollection.put(t, null);
77 | }
78 |
79 | public void remove(Thread t) {
80 |
81 | mWeakCollection.remove(t);
82 | }
83 |
84 | public Iterator iterator() {
85 |
86 | return mWeakCollection.keySet().iterator();
87 | }
88 | }
89 |
90 | private final WeakHashMap mThreadStatus =
91 | new WeakHashMap();
92 |
93 | private static BitmapManager sManager = null;
94 |
95 | private BitmapManager() {
96 |
97 | }
98 |
99 | /**
100 | * Get thread status and create one if specified.
101 | */
102 | private synchronized ThreadStatus getOrCreateThreadStatus(Thread t) {
103 |
104 | ThreadStatus status = mThreadStatus.get(t);
105 | if (status == null) {
106 | status = new ThreadStatus();
107 | mThreadStatus.put(t, status);
108 | }
109 | return status;
110 | }
111 |
112 | /**
113 | * The following three methods are used to keep track of
114 | * BitmapFaction.Options used for decoding and cancelling.
115 | */
116 | private synchronized void setDecodingOptions(Thread t,
117 | BitmapFactory.Options options) {
118 |
119 | getOrCreateThreadStatus(t).mOptions = options;
120 | }
121 |
122 | synchronized BitmapFactory.Options getDecodingOptions(Thread t) {
123 |
124 | ThreadStatus status = mThreadStatus.get(t);
125 | return status != null ? status.mOptions : null;
126 | }
127 |
128 | synchronized void removeDecodingOptions(Thread t) {
129 |
130 | ThreadStatus status = mThreadStatus.get(t);
131 | status.mOptions = null;
132 | }
133 |
134 | /**
135 | * The following two methods are used to allow/cancel a set of threads
136 | * for bitmap decoding.
137 | */
138 | public synchronized void allowThreadDecoding(ThreadSet threads) {
139 |
140 | for (Thread t : threads) {
141 | allowThreadDecoding(t);
142 | }
143 | }
144 |
145 | public synchronized void cancelThreadDecoding(ThreadSet threads) {
146 |
147 | for (Thread t : threads) {
148 | cancelThreadDecoding(t);
149 | }
150 | }
151 |
152 | /**
153 | * The following three methods are used to keep track of which thread
154 | * is being disabled for bitmap decoding.
155 | */
156 | public synchronized boolean canThreadDecoding(Thread t) {
157 |
158 | ThreadStatus status = mThreadStatus.get(t);
159 | if (status == null) {
160 | // allow decoding by default
161 | return true;
162 | }
163 |
164 | return (status.mState != State.CANCEL);
165 | }
166 |
167 | public synchronized void allowThreadDecoding(Thread t) {
168 |
169 | getOrCreateThreadStatus(t).mState = State.ALLOW;
170 | }
171 |
172 | public synchronized void cancelThreadDecoding(Thread t) {
173 |
174 | ThreadStatus status = getOrCreateThreadStatus(t);
175 | status.mState = State.CANCEL;
176 | if (status.mOptions != null) {
177 | status.mOptions.requestCancelDecode();
178 | }
179 |
180 | // Wake up threads in waiting list
181 | notifyAll();
182 | }
183 |
184 | /**
185 | * A debugging routine.
186 | */
187 | public synchronized void dump() {
188 |
189 | Iterator> i =
190 | mThreadStatus.entrySet().iterator();
191 |
192 | while (i.hasNext()) {
193 | Map.Entry entry = i.next();
194 | Log.v(TAG, "[Dump] Thread " + entry.getKey() + " ("
195 | + entry.getKey().getId()
196 | + ")'s status is " + entry.getValue());
197 | }
198 | }
199 |
200 | public static synchronized BitmapManager instance() {
201 |
202 | if (sManager == null) {
203 | sManager = new BitmapManager();
204 | }
205 | return sManager;
206 | }
207 |
208 | /**
209 | * The real place to delegate bitmap decoding to BitmapFactory.
210 | */
211 | public Bitmap decodeFileDescriptor(FileDescriptor fd,
212 | BitmapFactory.Options options) {
213 |
214 | if (options.mCancel) {
215 | return null;
216 | }
217 |
218 | Thread thread = Thread.currentThread();
219 | if (!canThreadDecoding(thread)) {
220 | // Log.d(TAG, "Thread " + thread + " is not allowed to decode.");
221 | return null;
222 | }
223 |
224 | setDecodingOptions(thread, options);
225 | Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, options);
226 |
227 | removeDecodingOptions(thread);
228 | return b;
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/CropImage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 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 eu.janmuller.android.simplecropimage;
18 |
19 |
20 | import java.io.File;
21 | import java.io.FileNotFoundException;
22 | import java.io.IOException;
23 | import java.io.InputStream;
24 | import java.io.OutputStream;
25 | import java.util.concurrent.CountDownLatch;
26 |
27 | import android.app.Activity;
28 | import android.content.ContentResolver;
29 | import android.content.Intent;
30 | import android.graphics.Bitmap;
31 | import android.graphics.BitmapFactory;
32 | import android.graphics.Canvas;
33 | import android.graphics.Matrix;
34 | import android.graphics.Path;
35 | import android.graphics.PointF;
36 | import android.graphics.PorterDuff;
37 | import android.graphics.Rect;
38 | import android.graphics.RectF;
39 | import android.graphics.Region;
40 | import android.media.FaceDetector;
41 | import android.net.Uri;
42 | import android.os.Build;
43 | import android.os.Bundle;
44 | import android.os.Environment;
45 | import android.os.Handler;
46 | import android.os.StatFs;
47 | import android.util.Log;
48 | import android.view.View;
49 | import android.view.Window;
50 | import android.view.WindowManager;
51 | import android.widget.Toast;
52 |
53 |
54 | /**
55 | * The activity can crop specific region of interest from an image.
56 | */
57 | public class CropImage extends MonitoredActivity {
58 |
59 | final int IMAGE_MAX_SIZE = 1024;
60 |
61 | private static final String TAG = "CropImage";
62 | public static final String IMAGE_PATH = "image-path";
63 | public static final String SCALE = "scale";
64 | public static final String ORIENTATION_IN_DEGREES = "orientation_in_degrees";
65 | public static final String ASPECT_X = "aspectX";
66 | public static final String ASPECT_Y = "aspectY";
67 | public static final String OUTPUT_X = "outputX";
68 | public static final String OUTPUT_Y = "outputY";
69 | public static final String SCALE_UP_IF_NEEDED = "scaleUpIfNeeded";
70 | public static final String CIRCLE_CROP = "circleCrop";
71 | public static final String RETURN_DATA = "return-data";
72 | public static final String RETURN_DATA_AS_BITMAP = "data";
73 | public static final String ACTION_INLINE_DATA = "inline-data";
74 |
75 | // These are various options can be specified in the intent.
76 | private Bitmap.CompressFormat mOutputFormat = Bitmap.CompressFormat.JPEG;
77 | private Uri mSaveUri = null;
78 | private boolean mDoFaceDetection = true;
79 | private boolean mCircleCrop = false;
80 | private final Handler mHandler = new Handler();
81 |
82 | private int mAspectX;
83 | private int mAspectY;
84 | private int mOutputX;
85 | private int mOutputY;
86 | private boolean mScale;
87 | private CropImageView mImageView;
88 | private ContentResolver mContentResolver;
89 | private Bitmap mBitmap;
90 | private String mImagePath;
91 |
92 | boolean mWaitingToPick; // Whether we are wait the user to pick a face.
93 | boolean mSaving; // Whether the "save" button is already clicked.
94 | HighlightView mCrop;
95 |
96 | // These options specifiy the output image size and whether we should
97 | // scale the output to fit it (or just crop it).
98 | private boolean mScaleUp = true;
99 |
100 | private final BitmapManager.ThreadSet mDecodingThreads =
101 | new BitmapManager.ThreadSet();
102 |
103 | @Override
104 | public void onCreate(Bundle icicle) {
105 |
106 | super.onCreate(icicle);
107 | mContentResolver = getContentResolver();
108 |
109 | requestWindowFeature(Window.FEATURE_NO_TITLE);
110 | setContentView(R.layout.cropimage);
111 |
112 | mImageView = (CropImageView) findViewById(R.id.image);
113 |
114 | showStorageToast(this);
115 |
116 | Intent intent = getIntent();
117 | Bundle extras = intent.getExtras();
118 | if (extras != null) {
119 |
120 | if (extras.getString(CIRCLE_CROP) != null) {
121 |
122 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
123 | mImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
124 | }
125 |
126 | mCircleCrop = true;
127 | mAspectX = 1;
128 | mAspectY = 1;
129 | }
130 |
131 | mImagePath = extras.getString(IMAGE_PATH);
132 |
133 | mSaveUri = getImageUri(mImagePath);
134 | mBitmap = getBitmap(mImagePath);
135 |
136 | if (extras.containsKey(ASPECT_X) && extras.get(ASPECT_X) instanceof Integer) {
137 |
138 | mAspectX = extras.getInt(ASPECT_X);
139 | } else {
140 |
141 | throw new IllegalArgumentException("aspect_x must be integer");
142 | }
143 | if (extras.containsKey(ASPECT_Y) && extras.get(ASPECT_Y) instanceof Integer) {
144 |
145 | mAspectY = extras.getInt(ASPECT_Y);
146 | } else {
147 |
148 | throw new IllegalArgumentException("aspect_y must be integer");
149 | }
150 | mOutputX = extras.getInt(OUTPUT_X);
151 | mOutputY = extras.getInt(OUTPUT_Y);
152 | mScale = extras.getBoolean(SCALE, true);
153 | mScaleUp = extras.getBoolean(SCALE_UP_IF_NEEDED, true);
154 | }
155 |
156 |
157 | if (mBitmap == null) {
158 |
159 | Log.d(TAG, "finish!!!");
160 | finish();
161 | return;
162 | }
163 |
164 | // Make UI fullscreen.
165 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
166 |
167 | findViewById(R.id.discard).setOnClickListener(
168 | new View.OnClickListener() {
169 | public void onClick(View v) {
170 |
171 | setResult(RESULT_CANCELED);
172 | finish();
173 | }
174 | });
175 |
176 | findViewById(R.id.save).setOnClickListener(
177 | new View.OnClickListener() {
178 | public void onClick(View v) {
179 |
180 | try {
181 | onSaveClicked();
182 | } catch (Exception e) {
183 | finish();
184 | }
185 | }
186 | });
187 | findViewById(R.id.rotateLeft).setOnClickListener(
188 | new View.OnClickListener() {
189 | public void onClick(View v) {
190 |
191 | mBitmap = Util.rotateImage(mBitmap, -90);
192 | RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
193 | mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
194 | mRunFaceDetection.run();
195 | }
196 | });
197 |
198 | findViewById(R.id.rotateRight).setOnClickListener(
199 | new View.OnClickListener() {
200 | public void onClick(View v) {
201 |
202 | mBitmap = Util.rotateImage(mBitmap, 90);
203 | RotateBitmap rotateBitmap = new RotateBitmap(mBitmap);
204 | mImageView.setImageRotateBitmapResetBase(rotateBitmap, true);
205 | mRunFaceDetection.run();
206 | }
207 | });
208 | startFaceDetection();
209 | }
210 |
211 | private Uri getImageUri(String path) {
212 |
213 | return Uri.fromFile(new File(path));
214 | }
215 |
216 | private Bitmap getBitmap(String path) {
217 |
218 | Uri uri = getImageUri(path);
219 | InputStream in = null;
220 | try {
221 | in = mContentResolver.openInputStream(uri);
222 |
223 | //Decode image size
224 | BitmapFactory.Options o = new BitmapFactory.Options();
225 | o.inJustDecodeBounds = true;
226 |
227 | BitmapFactory.decodeStream(in, null, o);
228 | in.close();
229 |
230 | int scale = 1;
231 | if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
232 | scale = (int) Math.pow(2, (int) Math.round(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
233 | }
234 |
235 | BitmapFactory.Options o2 = new BitmapFactory.Options();
236 | o2.inSampleSize = scale;
237 | in = mContentResolver.openInputStream(uri);
238 | Bitmap b = BitmapFactory.decodeStream(in, null, o2);
239 | in.close();
240 |
241 | return b;
242 | } catch (FileNotFoundException e) {
243 | Log.e(TAG, "file " + path + " not found");
244 | } catch (IOException e) {
245 | Log.e(TAG, "file " + path + " not found");
246 | }
247 | return null;
248 | }
249 |
250 |
251 | private void startFaceDetection() {
252 |
253 | if (isFinishing()) {
254 | return;
255 | }
256 |
257 | mImageView.setImageBitmapResetBase(mBitmap, true);
258 |
259 | Util.startBackgroundJob(this, null,
260 | "Please wait\u2026",
261 | new Runnable() {
262 | public void run() {
263 |
264 | final CountDownLatch latch = new CountDownLatch(1);
265 | final Bitmap b = mBitmap;
266 | mHandler.post(new Runnable() {
267 | public void run() {
268 |
269 | if (b != mBitmap && b != null) {
270 | mImageView.setImageBitmapResetBase(b, true);
271 | mBitmap.recycle();
272 | mBitmap = b;
273 | }
274 | if (mImageView.getScale() == 1F) {
275 | mImageView.center(true, true);
276 | }
277 | latch.countDown();
278 | }
279 | });
280 | try {
281 | latch.await();
282 | } catch (InterruptedException e) {
283 | throw new RuntimeException(e);
284 | }
285 | mRunFaceDetection.run();
286 | }
287 | }, mHandler);
288 | }
289 |
290 |
291 | private void onSaveClicked() throws Exception {
292 | // TODO this code needs to change to use the decode/crop/encode single
293 | // step api so that we don't require that the whole (possibly large)
294 | // bitmap doesn't have to be read into memory
295 | if (mSaving) return;
296 |
297 | if (mCrop == null) {
298 |
299 | return;
300 | }
301 |
302 | mSaving = true;
303 |
304 | Rect r = mCrop.getCropRect();
305 |
306 | int width = r.width();
307 | int height = r.height();
308 |
309 | // If we are circle cropping, we want alpha channel, which is the
310 | // third param here.
311 | Bitmap croppedImage;
312 | try {
313 |
314 | croppedImage = Bitmap.createBitmap(width, height,
315 | mCircleCrop ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565);
316 | } catch (Exception e) {
317 | throw e;
318 | }
319 | if (croppedImage == null) {
320 |
321 | return;
322 | }
323 |
324 | {
325 | Canvas canvas = new Canvas(croppedImage);
326 | Rect dstRect = new Rect(0, 0, width, height);
327 | canvas.drawBitmap(mBitmap, r, dstRect, null);
328 | }
329 |
330 | if (mCircleCrop) {
331 |
332 | // OK, so what's all this about?
333 | // Bitmaps are inherently rectangular but we want to return
334 | // something that's basically a circle. So we fill in the
335 | // area around the circle with alpha. Note the all important
336 | // PortDuff.Mode.CLEAR.
337 | Canvas c = new Canvas(croppedImage);
338 | Path p = new Path();
339 | p.addCircle(width / 2F, height / 2F, width / 2F,
340 | Path.Direction.CW);
341 | c.clipPath(p, Region.Op.DIFFERENCE);
342 | c.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
343 | }
344 |
345 | /* If the output is required to a specific size then scale or fill */
346 | if (mOutputX != 0 && mOutputY != 0) {
347 |
348 | if (mScale) {
349 |
350 | /* Scale the image to the required dimensions */
351 | Bitmap old = croppedImage;
352 | croppedImage = Util.transform(new Matrix(),
353 | croppedImage, mOutputX, mOutputY, mScaleUp);
354 | if (old != croppedImage) {
355 |
356 | old.recycle();
357 | }
358 | } else {
359 |
360 | /* Don't scale the image crop it to the size requested.
361 | * Create an new image with the cropped image in the center and
362 | * the extra space filled.
363 | */
364 |
365 | // Don't scale the image but instead fill it so it's the
366 | // required dimension
367 | Bitmap b = Bitmap.createBitmap(mOutputX, mOutputY,
368 | Bitmap.Config.RGB_565);
369 | Canvas canvas = new Canvas(b);
370 |
371 | Rect srcRect = mCrop.getCropRect();
372 | Rect dstRect = new Rect(0, 0, mOutputX, mOutputY);
373 |
374 | int dx = (srcRect.width() - dstRect.width()) / 2;
375 | int dy = (srcRect.height() - dstRect.height()) / 2;
376 |
377 | /* If the srcRect is too big, use the center part of it. */
378 | srcRect.inset(Math.max(0, dx), Math.max(0, dy));
379 |
380 | /* If the dstRect is too big, use the center part of it. */
381 | dstRect.inset(Math.max(0, -dx), Math.max(0, -dy));
382 |
383 | /* Draw the cropped bitmap in the center */
384 | canvas.drawBitmap(mBitmap, srcRect, dstRect, null);
385 |
386 | /* Set the cropped bitmap as the new bitmap */
387 | croppedImage.recycle();
388 | croppedImage = b;
389 | }
390 | }
391 |
392 | // Return the cropped image directly or save it to the specified URI.
393 | Bundle myExtras = getIntent().getExtras();
394 | if (myExtras != null && (myExtras.getParcelable("data") != null
395 | || myExtras.getBoolean(RETURN_DATA))) {
396 |
397 | Bundle extras = new Bundle();
398 | extras.putParcelable(RETURN_DATA_AS_BITMAP, croppedImage);
399 | setResult(RESULT_OK,
400 | (new Intent()).setAction(ACTION_INLINE_DATA).putExtras(extras));
401 | finish();
402 | } else {
403 | final Bitmap b = croppedImage;
404 | Util.startBackgroundJob(this, null, getString(R.string.saving_image),
405 | new Runnable() {
406 | public void run() {
407 |
408 | saveOutput(b);
409 | }
410 | }, mHandler);
411 | }
412 | }
413 |
414 | private void saveOutput(Bitmap croppedImage) {
415 |
416 | if (mSaveUri != null) {
417 | OutputStream outputStream = null;
418 | try {
419 | outputStream = mContentResolver.openOutputStream(mSaveUri);
420 | if (outputStream != null) {
421 | croppedImage.compress(mOutputFormat, 90, outputStream);
422 | }
423 | } catch (IOException ex) {
424 |
425 | Log.e(TAG, "Cannot open file: " + mSaveUri, ex);
426 | setResult(RESULT_CANCELED);
427 | finish();
428 | return;
429 | } finally {
430 |
431 | Util.closeSilently(outputStream);
432 | }
433 |
434 | Bundle extras = new Bundle();
435 | Intent intent = new Intent(mSaveUri.toString());
436 | intent.putExtras(extras);
437 | intent.putExtra(IMAGE_PATH, mImagePath);
438 | intent.putExtra(ORIENTATION_IN_DEGREES, Util.getOrientationInDegree(this));
439 | setResult(RESULT_OK, intent);
440 | } else {
441 |
442 | Log.e(TAG, "not defined image url");
443 | }
444 | croppedImage.recycle();
445 | finish();
446 | }
447 |
448 | @Override
449 | protected void onPause() {
450 |
451 | super.onPause();
452 | BitmapManager.instance().cancelThreadDecoding(mDecodingThreads);
453 | }
454 |
455 | @Override
456 | protected void onDestroy() {
457 |
458 | super.onDestroy();
459 |
460 | if (mBitmap != null) {
461 |
462 | mBitmap.recycle();
463 | }
464 | }
465 |
466 |
467 | Runnable mRunFaceDetection = new Runnable() {
468 | @SuppressWarnings("hiding")
469 | float mScale = 1F;
470 | Matrix mImageMatrix;
471 | FaceDetector.Face[] mFaces = new FaceDetector.Face[3];
472 | int mNumFaces;
473 |
474 | // For each face, we create a HightlightView for it.
475 | private void handleFace(FaceDetector.Face f) {
476 |
477 | PointF midPoint = new PointF();
478 |
479 | int r = ((int) (f.eyesDistance() * mScale)) * 2;
480 | f.getMidPoint(midPoint);
481 | midPoint.x *= mScale;
482 | midPoint.y *= mScale;
483 |
484 | int midX = (int) midPoint.x;
485 | int midY = (int) midPoint.y;
486 |
487 | HighlightView hv = new HighlightView(mImageView);
488 |
489 | int width = mBitmap.getWidth();
490 | int height = mBitmap.getHeight();
491 |
492 | Rect imageRect = new Rect(0, 0, width, height);
493 |
494 | RectF faceRect = new RectF(midX, midY, midX, midY);
495 | faceRect.inset(-r, -r);
496 | if (faceRect.left < 0) {
497 | faceRect.inset(-faceRect.left, -faceRect.left);
498 | }
499 |
500 | if (faceRect.top < 0) {
501 | faceRect.inset(-faceRect.top, -faceRect.top);
502 | }
503 |
504 | if (faceRect.right > imageRect.right) {
505 | faceRect.inset(faceRect.right - imageRect.right,
506 | faceRect.right - imageRect.right);
507 | }
508 |
509 | if (faceRect.bottom > imageRect.bottom) {
510 | faceRect.inset(faceRect.bottom - imageRect.bottom,
511 | faceRect.bottom - imageRect.bottom);
512 | }
513 |
514 | hv.setup(mImageMatrix, imageRect, faceRect, mCircleCrop,
515 | mAspectX != 0 && mAspectY != 0);
516 |
517 | mImageView.add(hv);
518 | }
519 |
520 | // Create a default HightlightView if we found no face in the picture.
521 | private void makeDefault() {
522 |
523 | HighlightView hv = new HighlightView(mImageView);
524 |
525 | int width = mBitmap.getWidth();
526 | int height = mBitmap.getHeight();
527 |
528 | Rect imageRect = new Rect(0, 0, width, height);
529 |
530 | // make the default size about 4/5 of the width or height
531 | int cropWidth = Math.min(width, height) * 4 / 5;
532 | int cropHeight = cropWidth;
533 |
534 | if (mAspectX != 0 && mAspectY != 0) {
535 |
536 | if (mAspectX > mAspectY) {
537 |
538 | cropHeight = cropWidth * mAspectY / mAspectX;
539 | } else {
540 |
541 | cropWidth = cropHeight * mAspectX / mAspectY;
542 | }
543 | }
544 |
545 | int x = (width - cropWidth) / 2;
546 | int y = (height - cropHeight) / 2;
547 |
548 | RectF cropRect = new RectF(x, y, x + cropWidth, y + cropHeight);
549 | hv.setup(mImageMatrix, imageRect, cropRect, mCircleCrop,
550 | mAspectX != 0 && mAspectY != 0);
551 |
552 | mImageView.mHighlightViews.clear(); // Thong added for rotate
553 |
554 | mImageView.add(hv);
555 | }
556 |
557 | // Scale the image down for faster face detection.
558 | private Bitmap prepareBitmap() {
559 |
560 | if (mBitmap == null) {
561 |
562 | return null;
563 | }
564 |
565 | // 256 pixels wide is enough.
566 | if (mBitmap.getWidth() > 256) {
567 |
568 | mScale = 256.0F / mBitmap.getWidth();
569 | }
570 | Matrix matrix = new Matrix();
571 | matrix.setScale(mScale, mScale);
572 | return Bitmap.createBitmap(mBitmap, 0, 0, mBitmap.getWidth(), mBitmap.getHeight(), matrix, true);
573 | }
574 |
575 | public void run() {
576 |
577 | mImageMatrix = mImageView.getImageMatrix();
578 | Bitmap faceBitmap = prepareBitmap();
579 |
580 | mScale = 1.0F / mScale;
581 | if (faceBitmap != null && mDoFaceDetection) {
582 | FaceDetector detector = new FaceDetector(faceBitmap.getWidth(),
583 | faceBitmap.getHeight(), mFaces.length);
584 | mNumFaces = detector.findFaces(faceBitmap, mFaces);
585 | }
586 |
587 | if (faceBitmap != null && faceBitmap != mBitmap) {
588 | faceBitmap.recycle();
589 | }
590 |
591 | mHandler.post(new Runnable() {
592 | public void run() {
593 |
594 | mWaitingToPick = mNumFaces > 1;
595 | if (mNumFaces > 0) {
596 | for (int i = 0; i < mNumFaces; i++) {
597 | handleFace(mFaces[i]);
598 | }
599 | } else {
600 | makeDefault();
601 | }
602 | mImageView.invalidate();
603 | if (mImageView.mHighlightViews.size() == 1) {
604 | mCrop = mImageView.mHighlightViews.get(0);
605 | mCrop.setFocus(true);
606 | }
607 |
608 | if (mNumFaces > 1) {
609 | Toast.makeText(CropImage.this,
610 | "Multi face crop help",
611 | Toast.LENGTH_SHORT).show();
612 | }
613 | }
614 | });
615 | }
616 | };
617 |
618 | public static final int NO_STORAGE_ERROR = -1;
619 | public static final int CANNOT_STAT_ERROR = -2;
620 |
621 | public static void showStorageToast(Activity activity) {
622 |
623 | showStorageToast(activity, calculatePicturesRemaining(activity));
624 | }
625 |
626 | public static void showStorageToast(Activity activity, int remaining) {
627 |
628 | String noStorageText = null;
629 |
630 | if (remaining == NO_STORAGE_ERROR) {
631 |
632 | String state = Environment.getExternalStorageState();
633 | if (state.equals(Environment.MEDIA_CHECKING)) {
634 |
635 | noStorageText = activity.getString(R.string.preparing_card);
636 | } else {
637 |
638 | noStorageText = activity.getString(R.string.no_storage_card);
639 | }
640 | } else if (remaining < 1) {
641 |
642 | noStorageText = activity.getString(R.string.not_enough_space);
643 | }
644 |
645 | if (noStorageText != null) {
646 |
647 | Toast.makeText(activity, noStorageText, Toast.LENGTH_LONG).show();
648 | }
649 | }
650 |
651 | public static int calculatePicturesRemaining(Activity activity) {
652 |
653 | try {
654 | /*if (!ImageManager.hasStorage()) {
655 | return NO_STORAGE_ERROR;
656 | } else {*/
657 | String storageDirectory = "";
658 | String state = Environment.getExternalStorageState();
659 | if (Environment.MEDIA_MOUNTED.equals(state)) {
660 | storageDirectory = Environment.getExternalStorageDirectory().toString();
661 | }
662 | else {
663 | storageDirectory = activity.getFilesDir().toString();
664 | }
665 | StatFs stat = new StatFs(storageDirectory);
666 | float remaining = ((float) stat.getAvailableBlocks()
667 | * (float) stat.getBlockSize()) / 400000F;
668 | return (int) remaining;
669 | //}
670 | } catch (Exception ex) {
671 | // if we can't stat the filesystem then we don't know how many
672 | // pictures are remaining. it might be zero but just leave it
673 | // blank since we really don't know.
674 | return CANNOT_STAT_ERROR;
675 | }
676 | }
677 |
678 |
679 | }
680 |
681 |
682 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/CropImageView.java:
--------------------------------------------------------------------------------
1 | package eu.janmuller.android.simplecropimage;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Rect;
6 | import android.util.AttributeSet;
7 | import android.view.MotionEvent;
8 |
9 | import java.util.ArrayList;
10 |
11 | class CropImageView extends ImageViewTouchBase {
12 |
13 | ArrayList mHighlightViews = new ArrayList();
14 | HighlightView mMotionHighlightView = null;
15 | float mLastX, mLastY;
16 | int mMotionEdge;
17 |
18 | private Context mContext;
19 |
20 | @Override
21 | protected void onLayout(boolean changed, int left, int top,
22 | int right, int bottom) {
23 |
24 | super.onLayout(changed, left, top, right, bottom);
25 | if (mBitmapDisplayed.getBitmap() != null) {
26 | for (HighlightView hv : mHighlightViews) {
27 | hv.mMatrix.set(getImageMatrix());
28 | hv.invalidate();
29 | if (hv.mIsFocused) {
30 | centerBasedOnHighlightView(hv);
31 | }
32 | }
33 | }
34 | }
35 |
36 | public CropImageView(Context context, AttributeSet attrs) {
37 |
38 | super(context, attrs);
39 | this.mContext = context;
40 | }
41 |
42 | @Override
43 | protected void zoomTo(float scale, float centerX, float centerY) {
44 |
45 | super.zoomTo(scale, centerX, centerY);
46 | for (HighlightView hv : mHighlightViews) {
47 | hv.mMatrix.set(getImageMatrix());
48 | hv.invalidate();
49 | }
50 | }
51 |
52 | @Override
53 | protected void zoomIn() {
54 |
55 | super.zoomIn();
56 | for (HighlightView hv : mHighlightViews) {
57 | hv.mMatrix.set(getImageMatrix());
58 | hv.invalidate();
59 | }
60 | }
61 |
62 | @Override
63 | protected void zoomOut() {
64 |
65 | super.zoomOut();
66 | for (HighlightView hv : mHighlightViews) {
67 | hv.mMatrix.set(getImageMatrix());
68 | hv.invalidate();
69 | }
70 | }
71 |
72 | @Override
73 | protected void postTranslate(float deltaX, float deltaY) {
74 |
75 | super.postTranslate(deltaX, deltaY);
76 | for (int i = 0; i < mHighlightViews.size(); i++) {
77 | HighlightView hv = mHighlightViews.get(i);
78 | hv.mMatrix.postTranslate(deltaX, deltaY);
79 | hv.invalidate();
80 | }
81 | }
82 |
83 | // According to the event's position, change the focus to the first
84 | // hitting cropping rectangle.
85 | private void recomputeFocus(MotionEvent event) {
86 |
87 | for (int i = 0; i < mHighlightViews.size(); i++) {
88 | HighlightView hv = mHighlightViews.get(i);
89 | hv.setFocus(false);
90 | hv.invalidate();
91 | }
92 |
93 | for (int i = 0; i < mHighlightViews.size(); i++) {
94 | HighlightView hv = mHighlightViews.get(i);
95 | int edge = hv.getHit(event.getX(), event.getY());
96 | if (edge != HighlightView.GROW_NONE) {
97 | if (!hv.hasFocus()) {
98 | hv.setFocus(true);
99 | hv.invalidate();
100 | }
101 | break;
102 | }
103 | }
104 | invalidate();
105 | }
106 |
107 | @Override
108 | public boolean onTouchEvent(MotionEvent event) {
109 |
110 | CropImage cropImage = (CropImage) mContext;
111 | if (cropImage.mSaving) {
112 | return false;
113 | }
114 |
115 | switch (event.getAction()) {
116 | case MotionEvent.ACTION_DOWN:
117 | if (cropImage.mWaitingToPick) {
118 | recomputeFocus(event);
119 | } else {
120 | for (int i = 0; i < mHighlightViews.size(); i++) {
121 | HighlightView hv = mHighlightViews.get(i);
122 | int edge = hv.getHit(event.getX(), event.getY());
123 | if (edge != HighlightView.GROW_NONE) {
124 | mMotionEdge = edge;
125 | mMotionHighlightView = hv;
126 | mLastX = event.getX();
127 | mLastY = event.getY();
128 | mMotionHighlightView.setMode(
129 | (edge == HighlightView.MOVE)
130 | ? HighlightView.ModifyMode.Move
131 | : HighlightView.ModifyMode.Grow);
132 | break;
133 | }
134 | }
135 | }
136 | break;
137 | case MotionEvent.ACTION_UP:
138 | if (cropImage.mWaitingToPick) {
139 | for (int i = 0; i < mHighlightViews.size(); i++) {
140 | HighlightView hv = mHighlightViews.get(i);
141 | if (hv.hasFocus()) {
142 | cropImage.mCrop = hv;
143 | for (int j = 0; j < mHighlightViews.size(); j++) {
144 | if (j == i) {
145 | continue;
146 | }
147 | mHighlightViews.get(j).setHidden(true);
148 | }
149 | centerBasedOnHighlightView(hv);
150 | ((CropImage) mContext).mWaitingToPick = false;
151 | return true;
152 | }
153 | }
154 | } else if (mMotionHighlightView != null) {
155 | centerBasedOnHighlightView(mMotionHighlightView);
156 | mMotionHighlightView.setMode(
157 | HighlightView.ModifyMode.None);
158 | }
159 | mMotionHighlightView = null;
160 | break;
161 | case MotionEvent.ACTION_MOVE:
162 | if (cropImage.mWaitingToPick) {
163 | recomputeFocus(event);
164 | } else if (mMotionHighlightView != null) {
165 | mMotionHighlightView.handleMotion(mMotionEdge,
166 | event.getX() - mLastX,
167 | event.getY() - mLastY);
168 | mLastX = event.getX();
169 | mLastY = event.getY();
170 |
171 | if (true) {
172 | // This section of code is optional. It has some user
173 | // benefit in that moving the crop rectangle against
174 | // the edge of the screen causes scrolling but it means
175 | // that the crop rectangle is no longer fixed under
176 | // the user's finger.
177 | ensureVisible(mMotionHighlightView);
178 | }
179 | }
180 | break;
181 | }
182 |
183 | switch (event.getAction()) {
184 | case MotionEvent.ACTION_UP:
185 | center(true, true);
186 | break;
187 | case MotionEvent.ACTION_MOVE:
188 | // if we're not zoomed then there's no point in even allowing
189 | // the user to move the image around. This call to center puts
190 | // it back to the normalized location (with false meaning don't
191 | // animate).
192 | if (getScale() == 1F) {
193 | center(true, true);
194 | }
195 | break;
196 | }
197 |
198 | return true;
199 | }
200 |
201 | // Pan the displayed image to make sure the cropping rectangle is visible.
202 | private void ensureVisible(HighlightView hv) {
203 |
204 | Rect r = hv.mDrawRect;
205 |
206 | int panDeltaX1 = Math.max(0, mLeft - r.left);
207 | int panDeltaX2 = Math.min(0, mRight - r.right);
208 |
209 | int panDeltaY1 = Math.max(0, mTop - r.top);
210 | int panDeltaY2 = Math.min(0, mBottom - r.bottom);
211 |
212 | int panDeltaX = panDeltaX1 != 0 ? panDeltaX1 : panDeltaX2;
213 | int panDeltaY = panDeltaY1 != 0 ? panDeltaY1 : panDeltaY2;
214 |
215 | if (panDeltaX != 0 || panDeltaY != 0) {
216 | panBy(panDeltaX, panDeltaY);
217 | }
218 | }
219 |
220 | // If the cropping rectangle's size changed significantly, change the
221 | // view's center and scale according to the cropping rectangle.
222 | private void centerBasedOnHighlightView(HighlightView hv) {
223 |
224 | Rect drawRect = hv.mDrawRect;
225 |
226 | float width = drawRect.width();
227 | float height = drawRect.height();
228 |
229 | float thisWidth = getWidth();
230 | float thisHeight = getHeight();
231 |
232 | float z1 = thisWidth / width * .6F;
233 | float z2 = thisHeight / height * .6F;
234 |
235 | float zoom = Math.min(z1, z2);
236 | zoom = zoom * this.getScale();
237 | zoom = Math.max(1F, zoom);
238 | if ((Math.abs(zoom - getScale()) / zoom) > .1) {
239 | float[] coordinates = new float[]{hv.mCropRect.centerX(),
240 | hv.mCropRect.centerY()};
241 | getImageMatrix().mapPoints(coordinates);
242 | zoomTo(zoom, coordinates[0], coordinates[1], 300F);
243 | }
244 |
245 | ensureVisible(hv);
246 | }
247 |
248 | @Override
249 | protected void onDraw(Canvas canvas) {
250 |
251 | super.onDraw(canvas);
252 | for (int i = 0; i < mHighlightViews.size(); i++) {
253 | mHighlightViews.get(i).draw(canvas);
254 | }
255 | }
256 |
257 | public void add(HighlightView hv) {
258 |
259 | mHighlightViews.add(hv);
260 | invalidate();
261 | }
262 | }
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/HighlightView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 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 eu.janmuller.android.simplecropimage;
18 |
19 |
20 | import android.graphics.*;
21 | import android.graphics.drawable.Drawable;
22 | import android.view.View;
23 |
24 | // This class is used by CropImage to display a highlighted cropping rectangle
25 | // overlayed with the image. There are two coordinate spaces in use. One is
26 | // image, another is screen. computeLayout() uses mMatrix to map from image
27 | // space to screen space.
28 | class HighlightView {
29 |
30 | @SuppressWarnings("unused")
31 | private static final String TAG = "HighlightView";
32 | View mContext; // The View displaying the image.
33 |
34 | public static final int GROW_NONE = (1 << 0);
35 | public static final int GROW_LEFT_EDGE = (1 << 1);
36 | public static final int GROW_RIGHT_EDGE = (1 << 2);
37 | public static final int GROW_TOP_EDGE = (1 << 3);
38 | public static final int GROW_BOTTOM_EDGE = (1 << 4);
39 | public static final int MOVE = (1 << 5);
40 |
41 | public HighlightView(View ctx) {
42 |
43 | mContext = ctx;
44 | }
45 |
46 | private void init() {
47 |
48 | android.content.res.Resources resources = mContext.getResources();
49 | mResizeDrawableWidth =
50 | resources.getDrawable(R.drawable.camera_crop_width);
51 | mResizeDrawableHeight =
52 | resources.getDrawable(R.drawable.camera_crop_height);
53 | mResizeDrawableDiagonal =
54 | resources.getDrawable(R.drawable.indicator_autocrop);
55 | }
56 |
57 | boolean mIsFocused;
58 | boolean mHidden;
59 |
60 | public boolean hasFocus() {
61 |
62 | return mIsFocused;
63 | }
64 |
65 | public void setFocus(boolean f) {
66 |
67 | mIsFocused = f;
68 | }
69 |
70 | public void setHidden(boolean hidden) {
71 |
72 | mHidden = hidden;
73 | }
74 |
75 | protected void draw(Canvas canvas) {
76 |
77 | if (mHidden) {
78 | return;
79 | }
80 |
81 | Path path = new Path();
82 | if (!hasFocus()) {
83 | mOutlinePaint.setColor(0xFF000000);
84 | canvas.drawRect(mDrawRect, mOutlinePaint);
85 | } else {
86 | Rect viewDrawingRect = new Rect();
87 | mContext.getDrawingRect(viewDrawingRect);
88 | if (mCircle) {
89 |
90 | canvas.save();
91 |
92 | float width = mDrawRect.width();
93 | float height = mDrawRect.height();
94 | path.addCircle(mDrawRect.left + (width / 2),
95 | mDrawRect.top + (height / 2),
96 | width / 2,
97 | Path.Direction.CW);
98 | mOutlinePaint.setColor(0xFFEF04D6);
99 |
100 | canvas.clipPath(path, Region.Op.DIFFERENCE);
101 | canvas.drawRect(viewDrawingRect,
102 | hasFocus() ? mFocusPaint : mNoFocusPaint);
103 |
104 | canvas.restore();
105 |
106 |
107 | } else {
108 |
109 | Rect topRect = new Rect(viewDrawingRect.left, viewDrawingRect.top, viewDrawingRect.right, mDrawRect.top );
110 | if (topRect.width() > 0 && topRect.height() > 0) {
111 | canvas.drawRect(topRect, hasFocus() ? mFocusPaint : mNoFocusPaint);
112 | }
113 | Rect bottomRect = new Rect(viewDrawingRect.left, mDrawRect.bottom, viewDrawingRect.right, viewDrawingRect.bottom);
114 | if (bottomRect.width() > 0 && bottomRect.height() > 0) {
115 | canvas.drawRect(bottomRect, hasFocus() ? mFocusPaint : mNoFocusPaint);
116 | }
117 | Rect leftRect = new Rect(viewDrawingRect.left, topRect.bottom, mDrawRect.left, bottomRect.top);
118 | if (leftRect.width() > 0 && leftRect.height() > 0) {
119 | canvas.drawRect(leftRect, hasFocus() ? mFocusPaint : mNoFocusPaint);
120 | }
121 | Rect rightRect = new Rect(mDrawRect.right, topRect.bottom, viewDrawingRect.right, bottomRect.top);
122 | if (rightRect.width() > 0 && rightRect.height() > 0) {
123 | canvas.drawRect(rightRect, hasFocus() ? mFocusPaint : mNoFocusPaint);
124 | }
125 |
126 | path.addRect(new RectF(mDrawRect), Path.Direction.CW);
127 |
128 | mOutlinePaint.setColor(0xFFFF8A00);
129 |
130 | }
131 |
132 |
133 | canvas.drawPath(path, mOutlinePaint);
134 |
135 | if (mMode == ModifyMode.Grow) {
136 | if (mCircle) {
137 | int width = mResizeDrawableDiagonal.getIntrinsicWidth();
138 | int height = mResizeDrawableDiagonal.getIntrinsicHeight();
139 |
140 | int d = (int) Math.round(Math.cos(/*45deg*/Math.PI / 4D)
141 | * (mDrawRect.width() / 2D));
142 | int x = mDrawRect.left
143 | + (mDrawRect.width() / 2) + d - width / 2;
144 | int y = mDrawRect.top
145 | + (mDrawRect.height() / 2) - d - height / 2;
146 | mResizeDrawableDiagonal.setBounds(x, y,
147 | x + mResizeDrawableDiagonal.getIntrinsicWidth(),
148 | y + mResizeDrawableDiagonal.getIntrinsicHeight());
149 | mResizeDrawableDiagonal.draw(canvas);
150 | } else {
151 | int left = mDrawRect.left + 1;
152 | int right = mDrawRect.right + 1;
153 | int top = mDrawRect.top + 4;
154 | int bottom = mDrawRect.bottom + 3;
155 |
156 | int widthWidth =
157 | mResizeDrawableWidth.getIntrinsicWidth() / 2;
158 | int widthHeight =
159 | mResizeDrawableWidth.getIntrinsicHeight() / 2;
160 | int heightHeight =
161 | mResizeDrawableHeight.getIntrinsicHeight() / 2;
162 | int heightWidth =
163 | mResizeDrawableHeight.getIntrinsicWidth() / 2;
164 |
165 | int xMiddle = mDrawRect.left
166 | + ((mDrawRect.right - mDrawRect.left) / 2);
167 | int yMiddle = mDrawRect.top
168 | + ((mDrawRect.bottom - mDrawRect.top) / 2);
169 |
170 | mResizeDrawableWidth.setBounds(left - widthWidth,
171 | yMiddle - widthHeight,
172 | left + widthWidth,
173 | yMiddle + widthHeight);
174 | mResizeDrawableWidth.draw(canvas);
175 |
176 | mResizeDrawableWidth.setBounds(right - widthWidth,
177 | yMiddle - widthHeight,
178 | right + widthWidth,
179 | yMiddle + widthHeight);
180 | mResizeDrawableWidth.draw(canvas);
181 |
182 | mResizeDrawableHeight.setBounds(xMiddle - heightWidth,
183 | top - heightHeight,
184 | xMiddle + heightWidth,
185 | top + heightHeight);
186 | mResizeDrawableHeight.draw(canvas);
187 |
188 | mResizeDrawableHeight.setBounds(xMiddle - heightWidth,
189 | bottom - heightHeight,
190 | xMiddle + heightWidth,
191 | bottom + heightHeight);
192 | mResizeDrawableHeight.draw(canvas);
193 | }
194 | }
195 | }
196 | }
197 |
198 | public ModifyMode getMode() {
199 |
200 | return mMode;
201 | }
202 |
203 | public void setMode(ModifyMode mode) {
204 |
205 | if (mode != mMode) {
206 | mMode = mode;
207 | mContext.invalidate();
208 | }
209 | }
210 |
211 | // Determines which edges are hit by touching at (x, y).
212 | public int getHit(float x, float y) {
213 |
214 | Rect r = computeLayout();
215 | final float hysteresis = 20F;
216 | int retval = GROW_NONE;
217 |
218 | if (mCircle) {
219 | float distX = x - r.centerX();
220 | float distY = y - r.centerY();
221 | int distanceFromCenter =
222 | (int) Math.sqrt(distX * distX + distY * distY);
223 | int radius = mDrawRect.width() / 2;
224 | int delta = distanceFromCenter - radius;
225 | if (Math.abs(delta) <= hysteresis) {
226 | if (Math.abs(distY) > Math.abs(distX)) {
227 | if (distY < 0) {
228 | retval = GROW_TOP_EDGE;
229 | } else {
230 | retval = GROW_BOTTOM_EDGE;
231 | }
232 | } else {
233 | if (distX < 0) {
234 | retval = GROW_LEFT_EDGE;
235 | } else {
236 | retval = GROW_RIGHT_EDGE;
237 | }
238 | }
239 | } else if (distanceFromCenter < radius) {
240 | retval = MOVE;
241 | } else {
242 | retval = GROW_NONE;
243 | }
244 | } else {
245 | // verticalCheck makes sure the position is between the top and
246 | // the bottom edge (with some tolerance). Similar for horizCheck.
247 | boolean verticalCheck = (y >= r.top - hysteresis)
248 | && (y < r.bottom + hysteresis);
249 | boolean horizCheck = (x >= r.left - hysteresis)
250 | && (x < r.right + hysteresis);
251 |
252 | // Check whether the position is near some edge(s).
253 | if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
254 | retval |= GROW_LEFT_EDGE;
255 | }
256 | if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
257 | retval |= GROW_RIGHT_EDGE;
258 | }
259 | if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
260 | retval |= GROW_TOP_EDGE;
261 | }
262 | if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
263 | retval |= GROW_BOTTOM_EDGE;
264 | }
265 |
266 | // Not near any edge but inside the rectangle: move.
267 | if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
268 | retval = MOVE;
269 | }
270 | }
271 | return retval;
272 | }
273 |
274 | // Handles motion (dx, dy) in screen space.
275 | // The "edge" parameter specifies which edges the user is dragging.
276 | void handleMotion(int edge, float dx, float dy) {
277 |
278 | Rect r = computeLayout();
279 | if (edge == GROW_NONE) {
280 | return;
281 | } else if (edge == MOVE) {
282 | // Convert to image space before sending to moveBy().
283 | moveBy(dx * (mCropRect.width() / r.width()),
284 | dy * (mCropRect.height() / r.height()));
285 | } else {
286 | if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
287 | dx = 0;
288 | }
289 |
290 | if (((GROW_TOP_EDGE | GROW_BOTTOM_EDGE) & edge) == 0) {
291 | dy = 0;
292 | }
293 |
294 | // Convert to image space before sending to growBy().
295 | float xDelta = dx * (mCropRect.width() / r.width());
296 | float yDelta = dy * (mCropRect.height() / r.height());
297 | growBy((((edge & GROW_LEFT_EDGE) != 0) ? -1 : 1) * xDelta,
298 | (((edge & GROW_TOP_EDGE) != 0) ? -1 : 1) * yDelta);
299 | }
300 | }
301 |
302 | // Grows the cropping rectange by (dx, dy) in image space.
303 | void moveBy(float dx, float dy) {
304 |
305 | Rect invalRect = new Rect(mDrawRect);
306 |
307 | mCropRect.offset(dx, dy);
308 |
309 | // Put the cropping rectangle inside image rectangle.
310 | mCropRect.offset(
311 | Math.max(0, mImageRect.left - mCropRect.left),
312 | Math.max(0, mImageRect.top - mCropRect.top));
313 |
314 | mCropRect.offset(
315 | Math.min(0, mImageRect.right - mCropRect.right),
316 | Math.min(0, mImageRect.bottom - mCropRect.bottom));
317 |
318 | mDrawRect = computeLayout();
319 | invalRect.union(mDrawRect);
320 | invalRect.inset(-10, -10);
321 | mContext.invalidate(invalRect);
322 | }
323 |
324 | // Grows the cropping rectange by (dx, dy) in image space.
325 | void growBy(float dx, float dy) {
326 |
327 | if (mMaintainAspectRatio) {
328 | if (dx != 0) {
329 | dy = dx / mInitialAspectRatio;
330 | } else if (dy != 0) {
331 | dx = dy * mInitialAspectRatio;
332 | }
333 | }
334 |
335 | // Don't let the cropping rectangle grow too fast.
336 | // Grow at most half of the difference between the image rectangle and
337 | // the cropping rectangle.
338 | RectF r = new RectF(mCropRect);
339 | if (dx > 0F && r.width() + 2 * dx > mImageRect.width()) {
340 | float adjustment = (mImageRect.width() - r.width()) / 2F;
341 | dx = adjustment;
342 | if (mMaintainAspectRatio) {
343 | dy = dx / mInitialAspectRatio;
344 | }
345 | }
346 | if (dy > 0F && r.height() + 2 * dy > mImageRect.height()) {
347 | float adjustment = (mImageRect.height() - r.height()) / 2F;
348 | dy = adjustment;
349 | if (mMaintainAspectRatio) {
350 | dx = dy * mInitialAspectRatio;
351 | }
352 | }
353 |
354 | r.inset(-dx, -dy);
355 |
356 | // Don't let the cropping rectangle shrink too fast.
357 | final float widthCap = 25F;
358 | if (r.width() < widthCap) {
359 | r.inset(-(widthCap - r.width()) / 2F, 0F);
360 | }
361 | float heightCap = mMaintainAspectRatio
362 | ? (widthCap / mInitialAspectRatio)
363 | : widthCap;
364 | if (r.height() < heightCap) {
365 | r.inset(0F, -(heightCap - r.height()) / 2F);
366 | }
367 |
368 | // Put the cropping rectangle inside the image rectangle.
369 | if (r.left < mImageRect.left) {
370 | r.offset(mImageRect.left - r.left, 0F);
371 | } else if (r.right > mImageRect.right) {
372 | r.offset(-(r.right - mImageRect.right), 0);
373 | }
374 | if (r.top < mImageRect.top) {
375 | r.offset(0F, mImageRect.top - r.top);
376 | } else if (r.bottom > mImageRect.bottom) {
377 | r.offset(0F, -(r.bottom - mImageRect.bottom));
378 | }
379 |
380 | mCropRect.set(r);
381 | mDrawRect = computeLayout();
382 | mContext.invalidate();
383 | }
384 |
385 | // Returns the cropping rectangle in image space.
386 | public Rect getCropRect() {
387 |
388 | return new Rect((int) mCropRect.left, (int) mCropRect.top,
389 | (int) mCropRect.right, (int) mCropRect.bottom);
390 | }
391 |
392 | // Maps the cropping rectangle from image space to screen space.
393 | private Rect computeLayout() {
394 |
395 | RectF r = new RectF(mCropRect.left, mCropRect.top,
396 | mCropRect.right, mCropRect.bottom);
397 | mMatrix.mapRect(r);
398 | return new Rect(Math.round(r.left), Math.round(r.top),
399 | Math.round(r.right), Math.round(r.bottom));
400 | }
401 |
402 | public void invalidate() {
403 |
404 | mDrawRect = computeLayout();
405 | }
406 |
407 | public void setup(Matrix m, Rect imageRect, RectF cropRect, boolean circle,
408 | boolean maintainAspectRatio) {
409 |
410 | if (circle) {
411 | maintainAspectRatio = true;
412 | }
413 | mMatrix = new Matrix(m);
414 |
415 | mCropRect = cropRect;
416 | mImageRect = new RectF(imageRect);
417 | mMaintainAspectRatio = maintainAspectRatio;
418 | mCircle = circle;
419 |
420 | mInitialAspectRatio = mCropRect.width() / mCropRect.height();
421 | mDrawRect = computeLayout();
422 |
423 | mFocusPaint.setARGB(125, 50, 50, 50);
424 | mNoFocusPaint.setARGB(125, 50, 50, 50);
425 | mOutlinePaint.setStrokeWidth(3F);
426 | mOutlinePaint.setStyle(Paint.Style.STROKE);
427 | mOutlinePaint.setAntiAlias(true);
428 |
429 | mMode = ModifyMode.None;
430 | init();
431 | }
432 |
433 | enum ModifyMode {None, Move, Grow}
434 |
435 | private ModifyMode mMode = ModifyMode.None;
436 |
437 | Rect mDrawRect; // in screen space
438 | private RectF mImageRect; // in image space
439 | RectF mCropRect; // in image space
440 | Matrix mMatrix;
441 |
442 | private boolean mMaintainAspectRatio = false;
443 | private float mInitialAspectRatio;
444 | private boolean mCircle = false;
445 |
446 | private Drawable mResizeDrawableWidth;
447 | private Drawable mResizeDrawableHeight;
448 | private Drawable mResizeDrawableDiagonal;
449 |
450 | private final Paint mFocusPaint = new Paint();
451 | private final Paint mNoFocusPaint = new Paint();
452 | private final Paint mOutlinePaint = new Paint();
453 | }
454 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/ImageViewTouchBase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 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 eu.janmuller.android.simplecropimage;
18 |
19 | import android.content.Context;
20 | import android.graphics.Bitmap;
21 | import android.graphics.Matrix;
22 | import android.graphics.RectF;
23 | import android.graphics.drawable.Drawable;
24 | import android.os.Handler;
25 | import android.util.AttributeSet;
26 | import android.view.KeyEvent;
27 | import android.widget.ImageView;
28 |
29 | abstract class ImageViewTouchBase extends ImageView {
30 |
31 | @SuppressWarnings("unused")
32 | private static final String TAG = "ImageViewTouchBase";
33 |
34 | // This is the base transformation which is used to show the image
35 | // initially. The current computation for this shows the image in
36 | // it's entirety, letterboxing as needed. One could choose to
37 | // show the image as cropped instead.
38 | //
39 | // This matrix is recomputed when we go from the thumbnail image to
40 | // the full size image.
41 | protected Matrix mBaseMatrix = new Matrix();
42 |
43 | // This is the supplementary transformation which reflects what
44 | // the user has done in terms of zooming and panning.
45 | //
46 | // This matrix remains the same when we go from the thumbnail image
47 | // to the full size image.
48 | protected Matrix mSuppMatrix = new Matrix();
49 |
50 | // This is the final matrix which is computed as the concatentation
51 | // of the base matrix and the supplementary matrix.
52 | private final Matrix mDisplayMatrix = new Matrix();
53 |
54 | // Temporary buffer used for getting the values out of a matrix.
55 | private final float[] mMatrixValues = new float[9];
56 |
57 | // The current bitmap being displayed.
58 | final protected RotateBitmap mBitmapDisplayed = new RotateBitmap(null);
59 |
60 | int mThisWidth = -1, mThisHeight = -1;
61 |
62 | float mMaxZoom;
63 |
64 | int mLeft;
65 |
66 | int mRight;
67 |
68 | int mTop;
69 |
70 | int mBottom;
71 |
72 | // ImageViewTouchBase will pass a Bitmap to the Recycler if it has finished
73 | // its use of that Bitmap.
74 | public interface Recycler {
75 |
76 | public void recycle(Bitmap b);
77 | }
78 |
79 | public void setRecycler(Recycler r) {
80 |
81 | mRecycler = r;
82 | }
83 |
84 | private Recycler mRecycler;
85 |
86 | @Override
87 | protected void onLayout(boolean changed, int left, int top,
88 | int right, int bottom) {
89 |
90 | super.onLayout(changed, left, top, right, bottom);
91 | mLeft = left;
92 | mRight = right;
93 | mTop = top;
94 | mBottom = bottom;
95 | mThisWidth = right - left;
96 | mThisHeight = bottom - top;
97 | Runnable r = mOnLayoutRunnable;
98 | if (r != null) {
99 | mOnLayoutRunnable = null;
100 | r.run();
101 | }
102 | if (mBitmapDisplayed.getBitmap() != null) {
103 | getProperBaseMatrix(mBitmapDisplayed, mBaseMatrix);
104 | setImageMatrix(getImageViewMatrix());
105 | }
106 | }
107 |
108 | @Override
109 | public boolean onKeyDown(int keyCode, KeyEvent event) {
110 |
111 | if (keyCode == KeyEvent.KEYCODE_BACK && getScale() > 1.0f) {
112 | // If we're zoomed in, pressing Back jumps out to show the entire
113 | // image, otherwise Back returns the user to the gallery.
114 | zoomTo(1.0f);
115 | return true;
116 | }
117 | return super.onKeyDown(keyCode, event);
118 | }
119 |
120 | protected Handler mHandler = new Handler();
121 |
122 | @Override
123 | public void setImageBitmap(Bitmap bitmap) {
124 |
125 | setImageBitmap(bitmap, 0);
126 | }
127 |
128 | private void setImageBitmap(Bitmap bitmap, int rotation) {
129 |
130 | super.setImageBitmap(bitmap);
131 | Drawable d = getDrawable();
132 | if (d != null) {
133 | d.setDither(true);
134 | }
135 |
136 | Bitmap old = mBitmapDisplayed.getBitmap();
137 | mBitmapDisplayed.setBitmap(bitmap);
138 | mBitmapDisplayed.setRotation(rotation);
139 |
140 | if (old != null && old != bitmap && mRecycler != null) {
141 | mRecycler.recycle(old);
142 | }
143 | }
144 |
145 | public void clear() {
146 |
147 | setImageBitmapResetBase(null, true);
148 | }
149 |
150 | private Runnable mOnLayoutRunnable = null;
151 |
152 | // This function changes bitmap, reset base matrix according to the size
153 | // of the bitmap, and optionally reset the supplementary matrix.
154 | public void setImageBitmapResetBase(final Bitmap bitmap,
155 | final boolean resetSupp) {
156 |
157 | setImageRotateBitmapResetBase(new RotateBitmap(bitmap), resetSupp);
158 | }
159 |
160 | public void setImageRotateBitmapResetBase(final RotateBitmap bitmap,
161 | final boolean resetSupp) {
162 |
163 | final int viewWidth = getWidth();
164 |
165 | if (viewWidth <= 0) {
166 | mOnLayoutRunnable = new Runnable() {
167 | public void run() {
168 |
169 | setImageRotateBitmapResetBase(bitmap, resetSupp);
170 | }
171 | };
172 | return;
173 | }
174 |
175 | if (bitmap.getBitmap() != null) {
176 | getProperBaseMatrix(bitmap, mBaseMatrix);
177 | setImageBitmap(bitmap.getBitmap(), bitmap.getRotation());
178 | } else {
179 | mBaseMatrix.reset();
180 | setImageBitmap(null);
181 | }
182 |
183 | if (resetSupp) {
184 | mSuppMatrix.reset();
185 | }
186 | setImageMatrix(getImageViewMatrix());
187 | mMaxZoom = maxZoom();
188 | }
189 |
190 | // Center as much as possible in one or both axis. Centering is
191 | // defined as follows: if the image is scaled down below the
192 | // view's dimensions then center it (literally). If the image
193 | // is scaled larger than the view and is translated out of view
194 | // then translate it back into view (i.e. eliminate black bars).
195 | protected void center(boolean horizontal, boolean vertical) {
196 |
197 | if (mBitmapDisplayed.getBitmap() == null) {
198 | return;
199 | }
200 |
201 | Matrix m = getImageViewMatrix();
202 |
203 | RectF rect = new RectF(0, 0,
204 | mBitmapDisplayed.getBitmap().getWidth(),
205 | mBitmapDisplayed.getBitmap().getHeight());
206 |
207 | m.mapRect(rect);
208 |
209 | float height = rect.height();
210 | float width = rect.width();
211 |
212 | float deltaX = 0, deltaY = 0;
213 |
214 | if (vertical) {
215 | int viewHeight = getHeight();
216 | if (height < viewHeight) {
217 | deltaY = (viewHeight - height) / 2 - rect.top;
218 | } else if (rect.top > 0) {
219 | deltaY = -rect.top;
220 | } else if (rect.bottom < viewHeight) {
221 | deltaY = getHeight() - rect.bottom;
222 | }
223 | }
224 |
225 | if (horizontal) {
226 | int viewWidth = getWidth();
227 | if (width < viewWidth) {
228 | deltaX = (viewWidth - width) / 2 - rect.left;
229 | } else if (rect.left > 0) {
230 | deltaX = -rect.left;
231 | } else if (rect.right < viewWidth) {
232 | deltaX = viewWidth - rect.right;
233 | }
234 | }
235 |
236 | postTranslate(deltaX, deltaY);
237 | setImageMatrix(getImageViewMatrix());
238 | }
239 |
240 | public ImageViewTouchBase(Context context) {
241 |
242 | super(context);
243 | init();
244 | }
245 |
246 | public ImageViewTouchBase(Context context, AttributeSet attrs) {
247 |
248 | super(context, attrs);
249 | init();
250 | }
251 |
252 | private void init() {
253 |
254 | setScaleType(ScaleType.MATRIX);
255 | }
256 |
257 | protected float getValue(Matrix matrix, int whichValue) {
258 |
259 | matrix.getValues(mMatrixValues);
260 | return mMatrixValues[whichValue];
261 | }
262 |
263 | // Get the scale factor out of the matrix.
264 | protected float getScale(Matrix matrix) {
265 |
266 | return getValue(matrix, Matrix.MSCALE_X);
267 | }
268 |
269 | protected float getScale() {
270 |
271 | return getScale(mSuppMatrix);
272 | }
273 |
274 | // Setup the base matrix so that the image is centered and scaled properly.
275 | private void getProperBaseMatrix(RotateBitmap bitmap, Matrix matrix) {
276 |
277 | float viewWidth = getWidth();
278 | float viewHeight = getHeight();
279 |
280 | float w = bitmap.getWidth();
281 | float h = bitmap.getHeight();
282 | int rotation = bitmap.getRotation();
283 | matrix.reset();
284 |
285 | // We limit up-scaling to 2x otherwise the result may look bad if it's
286 | // a small icon.
287 | float widthScale = Math.min(viewWidth / w, 2.0f);
288 | float heightScale = Math.min(viewHeight / h, 2.0f);
289 | float scale = Math.min(widthScale, heightScale);
290 |
291 | matrix.postConcat(bitmap.getRotateMatrix());
292 | matrix.postScale(scale, scale);
293 |
294 | matrix.postTranslate(
295 | (viewWidth - w * scale) / 2F,
296 | (viewHeight - h * scale) / 2F);
297 | }
298 |
299 | // Combine the base matrix and the supp matrix to make the final matrix.
300 | protected Matrix getImageViewMatrix() {
301 | // The final matrix is computed as the concatentation of the base matrix
302 | // and the supplementary matrix.
303 | mDisplayMatrix.set(mBaseMatrix);
304 | mDisplayMatrix.postConcat(mSuppMatrix);
305 | return mDisplayMatrix;
306 | }
307 |
308 | static final float SCALE_RATE = 1.25F;
309 |
310 | // Sets the maximum zoom, which is a scale relative to the base matrix. It
311 | // is calculated to show the image at 400% zoom regardless of screen or
312 | // image orientation. If in the future we decode the full 3 megapixel image,
313 | // rather than the current 1024x768, this should be changed down to 200%.
314 | protected float maxZoom() {
315 |
316 | if (mBitmapDisplayed.getBitmap() == null) {
317 | return 1F;
318 | }
319 |
320 | float fw = (float) mBitmapDisplayed.getWidth() / (float) mThisWidth;
321 | float fh = (float) mBitmapDisplayed.getHeight() / (float) mThisHeight;
322 | float max = Math.max(fw, fh) * 4;
323 | return max;
324 | }
325 |
326 | protected void zoomTo(float scale, float centerX, float centerY) {
327 |
328 | if (scale > mMaxZoom) {
329 | scale = mMaxZoom;
330 | }
331 |
332 | float oldScale = getScale();
333 | float deltaScale = scale / oldScale;
334 |
335 | mSuppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
336 | setImageMatrix(getImageViewMatrix());
337 | center(true, true);
338 | }
339 |
340 | protected void zoomTo(final float scale, final float centerX,
341 | final float centerY, final float durationMs) {
342 |
343 | final float incrementPerMs = (scale - getScale()) / durationMs;
344 | final float oldScale = getScale();
345 | final long startTime = System.currentTimeMillis();
346 |
347 | mHandler.post(new Runnable() {
348 | public void run() {
349 |
350 | long now = System.currentTimeMillis();
351 | float currentMs = Math.min(durationMs, now - startTime);
352 | float target = oldScale + (incrementPerMs * currentMs);
353 | zoomTo(target, centerX, centerY);
354 |
355 | if (currentMs < durationMs) {
356 | mHandler.post(this);
357 | }
358 | }
359 | });
360 | }
361 |
362 | protected void zoomTo(float scale) {
363 |
364 | float cx = getWidth() / 2F;
365 | float cy = getHeight() / 2F;
366 |
367 | zoomTo(scale, cx, cy);
368 | }
369 |
370 | protected void zoomIn() {
371 |
372 | zoomIn(SCALE_RATE);
373 | }
374 |
375 | protected void zoomOut() {
376 |
377 | zoomOut(SCALE_RATE);
378 | }
379 |
380 | protected void zoomIn(float rate) {
381 |
382 | if (getScale() >= mMaxZoom) {
383 | return; // Don't let the user zoom into the molecular level.
384 | }
385 | if (mBitmapDisplayed.getBitmap() == null) {
386 | return;
387 | }
388 |
389 | float cx = getWidth() / 2F;
390 | float cy = getHeight() / 2F;
391 |
392 | mSuppMatrix.postScale(rate, rate, cx, cy);
393 | setImageMatrix(getImageViewMatrix());
394 | }
395 |
396 | protected void zoomOut(float rate) {
397 |
398 | if (mBitmapDisplayed.getBitmap() == null) {
399 | return;
400 | }
401 |
402 | float cx = getWidth() / 2F;
403 | float cy = getHeight() / 2F;
404 |
405 | // Zoom out to at most 1x.
406 | Matrix tmp = new Matrix(mSuppMatrix);
407 | tmp.postScale(1F / rate, 1F / rate, cx, cy);
408 |
409 | if (getScale(tmp) < 1F) {
410 | mSuppMatrix.setScale(1F, 1F, cx, cy);
411 | } else {
412 | mSuppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
413 | }
414 | setImageMatrix(getImageViewMatrix());
415 | center(true, true);
416 | }
417 |
418 | protected void postTranslate(float dx, float dy) {
419 |
420 | mSuppMatrix.postTranslate(dx, dy);
421 | }
422 |
423 | protected void panBy(float dx, float dy) {
424 |
425 | postTranslate(dx, dy);
426 | setImageMatrix(getImageViewMatrix());
427 | }
428 | }
429 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/MonitoredActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 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 eu.janmuller.android.simplecropimage;
18 |
19 | import android.app.Activity;
20 | import android.os.Bundle;
21 |
22 | import java.util.ArrayList;
23 |
24 | public class MonitoredActivity extends Activity {
25 |
26 | private final ArrayList mListeners =
27 | new ArrayList();
28 |
29 | public static interface LifeCycleListener {
30 |
31 | public void onActivityCreated(MonitoredActivity activity);
32 |
33 | public void onActivityDestroyed(MonitoredActivity activity);
34 |
35 | public void onActivityPaused(MonitoredActivity activity);
36 |
37 | public void onActivityResumed(MonitoredActivity activity);
38 |
39 | public void onActivityStarted(MonitoredActivity activity);
40 |
41 | public void onActivityStopped(MonitoredActivity activity);
42 | }
43 |
44 | public static class LifeCycleAdapter implements LifeCycleListener {
45 |
46 | public void onActivityCreated(MonitoredActivity activity) {
47 |
48 | }
49 |
50 | public void onActivityDestroyed(MonitoredActivity activity) {
51 |
52 | }
53 |
54 | public void onActivityPaused(MonitoredActivity activity) {
55 |
56 | }
57 |
58 | public void onActivityResumed(MonitoredActivity activity) {
59 |
60 | }
61 |
62 | public void onActivityStarted(MonitoredActivity activity) {
63 |
64 | }
65 |
66 | public void onActivityStopped(MonitoredActivity activity) {
67 |
68 | }
69 | }
70 |
71 | public void addLifeCycleListener(LifeCycleListener listener) {
72 |
73 | if (mListeners.contains(listener)) return;
74 | mListeners.add(listener);
75 | }
76 |
77 | public void removeLifeCycleListener(LifeCycleListener listener) {
78 |
79 | mListeners.remove(listener);
80 | }
81 |
82 | @Override
83 | protected void onCreate(Bundle savedInstanceState) {
84 |
85 | super.onCreate(savedInstanceState);
86 | for (LifeCycleListener listener : mListeners) {
87 | listener.onActivityCreated(this);
88 | }
89 | }
90 |
91 | @Override
92 | protected void onDestroy() {
93 |
94 | super.onDestroy();
95 | for (LifeCycleListener listener : mListeners) {
96 | listener.onActivityDestroyed(this);
97 | }
98 | }
99 |
100 | @Override
101 | protected void onStart() {
102 |
103 | super.onStart();
104 | for (LifeCycleListener listener : mListeners) {
105 | listener.onActivityStarted(this);
106 | }
107 | }
108 |
109 | @Override
110 | protected void onStop() {
111 |
112 | super.onStop();
113 | for (LifeCycleListener listener : mListeners) {
114 | listener.onActivityStopped(this);
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/RotateBitmap.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2009 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 eu.janmuller.android.simplecropimage;
18 |
19 | import android.graphics.Bitmap;
20 | import android.graphics.Matrix;
21 |
22 | public class RotateBitmap {
23 |
24 | public static final String TAG = "RotateBitmap";
25 | private Bitmap mBitmap;
26 | private int mRotation;
27 |
28 | public RotateBitmap(Bitmap bitmap) {
29 |
30 | mBitmap = bitmap;
31 | mRotation = 0;
32 | }
33 |
34 | public RotateBitmap(Bitmap bitmap, int rotation) {
35 |
36 | mBitmap = bitmap;
37 | mRotation = rotation % 360;
38 | }
39 |
40 | public void setRotation(int rotation) {
41 |
42 | mRotation = rotation;
43 | }
44 |
45 | public int getRotation() {
46 |
47 | return mRotation;
48 | }
49 |
50 | public Bitmap getBitmap() {
51 |
52 | return mBitmap;
53 | }
54 |
55 | public void setBitmap(Bitmap bitmap) {
56 |
57 | mBitmap = bitmap;
58 | }
59 |
60 | public Matrix getRotateMatrix() {
61 | // By default this is an identity matrix.
62 | Matrix matrix = new Matrix();
63 | if (mRotation != 0) {
64 | // We want to do the rotation at origin, but since the bounding
65 | // rectangle will be changed after rotation, so the delta values
66 | // are based on old & new width/height respectively.
67 | int cx = mBitmap.getWidth() / 2;
68 | int cy = mBitmap.getHeight() / 2;
69 | matrix.preTranslate(-cx, -cy);
70 | matrix.postRotate(mRotation);
71 | matrix.postTranslate(getWidth() / 2, getHeight() / 2);
72 | }
73 | return matrix;
74 | }
75 |
76 | public boolean isOrientationChanged() {
77 |
78 | return (mRotation / 90) % 2 != 0;
79 | }
80 |
81 | public int getHeight() {
82 |
83 | if (isOrientationChanged()) {
84 | return mBitmap.getWidth();
85 | } else {
86 | return mBitmap.getHeight();
87 | }
88 | }
89 |
90 | public int getWidth() {
91 |
92 | if (isOrientationChanged()) {
93 | return mBitmap.getHeight();
94 | } else {
95 | return mBitmap.getWidth();
96 | }
97 | }
98 |
99 | public void recycle() {
100 |
101 | if (mBitmap != null) {
102 | mBitmap.recycle();
103 | mBitmap = null;
104 | }
105 | }
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/simple-crop-image-lib/src/eu/janmuller/android/simplecropimage/Util.java:
--------------------------------------------------------------------------------
1 | package eu.janmuller.android.simplecropimage;
2 | /*
3 | * Copyright (C) 2009 The Android Open Source Project
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 |
19 | import android.app.Activity;
20 | import android.app.ProgressDialog;
21 | import android.graphics.*;
22 | import android.os.Handler;
23 | import android.view.Surface;
24 |
25 | import java.io.Closeable;
26 |
27 | /**
28 | * Collection of utility functions used in this package.
29 | */
30 | public class Util {
31 |
32 | private static final String TAG = "db.Util";
33 |
34 | private Util() {
35 |
36 | }
37 |
38 | /*
39 | * Compute the sample size as a function of minSideLength
40 | * and maxNumOfPixels.
41 | * minSideLength is used to specify that minimal width or height of a bitmap.
42 | * maxNumOfPixels is used to specify the maximal size in pixels that are tolerable
43 | * in terms of memory usage.
44 | *
45 | * The function returns a sample size based on the constraints.
46 | * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
47 | * which indicates no care of the corresponding constraint.
48 | * The functions prefers returning a sample size that
49 | * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
50 | */
51 |
52 |
53 | public static Bitmap transform(Matrix scaler,
54 | Bitmap source,
55 | int targetWidth,
56 | int targetHeight,
57 | boolean scaleUp) {
58 |
59 | int deltaX = source.getWidth() - targetWidth;
60 | int deltaY = source.getHeight() - targetHeight;
61 | if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
62 | /*
63 | * In this case the bitmap is smaller, at least in one dimension,
64 | * than the target. Transform it by placing as much of the image
65 | * as possible into the target and leaving the top/bottom or
66 | * left/right (or both) black.
67 | */
68 | Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
69 | Bitmap.Config.ARGB_8888);
70 | Canvas c = new Canvas(b2);
71 |
72 | int deltaXHalf = Math.max(0, deltaX / 2);
73 | int deltaYHalf = Math.max(0, deltaY / 2);
74 | Rect src = new Rect(
75 | deltaXHalf,
76 | deltaYHalf,
77 | deltaXHalf + Math.min(targetWidth, source.getWidth()),
78 | deltaYHalf + Math.min(targetHeight, source.getHeight()));
79 | int dstX = (targetWidth - src.width()) / 2;
80 | int dstY = (targetHeight - src.height()) / 2;
81 | Rect dst = new Rect(
82 | dstX,
83 | dstY,
84 | targetWidth - dstX,
85 | targetHeight - dstY);
86 | c.drawBitmap(source, src, dst, null);
87 | return b2;
88 | }
89 | float bitmapWidthF = source.getWidth();
90 | float bitmapHeightF = source.getHeight();
91 |
92 | float bitmapAspect = bitmapWidthF / bitmapHeightF;
93 | float viewAspect = (float) targetWidth / targetHeight;
94 |
95 | if (bitmapAspect > viewAspect) {
96 | float scale = targetHeight / bitmapHeightF;
97 | if (scale < .9F || scale > 1F) {
98 | scaler.setScale(scale, scale);
99 | } else {
100 | scaler = null;
101 | }
102 | } else {
103 | float scale = targetWidth / bitmapWidthF;
104 | if (scale < .9F || scale > 1F) {
105 | scaler.setScale(scale, scale);
106 | } else {
107 | scaler = null;
108 | }
109 | }
110 |
111 | Bitmap b1;
112 | if (scaler != null) {
113 | // this is used for minithumb and crop, so we want to mFilter here.
114 | b1 = Bitmap.createBitmap(source, 0, 0,
115 | source.getWidth(), source.getHeight(), scaler, true);
116 | } else {
117 | b1 = source;
118 | }
119 |
120 | int dx1 = Math.max(0, b1.getWidth() - targetWidth);
121 | int dy1 = Math.max(0, b1.getHeight() - targetHeight);
122 |
123 | Bitmap b2 = Bitmap.createBitmap(
124 | b1,
125 | dx1 / 2,
126 | dy1 / 2,
127 | targetWidth,
128 | targetHeight);
129 |
130 | if (b1 != source) {
131 | b1.recycle();
132 | }
133 |
134 | return b2;
135 | }
136 |
137 | public static void closeSilently(Closeable c) {
138 |
139 | if (c == null) return;
140 | try {
141 | c.close();
142 | } catch (Throwable t) {
143 | // do nothing
144 | }
145 | }
146 |
147 | private static class BackgroundJob
148 | extends MonitoredActivity.LifeCycleAdapter implements Runnable {
149 |
150 | private final MonitoredActivity mActivity;
151 | private final ProgressDialog mDialog;
152 | private final Runnable mJob;
153 | private final Handler mHandler;
154 | private final Runnable mCleanupRunner = new Runnable() {
155 | public void run() {
156 |
157 | mActivity.removeLifeCycleListener(BackgroundJob.this);
158 | if (mDialog.getWindow() != null) mDialog.dismiss();
159 | }
160 | };
161 |
162 | public BackgroundJob(MonitoredActivity activity, Runnable job,
163 | ProgressDialog dialog, Handler handler) {
164 |
165 | mActivity = activity;
166 | mDialog = dialog;
167 | mJob = job;
168 | mActivity.addLifeCycleListener(this);
169 | mHandler = handler;
170 | }
171 |
172 | public void run() {
173 |
174 | try {
175 | mJob.run();
176 | } finally {
177 | mHandler.post(mCleanupRunner);
178 | }
179 | }
180 |
181 |
182 | @Override
183 | public void onActivityDestroyed(MonitoredActivity activity) {
184 | // We get here only when the onDestroyed being called before
185 | // the mCleanupRunner. So, run it now and remove it from the queue
186 | mCleanupRunner.run();
187 | mHandler.removeCallbacks(mCleanupRunner);
188 | }
189 |
190 | @Override
191 | public void onActivityStopped(MonitoredActivity activity) {
192 |
193 | mDialog.hide();
194 | }
195 |
196 | @Override
197 | public void onActivityStarted(MonitoredActivity activity) {
198 |
199 | mDialog.show();
200 | }
201 | }
202 |
203 | public static void startBackgroundJob(MonitoredActivity activity,
204 | String title, String message, Runnable job, Handler handler) {
205 | // Make the progress dialog uncancelable, so that we can gurantee
206 | // the thread will be done before the activity getting destroyed.
207 | ProgressDialog dialog = ProgressDialog.show(
208 | activity, title, message, true, false);
209 | new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
210 | }
211 |
212 |
213 | // Returns Options that set the puregeable flag for Bitmap decode.
214 | public static BitmapFactory.Options createNativeAllocOptions() {
215 |
216 | BitmapFactory.Options options = new BitmapFactory.Options();
217 | //options.inNativeAlloc = true;
218 | return options;
219 | }
220 |
221 | // Thong added for rotate
222 | public static Bitmap rotateImage(Bitmap src, float degree) {
223 | // create new matrix
224 | Matrix matrix = new Matrix();
225 | // setup rotation degree
226 | matrix.postRotate(degree);
227 | Bitmap bmp = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
228 | return bmp;
229 | }
230 |
231 | public static int getOrientationInDegree(Activity activity) {
232 |
233 | int rotation = activity.getWindowManager().getDefaultDisplay()
234 | .getRotation();
235 | int degrees = 0;
236 |
237 | switch (rotation) {
238 | case Surface.ROTATION_0:
239 | degrees = 0;
240 | break;
241 | case Surface.ROTATION_90:
242 | degrees = 90;
243 | break;
244 | case Surface.ROTATION_180:
245 | degrees = 180;
246 | break;
247 | case Surface.ROTATION_270:
248 | degrees = 270;
249 | break;
250 | }
251 |
252 | return degrees;
253 | }
254 | }
255 |
--------------------------------------------------------------------------------