Compare commits
1 Commits
master
...
exif-same-
| Author | SHA1 | Date |
|---|---|---|
|
|
bd544eb7d2 |
18
.travis.yml
18
.travis.yml
|
|
@ -1,13 +1,21 @@
|
||||||
language: android
|
language: android
|
||||||
sudo: false
|
|
||||||
|
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- build-tools-23.0.1
|
- build-tools-21.1.1
|
||||||
- android-23
|
- android-21
|
||||||
- extra-android-support
|
- extra-android-support
|
||||||
- extra-android-m2repository
|
- sys-img-armeabi-v7a-android-21
|
||||||
|
|
||||||
|
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 clean build
|
- ./gradlew :lib:connectedAndroidTest
|
||||||
|
|
||||||
27
CHANGELOG.md
27
CHANGELOG.md
|
|
@ -1,28 +1,3 @@
|
||||||
## 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
|
||||||
|
|
@ -30,5 +5,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,65 +1,52 @@
|
||||||
> I guess people are just cropping out all the sadness
|
> I guess people are just cropping out all the sadness
|
||||||
|
|
||||||
An Android library project that provides a simple image cropping `Activity`, based on code from AOSP.
|
An Android library project to provide 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)
|
|
||||||
|
|
||||||
## Features
|
## Goals
|
||||||
|
|
||||||
* Gradle build & AAR
|
* Gradle build with AAR
|
||||||
* Modern UI
|
* Modern UI
|
||||||
* Backwards compatible to SDK 10
|
* Backwards compatible to Gingerbread
|
||||||
* 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:
|
||||||
|
|
||||||
```xml
|
`<activity android:name="com.soundcloud.android.crop.CropImageActivity" />`
|
||||||
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Crop
|
#### Crop
|
||||||
|
|
||||||
```java
|
`new Crop(inputUri).output(outputUri).asSquare().start(activity)`
|
||||||
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):
|
||||||
|
|
||||||
```java
|
@Override
|
||||||
@Override
|
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
|
if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) {
|
||||||
if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) {
|
doSomethingWithCroppedImage(outputUri);
|
||||||
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:
|
||||||
|
|
||||||
```java
|
`Crop.pickImage(activity)`
|
||||||
Crop.pickImage(activity)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Dependency
|
#### Dependency
|
||||||
|
|
||||||
The AAR is published on Maven Central:
|
The AAR is published on Maven Central:
|
||||||
|
|
||||||
```groovy
|
`compile 'com.soundcloud.android:android-crop:0.9.10@aar'`
|
||||||
compile 'com.soundcloud.android:android-crop:1.0.1@aar'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Users
|
#### Apps
|
||||||
|
|
||||||
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)
|
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)
|
||||||
|
|
||||||
## How does it look?
|
## How does it look?
|
||||||
|
|
||||||
|
|
@ -69,16 +56,5 @@ 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 2015 SoundCloud
|
Copyright 2014 [SoundCloud](https://soundcloud.com)
|
||||||
|
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.2.3'
|
classpath 'com.android.tools.build:gradle:1.0.0'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,12 @@ apply plugin: 'com.android.application'
|
||||||
archivesBaseName = 'android-crop-example'
|
archivesBaseName = 'android-crop-example'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 23
|
compileSdkVersion 21
|
||||||
buildToolsVersion '23.0.1'
|
buildToolsVersion '21.1.2'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 10
|
minSdkVersion 9
|
||||||
targetSdkVersion 22
|
targetSdkVersion 21
|
||||||
versionCode Integer.parseInt(project.VERSION_CODE)
|
versionCode Integer.parseInt(project.VERSION_CODE)
|
||||||
versionName project.VERSION
|
versionName project.VERSION
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
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 {
|
||||||
|
|
@ -50,8 +51,8 @@ public class MainActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginCrop(Uri source) {
|
private void beginCrop(Uri source) {
|
||||||
Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
|
Uri outputUri = Uri.fromFile(new File(getCacheDir(), "cropped"));
|
||||||
Crop.of(source, destination).asSquare().start(this);
|
new Crop(source).output(outputUri).asSquare().start(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCrop(int resultCode, Intent result) {
|
private void handleCrop(int resultCode, Intent result) {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
<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=1.0.1
|
VERSION=0.9.10
|
||||||
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 23
|
compileSdkVersion 21
|
||||||
buildToolsVersion '23.0.1'
|
buildToolsVersion '21.1.1'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 10
|
minSdkVersion 9
|
||||||
targetSdkVersion 22
|
targetSdkVersion 21
|
||||||
|
|
||||||
testApplicationId 'com.soundcloud.android.crop.test'
|
testApplicationId 'com.soundcloud.android.crop.test'
|
||||||
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
|
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
|
||||||
|
|
@ -19,10 +19,9 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'com.android.support:support-annotations:23.0.1'
|
compile 'com.android.support:support-annotations:21.0.0'
|
||||||
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:23.0.1'
|
androidTestCompile 'com.android.support:support-v4:21.0.0'
|
||||||
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 = Crop.of(Uri.parse("image:input"), Uri.parse("image:output"));
|
builder = new Crop(Uri.parse("image:input"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInputUriSetAsData() {
|
public void testInputUriSetAsData() {
|
||||||
|
|
@ -30,6 +30,8 @@ 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 +1,7 @@
|
||||||
<manifest package="com.soundcloud.android.crop" />
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
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;
|
||||||
|
|
||||||
interface Extra {
|
static 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,19 +31,23 @@ public class Crop {
|
||||||
private Intent cropIntent;
|
private Intent cropIntent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a crop Intent builder with source and destination image Uris
|
* Create a crop Intent builder with source image
|
||||||
*
|
*
|
||||||
* @param source Uri for image to crop
|
* @param source Source image URI
|
||||||
* @param destination Uri for saving the cropped image
|
|
||||||
*/
|
*/
|
||||||
public static Crop of(Uri source, Uri destination) {
|
public Crop(Uri source) {
|
||||||
return new Crop(source, destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Crop(Uri source, Uri destination) {
|
|
||||||
cropIntent = new Intent();
|
cropIntent = new Intent();
|
||||||
cropIntent.setData(source);
|
cropIntent.setData(source);
|
||||||
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set output URI where the cropped image will be saved
|
||||||
|
*
|
||||||
|
* @param output Output image URI
|
||||||
|
*/
|
||||||
|
public Crop output(Uri output) {
|
||||||
|
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, output);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -70,7 +74,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) {
|
||||||
|
|
@ -80,7 +84,7 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the crop Intent from an Activity
|
* Send the crop Intent!
|
||||||
*
|
*
|
||||||
* @param activity Activity to receive result
|
* @param activity Activity to receive result
|
||||||
*/
|
*/
|
||||||
|
|
@ -89,9 +93,9 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the crop Intent from an Activity with a custom request code
|
* Send the crop Intent with a custom requestCode
|
||||||
*
|
*
|
||||||
* @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) {
|
||||||
|
|
@ -99,30 +103,21 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the crop Intent from a Fragment
|
* Send the crop Intent!
|
||||||
*
|
*
|
||||||
* @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 from a support library Fragment
|
* Send the crop Intent with a custom requestCode
|
||||||
*
|
*
|
||||||
* @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)
|
||||||
|
|
@ -130,17 +125,6 @@ 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
|
||||||
*
|
*
|
||||||
|
|
@ -172,85 +156,27 @@ public class Crop {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pick image from an Activity
|
* Utility to start an image picker
|
||||||
*
|
*
|
||||||
* @param activity Activity to receive result
|
* @param activity Activity that will receive result
|
||||||
*/
|
*/
|
||||||
public static void pickImage(Activity activity) {
|
public static void pickImage(Activity activity) {
|
||||||
pickImage(activity, REQUEST_PICK);
|
pickImage(activity, REQUEST_PICK);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pick image from a Fragment
|
* Utility to start an image picker with request code
|
||||||
*
|
*
|
||||||
* @param context Context
|
* @param activity Activity that will receive result
|
||||||
* @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(getImagePicker(), requestCode);
|
activity.startActivityForResult(intent, requestCode);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
showImagePickerError(activity);
|
Toast.makeText(activity, R.string.crop__pick_error, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,6 +21,7 @@ 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;
|
||||||
|
|
@ -32,8 +33,8 @@ 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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
@ -44,6 +45,7 @@ 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;
|
||||||
|
|
||||||
|
|
@ -70,10 +72,11 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
super.onCreate(icicle);
|
super.onCreate(icicle);
|
||||||
setupWindowFlags();
|
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
setupViews();
|
setContentView(R.layout.crop__activity_crop);
|
||||||
|
initViews();
|
||||||
|
|
||||||
loadInput();
|
setupFromIntent();
|
||||||
if (rotateBitmap == null) {
|
if (rotateBitmap == null) {
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
|
|
@ -81,17 +84,7 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
startCrop();
|
startCrop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
private void initViews() {
|
||||||
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() {
|
||||||
|
|
@ -116,7 +109,7 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadInput() {
|
private void setupFromIntent() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Bundle extras = intent.getExtras();
|
Bundle extras = intent.getExtras();
|
||||||
|
|
||||||
|
|
@ -198,7 +191,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();
|
imageView.center(true, true);
|
||||||
}
|
}
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
|
@ -262,19 +255,23 @@ 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;
|
Bitmap croppedImage = null;
|
||||||
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;
|
int outWidth = width, outHeight = height;
|
||||||
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) {
|
||||||
|
|
@ -286,18 +283,27 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (IN_MEMORY_CROP && rotateBitmap != null) {
|
||||||
croppedImage = decodeRegionCrop(r, outWidth, outHeight);
|
croppedImage = inMemoryCrop(rotateBitmap, r, width, height, outWidth, outHeight);
|
||||||
} catch (IllegalArgumentException e) {
|
if (croppedImage != null) {
|
||||||
setResultException(e);
|
imageView.setImageBitmapResetBase(croppedImage, true);
|
||||||
finish();
|
imageView.center(true, true);
|
||||||
return;
|
imageView.highlightViews.clear();
|
||||||
}
|
}
|
||||||
|
} 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();
|
imageView.center(true, true);
|
||||||
imageView.highlightViews.clear();
|
imageView.highlightViews.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
saveImage(croppedImage);
|
saveImage(croppedImage);
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +323,8 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
|
@TargetApi(10)
|
||||||
|
private Bitmap decodeRegionCrop(Rect rect) {
|
||||||
// Release memory now
|
// Release memory now
|
||||||
clearImageView();
|
clearImageView();
|
||||||
|
|
||||||
|
|
@ -344,11 +351,6 @@ 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 ("
|
||||||
|
|
@ -357,7 +359,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);
|
||||||
setResultException(e);
|
finish();
|
||||||
} 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);
|
||||||
|
|
@ -367,6 +369,33 @@ 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) {
|
||||||
|
|
@ -390,10 +419,11 @@ public class CropImageActivity extends MonitoredActivity {
|
||||||
CropUtil.closeSilently(outputStream);
|
CropUtil.closeSilently(outputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
CropUtil.copyExifRotation(
|
if (!IN_MEMORY_CROP) {
|
||||||
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
|
// In-memory crop negates the rotation
|
||||||
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
|
File saveFile = CropUtil.getFromMediaUri(this, getContentResolver(), saveUri);
|
||||||
);
|
CropUtil.saveExifRotation(saveFile, exifRotation);
|
||||||
|
}
|
||||||
|
|
||||||
setResultUri(saveUri);
|
setResultUri(saveUri);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ 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;
|
||||||
|
|
||||||
|
|
@ -18,16 +17,18 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +85,7 @@ public class CropImageView extends ImageViewTouchBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
CropImageActivity cropImageActivity = (CropImageActivity) context;
|
CropImageActivity cropImageActivity = (CropImageActivity) context;
|
||||||
if (cropImageActivity.isSaving()) {
|
if (cropImageActivity.isSaving()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -99,8 +100,6 @@ 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);
|
||||||
|
|
@ -114,20 +113,29 @@ 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 && event.getPointerId(event.getActionIndex()) == validPointerId) {
|
if (motionHighlightView != null) {
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
// If we're not zoomed then there's no point in even allowing the user to move the image around.
|
switch (event.getAction()) {
|
||||||
// This call to center puts it back to the normalized location.
|
case MotionEvent.ACTION_UP:
|
||||||
|
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();
|
center(true, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -181,10 +189,10 @@ public class CropImageView extends ImageViewTouchBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(@NonNull Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
for (HighlightView highlightView : highlightViews) {
|
for (HighlightView mHighlightView : highlightViews) {
|
||||||
highlightView.draw(canvas);
|
mHighlightView.draw(canvas);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,21 +68,20 @@ class CropUtil {
|
||||||
return ExifInterface.ORIENTATION_UNDEFINED;
|
return ExifInterface.ORIENTATION_UNDEFINED;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e("Error getting Exif data", e);
|
Log.e("Error reading Exif rotation data", e);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean copyExifRotation(File sourceFile, File destFile) {
|
public static boolean saveExifRotation(File file, int exifRotation) {
|
||||||
if (sourceFile == null || destFile == null) return false;
|
if (file == null) return false;
|
||||||
try {
|
try {
|
||||||
ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath());
|
ExifInterface exifDest = new ExifInterface(file.getAbsolutePath());
|
||||||
ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());
|
exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(exifRotation));
|
||||||
exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION));
|
|
||||||
exifDest.saveAttributes();
|
exifDest.saveAttributes();
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e("Error copying Exif data", e);
|
Log.e("Error saving Exif rotation data", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +158,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 guarantee
|
// Make the progress dialog uncancelable, so that we can gurantee
|
||||||
// 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,7 +66,6 @@ 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;
|
||||||
|
|
@ -88,7 +87,6 @@ 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)];
|
||||||
|
|
@ -152,10 +150,6 @@ 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);
|
||||||
|
|
@ -215,11 +209,6 @@ 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,10 +192,12 @@ abstract class ImageViewTouchBase extends ImageView {
|
||||||
maxZoom = calculateMaxZoom();
|
maxZoom = calculateMaxZoom();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center as much as possible in one or both axis. Centering is defined as follows:
|
// Center as much as possible in one or both axis. Centering is
|
||||||
// * If the image is scaled down below the view's dimensions then center it.
|
// defined as follows: if the image is scaled down below the
|
||||||
// * If the image is scaled larger than the view and is translated out of view then translate it back into view.
|
// view's dimensions then center it (literally). If the image
|
||||||
protected void center() {
|
// is scaled larger than the view and is translated out of view
|
||||||
|
// 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;
|
||||||
|
|
@ -210,37 +212,32 @@ abstract class ImageViewTouchBase extends ImageView {
|
||||||
|
|
||||||
float deltaX = 0, deltaY = 0;
|
float deltaX = 0, deltaY = 0;
|
||||||
|
|
||||||
deltaY = centerVertical(rect, height, deltaY);
|
if (vertical) {
|
||||||
deltaX = centerHorizontal(rect, width, deltaX);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
@ -316,7 +313,7 @@ abstract class ImageViewTouchBase extends ImageView {
|
||||||
|
|
||||||
suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
|
suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
|
||||||
setImageMatrix(getImageViewMatrix());
|
setImageMatrix(getImageViewMatrix());
|
||||||
center();
|
center(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void zoomTo(final float scale, final float centerX,
|
protected void zoomTo(final float scale, final float centerX,
|
||||||
|
|
@ -386,7 +383,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();
|
center(true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 void e(String msg) {
|
public static final void e(String msg) {
|
||||||
android.util.Log.e(TAG, msg);
|
android.util.Log.e(TAG, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void e(String msg, Throwable e) {
|
public static final void e(String msg, Throwable e) {
|
||||||
android.util.Log.e(TAG, msg, e);
|
android.util.Log.e(TAG, msg, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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 imágenes disponibles</string>
|
<string name="crop__pick_error">No hay imagenes 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>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -3,8 +3,7 @@
|
||||||
<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>
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
<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: 299 KiB After Width: | Height: | Size: 179 KiB |
Loading…
Reference in New Issue