Compare commits
70 Commits
exif-same-
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
2db9b6eeae | |
|
|
182d247a52 | |
|
|
d8a549bb56 | |
|
|
1921af545c | |
|
|
946e959302 | |
|
|
0f8b5ae8e7 | |
|
|
65f3be4c56 | |
|
|
d981090e37 | |
|
|
97502265ce | |
|
|
b4be2c2703 | |
|
|
b532e0095a | |
|
|
3b4d512236 | |
|
|
5684c03ea5 | |
|
|
e152edf0c4 | |
|
|
a08203184b | |
|
|
8279b868ad | |
|
|
dfe8cd0724 | |
|
|
da391b4973 | |
|
|
d9b55467f1 | |
|
|
bafa75b42e | |
|
|
d38710fd91 | |
|
|
ccd2faba89 | |
|
|
7186cf2b61 | |
|
|
8e55a749e1 | |
|
|
48321788d8 | |
|
|
0abbe54b64 | |
|
|
35b8e40f83 | |
|
|
3abf2ddbd6 | |
|
|
e4cc1edc2f | |
|
|
2104764c9c | |
|
|
3a3c404ec8 | |
|
|
ad483ebf4a | |
|
|
801d204e96 | |
|
|
ebd7f7a135 | |
|
|
a8daff5617 | |
|
|
e5d0c97a29 | |
|
|
32772d8087 | |
|
|
ff4db1a904 | |
|
|
d6ddf873d9 | |
|
|
f384ae31dd | |
|
|
b37f43f2fc | |
|
|
2aeda079cd | |
|
|
b494ac0278 | |
|
|
931a222a2d | |
|
|
a80587562d | |
|
|
fe3f7fd41f | |
|
|
1f529d9f0c | |
|
|
df18e470f9 | |
|
|
01c77ff8b5 | |
|
|
71e936c17b | |
|
|
3d461dac55 | |
|
|
cd5ac96ad7 | |
|
|
9f9b8174fb | |
|
|
4bbc21ec2f | |
|
|
02758958be | |
|
|
bbe009289e | |
|
|
18ab2bef7c | |
|
|
fea38b4f9f | |
|
|
f734d4c22b | |
|
|
ecbc98997a | |
|
|
b6c3d2194c | |
|
|
9d654fa2b3 | |
|
|
80d0f0c754 | |
|
|
d30306ebff | |
|
|
b25f09ebbf | |
|
|
655c2c668f | |
|
|
0df72f4627 | |
|
|
06137586b4 | |
|
|
058b870a13 | |
|
|
d7a3ae2fb8 |
18
.travis.yml
18
.travis.yml
|
|
@ -1,21 +1,13 @@
|
||||||
language: android
|
language: android
|
||||||
|
sudo: false
|
||||||
|
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- build-tools-21.1.1
|
- build-tools-23.0.1
|
||||||
- android-21
|
- android-23
|
||||||
- extra-android-support
|
- extra-android-support
|
||||||
- sys-img-armeabi-v7a-android-21
|
- extra-android-m2repository
|
||||||
|
|
||||||
install:
|
|
||||||
- ./gradlew :lib:build
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- echo no | android create avd --force -n test -t android-21 --abi armeabi-v7a
|
|
||||||
- emulator -avd test -no-skin -no-audio -no-window &
|
|
||||||
- android-wait-for-emulator
|
|
||||||
- adb shell input keyevent 82 &
|
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./gradlew :lib:connectedAndroidTest
|
- ./gradlew clean build
|
||||||
|
|
||||||
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -1,3 +1,28 @@
|
||||||
|
## Next
|
||||||
|
* Fix max size crash when input cannot be decoded
|
||||||
|
* Translations: German, Chinese (simplified & traditional)
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* Support image picker helper from Fragments
|
||||||
|
* Restore support for SDK level 10
|
||||||
|
* Fix translucent status bar set via app theme
|
||||||
|
* Fix wrong result code when crop results in IOException
|
||||||
|
* Fix image "twitching" on zoom out to max bounds
|
||||||
|
* Translations: Italian, Turkish, Catalan, Swedish
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Improved builder interface: `Crop.of(in, out).start(activity)`
|
||||||
|
* Material styling
|
||||||
|
* Drop support for SDK level 9
|
||||||
|
* Start crop from support Fragment
|
||||||
|
* Fix max size
|
||||||
|
* Fix issue cropping images from Google Drive
|
||||||
|
* Optional circle crop guide
|
||||||
|
* Optional custom request code
|
||||||
|
* Translations: French, Korean, Chinese, Spanish, Japanese, Arabic, Portuguese, Indonesian, Russian
|
||||||
|
|
||||||
## 0.9.10
|
## 0.9.10
|
||||||
|
|
||||||
* Fix bug on some devices where image was displayed with 0 size
|
* Fix bug on some devices where image was displayed with 0 size
|
||||||
|
|
@ -5,5 +30,5 @@
|
||||||
## 0.9.9
|
## 0.9.9
|
||||||
|
|
||||||
* Downscale source images that are too big to load
|
* Downscale source images that are too big to load
|
||||||
|
* Optional always show crop handles
|
||||||
* Fix shading outside crop area on some API levels
|
* Fix shading outside crop area on some API levels
|
||||||
* Add option to always show crop handles
|
|
||||||
|
|
|
||||||
62
README.md
62
README.md
|
|
@ -1,52 +1,65 @@
|
||||||
> I guess people are just cropping out all the sadness
|
> I guess people are just cropping out all the sadness
|
||||||
|
|
||||||
An Android library project to provide a simple image cropping `Activity`, based on code from AOSP.
|
An Android library project that provides a simple image cropping `Activity`, based on code from AOSP.
|
||||||
|
|
||||||
[](https://travis-ci.org/jdamcd/android-crop)
|
[](https://travis-ci.org/jdamcd/android-crop)
|
||||||
|
[](http://search.maven.org/#artifactdetails%7Ccom.soundcloud.android%7Candroid-crop%7C1.0.1%7Caar.asc)
|
||||||
|
[](CHANGELOG.md)
|
||||||
|
|
||||||
## Goals
|
## Features
|
||||||
|
|
||||||
* Gradle build with AAR
|
* Gradle build & AAR
|
||||||
* Modern UI
|
* Modern UI
|
||||||
* Backwards compatible to Gingerbread
|
* Backwards compatible to SDK 10
|
||||||
* Simple builder for configuration
|
* Simple builder for configuration
|
||||||
* Example project
|
* Example project
|
||||||
* More tests, less unused complexity
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
First, declare `CropImageActivity` in your manifest file:
|
First, declare `CropImageActivity` in your manifest file:
|
||||||
|
|
||||||
`<activity android:name="com.soundcloud.android.crop.CropImageActivity" />`
|
```xml
|
||||||
|
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
|
||||||
|
```
|
||||||
|
|
||||||
#### Crop
|
#### Crop
|
||||||
|
|
||||||
`new Crop(inputUri).output(outputUri).asSquare().start(activity)`
|
```java
|
||||||
|
Crop.of(inputUri, outputUri).asSquare().start(activity)
|
||||||
|
```
|
||||||
|
|
||||||
Listen for the result of the crop (see example project if you want to do some error handling):
|
Listen for the result of the crop (see example project if you want to do some error handling):
|
||||||
|
|
||||||
@Override
|
```java
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
|
@Override
|
||||||
if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
|
||||||
doSomethingWithCroppedImage(outputUri);
|
if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) {
|
||||||
}
|
doSomethingWithCroppedImage(outputUri);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Some attributes are provided to customise the crop screen. See the example project [theme](https://github.com/jdamcd/android-crop/blob/master/example/src/main/res/values/theme.xml).
|
||||||
|
|
||||||
#### Pick
|
#### Pick
|
||||||
|
|
||||||
The library provides a utility method to start an image picker:
|
The library provides a utility method to start an image picker:
|
||||||
|
|
||||||
`Crop.pickImage(activity)`
|
```java
|
||||||
|
Crop.pickImage(activity)
|
||||||
|
```
|
||||||
|
|
||||||
#### Dependency
|
#### Dependency
|
||||||
|
|
||||||
The AAR is published on Maven Central:
|
The AAR is published on Maven Central:
|
||||||
|
|
||||||
`compile 'com.soundcloud.android:android-crop:0.9.10@aar'`
|
```groovy
|
||||||
|
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
|
||||||
|
```
|
||||||
|
|
||||||
#### Apps
|
#### Users
|
||||||
|
|
||||||
Apps that use this library include: [SoundCloud](https://play.google.com/store/apps/details?id=com.soundcloud.android), [Depop](https://play.google.com/store/apps/details?id=com.depop)
|
Apps that use this library include: [SoundCloud](https://play.google.com/store/apps/details?id=com.soundcloud.android), [Depop](https://play.google.com/store/apps/details?id=com.depop), [Polyvore](https://play.google.com/store/apps/details?id=com.polyvore), [TextSecure](https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms)
|
||||||
|
|
||||||
## How does it look?
|
## How does it look?
|
||||||
|
|
||||||
|
|
@ -56,5 +69,16 @@ Apps that use this library include: [SoundCloud](https://play.google.com/store/a
|
||||||
|
|
||||||
This project is based on the [AOSP](https://source.android.com) camera image cropper via [android-cropimage](https://github.com/lvillani/android-cropimage).
|
This project is based on the [AOSP](https://source.android.com) camera image cropper via [android-cropimage](https://github.com/lvillani/android-cropimage).
|
||||||
|
|
||||||
Copyright 2014 [SoundCloud](https://soundcloud.com)
|
Copyright 2015 SoundCloud
|
||||||
Apache License, Version 2.0
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:1.0.0'
|
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ apply plugin: 'com.android.application'
|
||||||
archivesBaseName = 'android-crop-example'
|
archivesBaseName = 'android-crop-example'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 21
|
compileSdkVersion 23
|
||||||
buildToolsVersion '21.1.2'
|
buildToolsVersion '23.0.1'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 9
|
minSdkVersion 10
|
||||||
targetSdkVersion 21
|
targetSdkVersion 22
|
||||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||||
versionName project.VERSION
|
versionName project.VERSION
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
package com.soundcloud.android.crop.example;
|
package com.soundcloud.android.crop.example;
|
||||||
|
|
||||||
|
import com.soundcloud.android.crop.Crop;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.soundcloud.android.crop.Crop;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class MainActivity extends Activity {
|
public class MainActivity extends Activity {
|
||||||
|
|
@ -51,8 +50,8 @@ public class MainActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginCrop(Uri source) {
|
private void beginCrop(Uri source) {
|
||||||
Uri outputUri = Uri.fromFile(new File(getCacheDir(), "cropped"));
|
Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
|
||||||
new Crop(source).output(outputUri).asSquare().start(this);
|
Crop.of(source, destination).asSquare().start(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCrop(int resultCode, Intent result) {
|
private void handleCrop(int resultCode, Intent result) {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
<style name="Widget.CropImageView" parent="android:Widget">
|
<style name="Widget.CropImageView" parent="android:Widget">
|
||||||
<item name="showThirds">true</item>
|
<item name="showThirds">true</item>
|
||||||
|
<item name="showCircle">false</item>
|
||||||
<item name="showHandles">always</item>
|
<item name="showHandles">always</item>
|
||||||
<item name="highlightColor">@color/highlight</item>
|
<item name="highlightColor">@color/highlight</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION=0.9.10
|
VERSION=1.0.1
|
||||||
VERSION_CODE=1
|
VERSION_CODE=1
|
||||||
|
|
||||||
signing.keyId=63A46540
|
signing.keyId=63A46540
|
||||||
|
|
@ -6,4 +6,4 @@ signing.secretKeyRingFile=
|
||||||
signing.password=
|
signing.password=
|
||||||
|
|
||||||
sonatypeUsername=jdamcd
|
sonatypeUsername=jdamcd
|
||||||
sonatypePassword=
|
sonatypePassword=
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ apply plugin: 'signing'
|
||||||
archivesBaseName = 'android-crop'
|
archivesBaseName = 'android-crop'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 21
|
compileSdkVersion 23
|
||||||
buildToolsVersion '21.1.1'
|
buildToolsVersion '23.0.1'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 9
|
minSdkVersion 10
|
||||||
targetSdkVersion 21
|
targetSdkVersion 22
|
||||||
|
|
||||||
testApplicationId 'com.soundcloud.android.crop.test'
|
testApplicationId 'com.soundcloud.android.crop.test'
|
||||||
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
|
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
|
||||||
|
|
@ -19,9 +19,10 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-annotations:21.0.0'
|
compile 'com.android.support:support-annotations:23.0.1'
|
||||||
|
compile 'com.android.support:support-v4:23.0.1'
|
||||||
androidTestCompile 'com.squareup:fest-android:1.0.7'
|
androidTestCompile 'com.squareup:fest-android:1.0.7'
|
||||||
androidTestCompile 'com.android.support:support-v4:21.0.0'
|
androidTestCompile 'com.android.support:support-v4:23.0.1'
|
||||||
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
|
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
|
||||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
|
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
package com.soundcloud.android.crop;
|
package com.soundcloud.android.crop;
|
||||||
|
|
||||||
|
import static org.fest.assertions.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.fest.assertions.api.ANDROID;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
|
|
||||||
import org.fest.assertions.api.ANDROID;
|
|
||||||
|
|
||||||
import static org.fest.assertions.api.Assertions.assertThat;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
public class CropBuilderTest extends BaseTestCase {
|
public class CropBuilderTest extends BaseTestCase {
|
||||||
|
|
||||||
private Activity activity;
|
private Activity activity;
|
||||||
|
|
@ -22,7 +22,7 @@ public class CropBuilderTest extends BaseTestCase {
|
||||||
activity = mock(Activity.class);
|
activity = mock(Activity.class);
|
||||||
when(activity.getPackageName()).thenReturn("com.example");
|
when(activity.getPackageName()).thenReturn("com.example");
|
||||||
|
|
||||||
builder = new Crop(Uri.parse("image:input"));
|
builder = Crop.of(Uri.parse("image:input"), Uri.parse("image:output"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInputUriSetAsData() {
|
public void testInputUriSetAsData() {
|
||||||
|
|
@ -30,8 +30,6 @@ public class CropBuilderTest extends BaseTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testOutputUriSetAsExtra() {
|
public void testOutputUriSetAsExtra() {
|
||||||
builder.output(Uri.parse("image:output"));
|
|
||||||
|
|
||||||
Intent intent = builder.getIntent(activity);
|
Intent intent = builder.getIntent(activity);
|
||||||
Uri output = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
|
Uri output = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest package="com.soundcloud.android.crop" />
|
||||||
package="com.soundcloud.android.crop">
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="true" />
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public class Crop {
|
||||||
public static final int REQUEST_PICK = 9162;
|
public static final int REQUEST_PICK = 9162;
|
||||||
public static final int RESULT_ERROR = 404;
|
public static final int RESULT_ERROR = 404;
|
||||||
|
|
||||||
static interface Extra {
|
interface Extra {
|
||||||
String ASPECT_X = "aspect_x";
|
String ASPECT_X = "aspect_x";
|
||||||
String ASPECT_Y = "aspect_y";
|
String ASPECT_Y = "aspect_y";
|
||||||
String MAX_X = "max_x";
|
String MAX_X = "max_x";
|
||||||
|
|
@ -31,23 +31,19 @@ public class Crop {
|
||||||
private Intent cropIntent;
|
private Intent cropIntent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a crop Intent builder with source image
|
* Create a crop Intent builder with source and destination image Uris
|
||||||
*
|
*
|
||||||
* @param source Source image URI
|
* @param source Uri for image to crop
|
||||||
|
* @param destination Uri for saving the cropped image
|
||||||
*/
|
*/
|
||||||
public Crop(Uri source) {
|
public static Crop of(Uri source, Uri destination) {
|
||||||
cropIntent = new Intent();
|
return new Crop(source, destination);
|
||||||
cropIntent.setData(source);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private Crop(Uri source, Uri destination) {
|
||||||
* Set output URI where the cropped image will be saved
|
cropIntent = new Intent();
|
||||||
*
|
cropIntent.setData(source);
|
||||||
* @param output Output image URI
|
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
|
||||||
*/
|
|
||||||
public Crop output(Uri output) {
|
|
||||||
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, output);
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,7 +70,7 @@ public class Crop {
|
||||||
/**
|
/**
|
||||||
* Set maximum crop size
|
* Set maximum crop size
|
||||||
*
|
*
|
||||||
* @param width Max width
|
* @param width Max width
|
||||||
* @param height Max height
|
* @param height Max height
|
||||||
*/
|
*/
|
||||||
public Crop withMaxSize(int width, int height) {
|
public Crop withMaxSize(int width, int height) {
|
||||||
|
|
@ -84,7 +80,7 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the crop Intent!
|
* Send the crop Intent from an Activity
|
||||||
*
|
*
|
||||||
* @param activity Activity to receive result
|
* @param activity Activity to receive result
|
||||||
*/
|
*/
|
||||||
|
|
@ -93,9 +89,9 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the crop Intent with a custom requestCode
|
* Send the crop Intent from an Activity with a custom request code
|
||||||
*
|
*
|
||||||
* @param activity Activity to receive result
|
* @param activity Activity to receive result
|
||||||
* @param requestCode requestCode for result
|
* @param requestCode requestCode for result
|
||||||
*/
|
*/
|
||||||
public void start(Activity activity, int requestCode) {
|
public void start(Activity activity, int requestCode) {
|
||||||
|
|
@ -103,21 +99,30 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the crop Intent!
|
* Send the crop Intent from a Fragment
|
||||||
*
|
*
|
||||||
* @param context Context
|
* @param context Context
|
||||||
* @param fragment Fragment to receive result
|
* @param fragment Fragment to receive result
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
|
||||||
public void start(Context context, Fragment fragment) {
|
public void start(Context context, Fragment fragment) {
|
||||||
start(context, fragment, REQUEST_CROP);
|
start(context, fragment, REQUEST_CROP);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the crop Intent with a custom requestCode
|
* Send the crop Intent from a support library Fragment
|
||||||
*
|
*
|
||||||
* @param context Context
|
* @param context Context
|
||||||
* @param fragment Fragment to receive result
|
* @param fragment Fragment to receive result
|
||||||
|
*/
|
||||||
|
public void start(Context context, android.support.v4.app.Fragment fragment) {
|
||||||
|
start(context, fragment, REQUEST_CROP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the crop Intent with a custom request code
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param fragment Fragment to receive result
|
||||||
* @param requestCode requestCode for result
|
* @param requestCode requestCode for result
|
||||||
*/
|
*/
|
||||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
|
@ -125,6 +130,17 @@ public class Crop {
|
||||||
fragment.startActivityForResult(getIntent(context), requestCode);
|
fragment.startActivityForResult(getIntent(context), requestCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the crop Intent with a custom request code
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param fragment Fragment to receive result
|
||||||
|
* @param requestCode requestCode for result
|
||||||
|
*/
|
||||||
|
public void start(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
|
||||||
|
fragment.startActivityForResult(getIntent(context), requestCode);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Intent to start crop Activity
|
* Get Intent to start crop Activity
|
||||||
*
|
*
|
||||||
|
|
@ -156,27 +172,85 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to start an image picker
|
* Pick image from an Activity
|
||||||
*
|
*
|
||||||
* @param activity Activity that will receive result
|
* @param activity Activity to receive result
|
||||||
*/
|
*/
|
||||||
public static void pickImage(Activity activity) {
|
public static void pickImage(Activity activity) {
|
||||||
pickImage(activity, REQUEST_PICK);
|
pickImage(activity, REQUEST_PICK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to start an image picker with request code
|
* Pick image from a Fragment
|
||||||
*
|
*
|
||||||
* @param activity Activity that will receive result
|
* @param context Context
|
||||||
|
* @param fragment Fragment to receive result
|
||||||
|
*/
|
||||||
|
public static void pickImage(Context context, Fragment fragment) {
|
||||||
|
pickImage(context, fragment, REQUEST_PICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick image from a support library Fragment
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param fragment Fragment to receive result
|
||||||
|
*/
|
||||||
|
public static void pickImage(Context context, android.support.v4.app.Fragment fragment) {
|
||||||
|
pickImage(context, fragment, REQUEST_PICK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick image from an Activity with a custom request code
|
||||||
|
*
|
||||||
|
* @param activity Activity to receive result
|
||||||
* @param requestCode requestCode for result
|
* @param requestCode requestCode for result
|
||||||
*/
|
*/
|
||||||
public static void pickImage(Activity activity, int requestCode) {
|
public static void pickImage(Activity activity, int requestCode) {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
|
|
||||||
try {
|
try {
|
||||||
activity.startActivityForResult(intent, requestCode);
|
activity.startActivityForResult(getImagePicker(), requestCode);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
Toast.makeText(activity, R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
|
showImagePickerError(activity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick image from a Fragment with a custom request code
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param fragment Fragment to receive result
|
||||||
|
* @param requestCode requestCode for result
|
||||||
|
*/
|
||||||
|
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||||
|
public static void pickImage(Context context, Fragment fragment, int requestCode) {
|
||||||
|
try {
|
||||||
|
fragment.startActivityForResult(getImagePicker(), requestCode);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
showImagePickerError(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick image from a support library Fragment with a custom request code
|
||||||
|
*
|
||||||
|
* @param context Context
|
||||||
|
* @param fragment Fragment to receive result
|
||||||
|
* @param requestCode requestCode for result
|
||||||
|
*/
|
||||||
|
public static void pickImage(Context context, android.support.v4.app.Fragment fragment, int requestCode) {
|
||||||
|
try {
|
||||||
|
fragment.startActivityForResult(getImagePicker(), requestCode);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
showImagePickerError(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent getImagePicker() {
|
||||||
|
return new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showImagePickerError(Context context) {
|
||||||
|
Toast.makeText(context, R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.BitmapRegionDecoder;
|
import android.graphics.BitmapRegionDecoder;
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.graphics.Matrix;
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
|
|
@ -33,6 +32,7 @@ import android.os.Handler;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -44,7 +44,6 @@ import java.util.concurrent.CountDownLatch;
|
||||||
*/
|
*/
|
||||||
public class CropImageActivity extends MonitoredActivity {
|
public class CropImageActivity extends MonitoredActivity {
|
||||||
|
|
||||||
private static final boolean IN_MEMORY_CROP = Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD_MR1;
|
|
||||||
private static final int SIZE_DEFAULT = 2048;
|
private static final int SIZE_DEFAULT = 2048;
|
||||||
private static final int SIZE_LIMIT = 4096;
|
private static final int SIZE_LIMIT = 4096;
|
||||||
|
|
||||||
|
|
@ -71,11 +70,10 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
setupWindowFlags();
|
||||||
setContentView(R.layout.crop__activity_crop);
|
setupViews();
|
||||||
initViews();
|
|
||||||
|
|
||||||
setupFromIntent();
|
loadInput();
|
||||||
if (rotateBitmap == null) {
|
if (rotateBitmap == null) {
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
|
|
@ -83,7 +81,17 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
startCrop();
|
startCrop();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initViews() {
|
@TargetApi(Build.VERSION_CODES.KITKAT)
|
||||||
|
private void setupWindowFlags() {
|
||||||
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||||
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupViews() {
|
||||||
|
setContentView(R.layout.crop__activity_crop);
|
||||||
|
|
||||||
imageView = (CropImageView) findViewById(R.id.crop_image);
|
imageView = (CropImageView) findViewById(R.id.crop_image);
|
||||||
imageView.context = this;
|
imageView.context = this;
|
||||||
imageView.setRecycler(new ImageViewTouchBase.Recycler() {
|
imageView.setRecycler(new ImageViewTouchBase.Recycler() {
|
||||||
|
|
@ -108,7 +116,7 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFromIntent() {
|
private void loadInput() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Bundle extras = intent.getExtras();
|
Bundle extras = intent.getExtras();
|
||||||
|
|
||||||
|
|
@ -190,7 +198,7 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (imageView.getScale() == 1F) {
|
if (imageView.getScale() == 1F) {
|
||||||
imageView.center(true, true);
|
imageView.center();
|
||||||
}
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
|
@ -254,23 +262,19 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO
|
|
||||||
* This should use the decode/crop/encode single step API so that the whole
|
|
||||||
* (possibly large) Bitmap doesn't need to be read into memory
|
|
||||||
*/
|
|
||||||
private void onSaveClicked() {
|
private void onSaveClicked() {
|
||||||
if (cropView == null || isSaving) {
|
if (cropView == null || isSaving) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isSaving = true;
|
isSaving = true;
|
||||||
|
|
||||||
Bitmap croppedImage = null;
|
Bitmap croppedImage;
|
||||||
Rect r = cropView.getScaledCropRect(sampleSize);
|
Rect r = cropView.getScaledCropRect(sampleSize);
|
||||||
int width = r.width();
|
int width = r.width();
|
||||||
int height = r.height();
|
int height = r.height();
|
||||||
|
|
||||||
int outWidth = width, outHeight = height;
|
int outWidth = width;
|
||||||
|
int outHeight = height;
|
||||||
if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
|
if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
|
||||||
float ratio = (float) width / (float) height;
|
float ratio = (float) width / (float) height;
|
||||||
if ((float) maxX / (float) maxY > ratio) {
|
if ((float) maxX / (float) maxY > ratio) {
|
||||||
|
|
@ -282,27 +286,18 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IN_MEMORY_CROP && rotateBitmap != null) {
|
try {
|
||||||
croppedImage = inMemoryCrop(rotateBitmap, r, width, height, outWidth, outHeight);
|
croppedImage = decodeRegionCrop(r, outWidth, outHeight);
|
||||||
if (croppedImage != null) {
|
} catch (IllegalArgumentException e) {
|
||||||
imageView.setImageBitmapResetBase(croppedImage, true);
|
setResultException(e);
|
||||||
imageView.center(true, true);
|
finish();
|
||||||
imageView.highlightViews.clear();
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
croppedImage = decodeRegionCrop(r);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
setResultException(e);
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (croppedImage != null) {
|
if (croppedImage != null) {
|
||||||
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
|
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
|
||||||
imageView.center(true, true);
|
imageView.center();
|
||||||
imageView.highlightViews.clear();
|
imageView.highlightViews.clear();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
saveImage(croppedImage);
|
saveImage(croppedImage);
|
||||||
}
|
}
|
||||||
|
|
@ -322,8 +317,7 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(10)
|
private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
|
||||||
private Bitmap decodeRegionCrop(Rect rect) {
|
|
||||||
// Release memory now
|
// Release memory now
|
||||||
clearImageView();
|
clearImageView();
|
||||||
|
|
||||||
|
|
@ -350,6 +344,11 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
|
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
|
||||||
|
if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
|
||||||
|
Matrix matrix = new Matrix();
|
||||||
|
matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
|
||||||
|
croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
|
||||||
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
// Rethrow with some extra information
|
// Rethrow with some extra information
|
||||||
throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
|
throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
|
||||||
|
|
@ -358,7 +357,7 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e("Error cropping image: " + e.getMessage(), e);
|
Log.e("Error cropping image: " + e.getMessage(), e);
|
||||||
finish();
|
setResultException(e);
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
Log.e("OOM cropping image: " + e.getMessage(), e);
|
Log.e("OOM cropping image: " + e.getMessage(), e);
|
||||||
setResultException(e);
|
setResultException(e);
|
||||||
|
|
@ -368,33 +367,6 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
return croppedImage;
|
return croppedImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap inMemoryCrop(RotateBitmap rotateBitmap, Rect r, int width, int height, int outWidth, int outHeight) {
|
|
||||||
// In-memory crop means potential OOM errors,
|
|
||||||
// but we have no choice as we can't selectively decode a bitmap with this API level
|
|
||||||
System.gc();
|
|
||||||
|
|
||||||
Bitmap croppedImage = null;
|
|
||||||
try {
|
|
||||||
croppedImage = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.RGB_565);
|
|
||||||
|
|
||||||
Canvas canvas = new Canvas(croppedImage);
|
|
||||||
RectF dstRect = new RectF(0, 0, width, height);
|
|
||||||
|
|
||||||
Matrix m = new Matrix();
|
|
||||||
m.setRectToRect(new RectF(r), dstRect, Matrix.ScaleToFit.FILL);
|
|
||||||
m.preConcat(rotateBitmap.getRotateMatrix());
|
|
||||||
canvas.drawBitmap(rotateBitmap.getBitmap(), m, null);
|
|
||||||
} catch (OutOfMemoryError e) {
|
|
||||||
Log.e("OOM cropping image: " + e.getMessage(), e);
|
|
||||||
setResultException(e);
|
|
||||||
System.gc();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release Bitmap memory as soon as possible
|
|
||||||
clearImageView();
|
|
||||||
return croppedImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearImageView() {
|
private void clearImageView() {
|
||||||
imageView.clear();
|
imageView.clear();
|
||||||
if (rotateBitmap != null) {
|
if (rotateBitmap != null) {
|
||||||
|
|
@ -418,13 +390,10 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
CropUtil.closeSilently(outputStream);
|
CropUtil.closeSilently(outputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IN_MEMORY_CROP) {
|
CropUtil.copyExifRotation(
|
||||||
// In-memory crop negates the rotation
|
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
|
||||||
CropUtil.copyExifRotation(
|
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
|
||||||
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
|
);
|
||||||
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setResultUri(saveUri);
|
setResultUri(saveUri);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.soundcloud.android.crop;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
|
@ -17,18 +18,16 @@ public class CropImageView extends ImageViewTouchBase {
|
||||||
private float lastX;
|
private float lastX;
|
||||||
private float lastY;
|
private float lastY;
|
||||||
private int motionEdge;
|
private int motionEdge;
|
||||||
|
private int validPointerId;
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
public CropImageView(Context context) {
|
public CropImageView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
public CropImageView(Context context, AttributeSet attrs) {
|
public CropImageView(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
public CropImageView(Context context, AttributeSet attrs, int defStyle) {
|
public CropImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +84,7 @@ public class CropImageView extends ImageViewTouchBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||||
CropImageActivity cropImageActivity = (CropImageActivity) context;
|
CropImageActivity cropImageActivity = (CropImageActivity) context;
|
||||||
if (cropImageActivity.isSaving()) {
|
if (cropImageActivity.isSaving()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -100,6 +99,8 @@ public class CropImageView extends ImageViewTouchBase {
|
||||||
motionHighlightView = hv;
|
motionHighlightView = hv;
|
||||||
lastX = event.getX();
|
lastX = event.getX();
|
||||||
lastY = event.getY();
|
lastY = event.getY();
|
||||||
|
// Prevent multiple touches from interfering with crop area re-sizing
|
||||||
|
validPointerId = event.getPointerId(event.getActionIndex());
|
||||||
motionHighlightView.setMode((edge == HighlightView.MOVE)
|
motionHighlightView.setMode((edge == HighlightView.MOVE)
|
||||||
? HighlightView.ModifyMode.Move
|
? HighlightView.ModifyMode.Move
|
||||||
: HighlightView.ModifyMode.Grow);
|
: HighlightView.ModifyMode.Grow);
|
||||||
|
|
@ -113,29 +114,20 @@ public class CropImageView extends ImageViewTouchBase {
|
||||||
motionHighlightView.setMode(HighlightView.ModifyMode.None);
|
motionHighlightView.setMode(HighlightView.ModifyMode.None);
|
||||||
}
|
}
|
||||||
motionHighlightView = null;
|
motionHighlightView = null;
|
||||||
|
center();
|
||||||
break;
|
break;
|
||||||
case MotionEvent.ACTION_MOVE:
|
case MotionEvent.ACTION_MOVE:
|
||||||
if (motionHighlightView != null) {
|
if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {
|
||||||
motionHighlightView.handleMotion(motionEdge, event.getX()
|
motionHighlightView.handleMotion(motionEdge, event.getX()
|
||||||
- lastX, event.getY() - lastY);
|
- lastX, event.getY() - lastY);
|
||||||
lastX = event.getX();
|
lastX = event.getX();
|
||||||
lastY = event.getY();
|
lastY = event.getY();
|
||||||
ensureVisible(motionHighlightView);
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.getAction()) {
|
// If we're not zoomed then there's no point in even allowing the user to move the image around.
|
||||||
case MotionEvent.ACTION_UP:
|
// This call to center puts it back to the normalized location.
|
||||||
center(true, true);
|
|
||||||
break;
|
|
||||||
case MotionEvent.ACTION_MOVE:
|
|
||||||
// if we're not zoomed then there's no point in even allowing
|
|
||||||
// the user to move the image around. This call to center puts
|
|
||||||
// it back to the normalized location (with false meaning don't
|
|
||||||
// animate).
|
|
||||||
if (getScale() == 1F) {
|
if (getScale() == 1F) {
|
||||||
center(true, true);
|
center();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -189,10 +181,10 @@ public class CropImageView extends ImageViewTouchBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(@NonNull Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
for (HighlightView mHighlightView : highlightViews) {
|
for (HighlightView highlightView : highlightViews) {
|
||||||
mHighlightView.draw(canvas);
|
highlightView.draw(canvas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ class CropUtil {
|
||||||
|
|
||||||
public static void startBackgroundJob(MonitoredActivity activity,
|
public static void startBackgroundJob(MonitoredActivity activity,
|
||||||
String title, String message, Runnable job, Handler handler) {
|
String title, String message, Runnable job, Handler handler) {
|
||||||
// Make the progress dialog uncancelable, so that we can gurantee
|
// Make the progress dialog uncancelable, so that we can guarantee
|
||||||
// the thread will be done before the activity getting destroyed
|
// the thread will be done before the activity getting destroyed
|
||||||
ProgressDialog dialog = ProgressDialog.show(
|
ProgressDialog dialog = ProgressDialog.show(
|
||||||
activity, title, message, true, false);
|
activity, title, message, true, false);
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ class HighlightView {
|
||||||
|
|
||||||
private View viewContext; // View displaying image
|
private View viewContext; // View displaying image
|
||||||
private boolean showThirds;
|
private boolean showThirds;
|
||||||
|
private boolean showCircle;
|
||||||
private int highlightColor;
|
private int highlightColor;
|
||||||
|
|
||||||
private ModifyMode modifyMode = ModifyMode.None;
|
private ModifyMode modifyMode = ModifyMode.None;
|
||||||
|
|
@ -87,6 +88,7 @@ class HighlightView {
|
||||||
TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
|
TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
|
||||||
try {
|
try {
|
||||||
showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
|
showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
|
||||||
|
showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);
|
||||||
highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
|
highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
|
||||||
DEFAULT_HIGHLIGHT_COLOR);
|
DEFAULT_HIGHLIGHT_COLOR);
|
||||||
handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
|
handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
|
||||||
|
|
@ -150,6 +152,10 @@ class HighlightView {
|
||||||
drawThirds(canvas);
|
drawThirds(canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCircle) {
|
||||||
|
drawCircle(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
if (handleMode == HandleMode.Always ||
|
if (handleMode == HandleMode.Always ||
|
||||||
(handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
|
(handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
|
||||||
drawHandles(canvas);
|
drawHandles(canvas);
|
||||||
|
|
@ -209,6 +215,11 @@ class HighlightView {
|
||||||
drawRect.right, drawRect.top + yThird * 2, outlinePaint);
|
drawRect.right, drawRect.top + yThird * 2, outlinePaint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void drawCircle(Canvas canvas) {
|
||||||
|
outlinePaint.setStrokeWidth(1);
|
||||||
|
canvas.drawOval(new RectF(drawRect), outlinePaint);
|
||||||
|
}
|
||||||
|
|
||||||
public void setMode(ModifyMode mode) {
|
public void setMode(ModifyMode mode) {
|
||||||
if (mode != modifyMode) {
|
if (mode != modifyMode) {
|
||||||
modifyMode = mode;
|
modifyMode = mode;
|
||||||
|
|
|
||||||
|
|
@ -192,12 +192,10 @@ abstract class ImageViewTouchBase extends ImageView {
|
||||||
maxZoom = calculateMaxZoom();
|
maxZoom = calculateMaxZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center as much as possible in one or both axis. Centering is
|
// Center as much as possible in one or both axis. Centering is defined as follows:
|
||||||
// defined as follows: if the image is scaled down below the
|
// * If the image is scaled down below the view's dimensions then center it.
|
||||||
// view's dimensions then center it (literally). If the image
|
// * If the image is scaled larger than the view and is translated out of view then translate it back into view.
|
||||||
// is scaled larger than the view and is translated out of view
|
protected void center() {
|
||||||
// then translate it back into view (i.e. eliminate black bars).
|
|
||||||
protected void center(boolean horizontal, boolean vertical) {
|
|
||||||
final Bitmap bitmap = bitmapDisplayed.getBitmap();
|
final Bitmap bitmap = bitmapDisplayed.getBitmap();
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -212,32 +210,37 @@ abstract class ImageViewTouchBase extends ImageView {
|
||||||
|
|
||||||
float deltaX = 0, deltaY = 0;
|
float deltaX = 0, deltaY = 0;
|
||||||
|
|
||||||
if (vertical) {
|
deltaY = centerVertical(rect, height, deltaY);
|
||||||
int viewHeight = getHeight();
|
deltaX = centerHorizontal(rect, width, deltaX);
|
||||||
if (height < viewHeight) {
|
|
||||||
deltaY = (viewHeight - height) / 2 - rect.top;
|
|
||||||
} else if (rect.top > 0) {
|
|
||||||
deltaY = -rect.top;
|
|
||||||
} else if (rect.bottom < viewHeight) {
|
|
||||||
deltaY = getHeight() - rect.bottom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (horizontal) {
|
|
||||||
int viewWidth = getWidth();
|
|
||||||
if (width < viewWidth) {
|
|
||||||
deltaX = (viewWidth - width) / 2 - rect.left;
|
|
||||||
} else if (rect.left > 0) {
|
|
||||||
deltaX = -rect.left;
|
|
||||||
} else if (rect.right < viewWidth) {
|
|
||||||
deltaX = viewWidth - rect.right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
postTranslate(deltaX, deltaY);
|
postTranslate(deltaX, deltaY);
|
||||||
setImageMatrix(getImageViewMatrix());
|
setImageMatrix(getImageViewMatrix());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float centerVertical(RectF rect, float height, float deltaY) {
|
||||||
|
int viewHeight = getHeight();
|
||||||
|
if (height < viewHeight) {
|
||||||
|
deltaY = (viewHeight - height) / 2 - rect.top;
|
||||||
|
} else if (rect.top > 0) {
|
||||||
|
deltaY = -rect.top;
|
||||||
|
} else if (rect.bottom < viewHeight) {
|
||||||
|
deltaY = getHeight() - rect.bottom;
|
||||||
|
}
|
||||||
|
return deltaY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float centerHorizontal(RectF rect, float width, float deltaX) {
|
||||||
|
int viewWidth = getWidth();
|
||||||
|
if (width < viewWidth) {
|
||||||
|
deltaX = (viewWidth - width) / 2 - rect.left;
|
||||||
|
} else if (rect.left > 0) {
|
||||||
|
deltaX = -rect.left;
|
||||||
|
} else if (rect.right < viewWidth) {
|
||||||
|
deltaX = viewWidth - rect.right;
|
||||||
|
}
|
||||||
|
return deltaX;
|
||||||
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
setScaleType(ImageView.ScaleType.MATRIX);
|
setScaleType(ImageView.ScaleType.MATRIX);
|
||||||
}
|
}
|
||||||
|
|
@ -313,7 +316,7 @@ abstract class ImageViewTouchBase extends ImageView {
|
||||||
|
|
||||||
suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
|
suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
|
||||||
setImageMatrix(getImageViewMatrix());
|
setImageMatrix(getImageViewMatrix());
|
||||||
center(true, true);
|
center();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void zoomTo(final float scale, final float centerX,
|
protected void zoomTo(final float scale, final float centerX,
|
||||||
|
|
@ -383,7 +386,7 @@ abstract class ImageViewTouchBase extends ImageView {
|
||||||
suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
|
suppMatrix.postScale(1F / rate, 1F / rate, cx, cy);
|
||||||
}
|
}
|
||||||
setImageMatrix(getImageViewMatrix());
|
setImageMatrix(getImageViewMatrix());
|
||||||
center(true, true);
|
center();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void postTranslate(float dx, float dy) {
|
protected void postTranslate(float dx, float dy) {
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ class Log {
|
||||||
|
|
||||||
private static final String TAG = "android-crop";
|
private static final String TAG = "android-crop";
|
||||||
|
|
||||||
public static final void e(String msg) {
|
public static void e(String msg) {
|
||||||
android.util.Log.e(TAG, msg);
|
android.util.Log.e(TAG, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final void e(String msg, Throwable e) {
|
public static void e(String msg, Throwable e) {
|
||||||
android.util.Log.e(TAG, msg, e);
|
android.util.Log.e(TAG, msg, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">جارى حفظ الصورة …</string>
|
||||||
|
<string name="crop__wait">رجاء الأنتظار …</string>
|
||||||
|
<string name="crop__pick_error">الصورة غير متاحة</string>
|
||||||
|
|
||||||
|
<string name="crop__done">تم</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">الغاء</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Guardant imatge…</string>
|
||||||
|
<string name="crop__wait">Si us plau esperi…</string>
|
||||||
|
<string name="crop__pick_error">No hi ha imatges disponibles</string>
|
||||||
|
|
||||||
|
<string name="crop__done">ACCEPTAR</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">CANCEL·LAR</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Bild speichern…</string>
|
||||||
|
<string name="crop__wait">Bitte warten…</string>
|
||||||
|
<string name="crop__pick_error">Keine Bildquellen verfügbar</string>
|
||||||
|
|
||||||
|
<string name="crop__done">übernehmen</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">abbrechen</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<string name="crop__saving">Guardando imagen…</string>
|
<string name="crop__saving">Guardando imagen…</string>
|
||||||
<string name="crop__wait">Por favor espere…</string>
|
<string name="crop__wait">Por favor espere…</string>
|
||||||
<string name="crop__pick_error">No hay imagenes disponibles</string>
|
<string name="crop__pick_error">No hay imágenes disponibles</string>
|
||||||
|
|
||||||
<string name="crop__done">ACEPTAR</string>
|
<string name="crop__done">ACEPTAR</string>
|
||||||
<string name="crop__cancel" tools:ignore="ButtonCase">CANCELAR</string>
|
<string name="crop__cancel" tools:ignore="ButtonCase">CANCELAR</string>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Enregistrement de l\'image…</string>
|
||||||
|
<string name="crop__wait">Veuillez patienter…</string>
|
||||||
|
<string name="crop__pick_error">Aucune image disponible</string>
|
||||||
|
|
||||||
|
<string name="crop__done">ACCEPTER</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">ANNULER</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Menyimpan gambar…</string>
|
||||||
|
<string name="crop__wait">Silakan tunggu…</string>
|
||||||
|
<string name="crop__pick_error">Tidak ada sumber gambar yang tersedia</string>
|
||||||
|
|
||||||
|
<string name="crop__done">SELESAI</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">BATAL</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Salvataggio immagine…</string>
|
||||||
|
<string name="crop__wait">Attendere prego…</string>
|
||||||
|
<string name="crop__pick_error">Nessuna immagine disponibile</string>
|
||||||
|
|
||||||
|
<string name="crop__done">ACCETTA</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">ANNULLA</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">사진을 저장중입니다…</string>
|
||||||
|
<string name="crop__wait">잠시만 기다려주세요…</string>
|
||||||
|
<string name="crop__pick_error">이미지가 존재하지 않습니다.</string>
|
||||||
|
|
||||||
|
<string name="crop__done">확인</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">취소</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Salvando imagem…</string>
|
||||||
|
<string name="crop__wait">Por favor, aguarde…</string>
|
||||||
|
<string name="crop__pick_error">Sem fontes de imagem disponíveis</string>
|
||||||
|
|
||||||
|
<string name="crop__done">FINALIZADO</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">CANCELAR</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Изображение сохраняется…</string>
|
||||||
|
<string name="crop__wait">Пожалуйста, подождите…</string>
|
||||||
|
<string name="crop__pick_error">Нет доступных изображений</string>
|
||||||
|
|
||||||
|
<string name="crop__done">ГОТОВО</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">ОТМЕНА</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Sparar bild…</string>
|
||||||
|
<string name="crop__wait">Var god vänta…</string>
|
||||||
|
<string name="crop__pick_error">Inga bildkällor tillgängliga</string>
|
||||||
|
|
||||||
|
<string name="crop__done">KLAR</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">AVBRYT</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">Fotoğraf kaydediliyor…</string>
|
||||||
|
<string name="crop__wait">Lütfen bekleyin…</string>
|
||||||
|
<string name="crop__pick_error">Fotoğraf bulunamadı</string>
|
||||||
|
|
||||||
|
<string name="crop__done">TAMAM</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">ÇIKIŞ</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
<string name="crop__saving">正在保存照片…</string>
|
<string name="crop__saving">正在保存照片…</string>
|
||||||
<string name="crop__wait">请等待…</string>
|
<string name="crop__wait">请等待…</string>
|
||||||
<string name="crop__pick_error">无效的图片</string>
|
<string name="crop__pick_error">无效的图片</string>
|
||||||
|
|
||||||
<string name="crop__done">完成</string>
|
<string name="crop__done">完成</string>
|
||||||
<string name="crop__cancel" tools:ignore="ButtonCase">取消</string>
|
<string name="crop__cancel" tools:ignore="ButtonCase">取消</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<string name="crop__saving">正在儲存相片…</string>
|
||||||
|
<string name="crop__wait">請稍候…</string>
|
||||||
|
<string name="crop__pick_error">沒有可用的圖片來源</string>
|
||||||
|
|
||||||
|
<string name="crop__done">完成</string>
|
||||||
|
<string name="crop__cancel" tools:ignore="ButtonCase">取消</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<declare-styleable name="CropImageView">
|
<declare-styleable name="CropImageView">
|
||||||
<attr name="highlightColor" format="reference|color" />
|
<attr name="highlightColor" format="reference|color" />
|
||||||
<attr name="showThirds" format="boolean" />
|
<attr name="showThirds" format="boolean" />
|
||||||
|
<attr name="showCircle" format="boolean" />
|
||||||
<attr name="showHandles">
|
<attr name="showHandles">
|
||||||
<enum name="changing" value="0" />
|
<enum name="changing" value="0" />
|
||||||
<enum name="always" value="1" />
|
<enum name="always" value="1" />
|
||||||
|
|
|
||||||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 299 KiB |
Loading…
Reference in New Issue