Compare commits

...

70 Commits

Author SHA1 Message Date
Jamie McDonald 2db9b6eeae Cleaning 2015-11-20 16:09:39 +01:00
Jamie McDonald 182d247a52 Add updated Chinese translations from @dlackty 2015-11-20 15:33:00 +01:00
Jamie McDonald d8a549bb56 Don’t attempt to scale an image that could not be decoded 2015-11-20 15:23:47 +01:00
Jamie McDonald 1921af545c Update README with link to theme attrs 2015-11-20 14:09:47 +01:00
Jamie McDonald 946e959302 Merge pull request #171 from dlackty/tweak-readme
Add syntax highlighting and SVG badge
2015-11-20 13:58:19 +01:00
Jamie McDonald 0f8b5ae8e7 Merge pull request #179 from kriztan/patch-1
added German strings
2015-11-20 13:57:33 +01:00
Christian S 65f3be4c56 added German strings 2015-11-07 16:30:41 +01:00
Richard Lee d981090e37 Add syntax highlighting and SVG badge 2015-10-23 13:45:15 +08:00
Jamie McDonald 97502265ce Update versions, README & CHANGELOG 2015-09-09 11:58:34 +02:00
Jamie McDonald b4be2c2703 Fix image offset on ACTION_MOVE 2015-09-09 11:58:11 +02:00
Jamie McDonald b532e0095a Cleaning 2015-09-09 11:20:56 +02:00
Jamie McDonald 3b4d512236 Override script to skip connectedCheck for now 2015-09-08 23:50:41 +02:00
Jamie McDonald 5684c03ea5 Another Travis fix 2015-09-08 23:39:24 +02:00
Jamie McDonald e152edf0c4 Travis fix 2015-09-08 23:35:06 +02:00
Jamie McDonald a08203184b Use new Travis builders 2015-09-08 23:03:26 +02:00
Jamie McDonald 8279b868ad Version bumps & do less with Travis 2015-09-08 22:18:19 +02:00
Jamie McDonald dfe8cd0724 Lint fixes 2015-09-08 22:15:47 +02:00
Jamie McDonald da391b4973 Formatting 2015-09-08 19:35:04 +02:00
Jamie McDonald d9b55467f1 Merge pull request #123 from torstenojaperv/master
Fixed multiple touch zoom in issue and image "twitching" when zoomin …
2015-09-08 19:21:12 +02:00
Jamie McDonald bafa75b42e Clear windowTranslucentStatus flag in case it’s set in the app theme
Fixes #162
2015-09-07 16:18:10 +02:00
Jamie McDonald d38710fd91 Formatting 2015-09-07 15:38:35 +02:00
Jamie McDonald ccd2faba89 Cleaning & builder interface docs 2015-09-07 15:30:07 +02:00
Jamie McDonald 7186cf2b61 Merge pull request #158 from growingdever/pick-from-fragment
pick image for fragment
2015-09-07 15:02:38 +02:00
Jamie McDonald 8e55a749e1 Merge pull request #126 from mcginty/gb-support
re-add gingerbread support
2015-09-07 14:24:57 +02:00
Jamie McDonald 48321788d8 Merge pull request #150 from hcoosthuizen/master
setResult on exception instead of finish
2015-09-07 12:47:23 +02:00
Jamie McDonald 0abbe54b64 Lint & consistency 2015-09-07 12:12:33 +02:00
Jamie McDonald 35b8e40f83 Merge pull request #115 from folivares/master
Add Italian support
2015-09-07 12:03:57 +02:00
Jamie McDonald 3abf2ddbd6 Merge pull request #141 from slmyldz/master
Add turkish translations
2015-09-07 12:02:12 +02:00
Jamie McDonald e4cc1edc2f Merge pull request #143 from antonio-manuel/master
Added Catalan translations
2015-09-07 11:59:52 +02:00
Jamie McDonald 2104764c9c Merge pull request #147 from sergii-frost/master
Adding Swedish translations.
2015-09-07 11:56:55 +02:00
loki 3a3c404ec8 pick image for fragment 2015-08-17 01:20:58 +09:00
Huxley Oosthuizen ad483ebf4a setResult on exception instead of finish 2015-07-27 12:21:42 +02:00
Sergii Nezdolii 801d204e96 Adding Swedish translations. 2015-07-24 13:55:07 +02:00
Antonio Lopez ebd7f7a135 Added Catalan translations 2015-07-20 15:37:11 +02:00
Selim YILDIZ a8daff5617 Add turkish translations 2015-07-16 12:13:38 +03:00
Jake McGinty e5d0c97a29 re-add gingerbread support 2015-06-03 17:34:07 -07:00
Torsten Ojaperv 32772d8087 Fixed multiple touch zoom in issue and image "twitching" when zoomin back to maximum bounds. 2015-05-29 10:42:07 +03:00
Federico ff4db1a904 add Italian support 2015-05-12 20:13:51 +02:00
Jamie McDonald d6ddf873d9 Fancy badges [skip ci] 2015-05-10 17:34:06 -04:00
Jamie McDonald f384ae31dd Updated README [skip ci] 2015-05-09 21:41:16 -04:00
Jamie McDonald b37f43f2fc README update 2015-05-08 15:58:59 -04:00
Jamie McDonald 2aeda079cd Build tools version 2015-05-08 14:30:49 -04:00
Jamie McDonald b494ac0278 SDK 21 for now to keep Travis happy 2015-05-08 14:13:48 -04:00
Jamie McDonald 931a222a2d 1.0.0 2015-05-08 13:58:51 -04:00
Jamie McDonald a80587562d Preparing for release 2015-05-08 13:22:54 -04:00
Jamie McDonald fe3f7fd41f Formatting 2015-05-08 12:49:18 -04:00
Jamie McDonald 1f529d9f0c Merge pull request #98 from falcon2010/master
Arabic support
2015-05-08 12:44:57 -04:00
Jamie McDonald df18e470f9 Merge pull request #107 from deronbrown/master
Remove "allowBackup" from AndroidManifest
2015-05-08 12:41:22 -04:00
Jamie McDonald 01c77ff8b5 Added extra translations 2015-05-08 12:31:51 -04:00
DeRon Brown 71e936c17b Remove "allowBackup" from Manifest 2015-04-26 22:19:48 -04:00
Jamie McDonald 3d461dac55 Updated README [skip ci] 2015-04-24 14:47:34 -04:00
Jamie McDonald cd5ac96ad7 Interface change since output is not optional 2015-04-24 13:23:13 -04:00
mohamed ibrahim 9f9b8174fb add Arabic Support
Add Arabic support to the string file
2015-04-19 16:08:40 +02:00
Jamie McDonald 4bbc21ec2f Update changelog (snapshot) [skip ci] 2015-04-17 17:14:43 -04:00
Jamie McDonald 02758958be Docs on public interface 2015-04-17 15:36:32 -04:00
Jamie McDonald bbe009289e Merge pull request #86 from nostra13/master
Support Fragments from support-v4
2015-04-17 15:01:51 -04:00
Jamie McDonald 18ab2bef7c No circle guide by default in example [skip ci] 2015-04-17 14:45:41 -04:00
Jamie McDonald fea38b4f9f Typo [skip ci] 2015-04-17 13:52:21 -04:00
Jamie McDonald f734d4c22b Merge pull request #93 from BoD/feature/showCircle
'Show circle' feature
2015-04-17 13:49:23 -04:00
Jamie McDonald ecbc98997a Cleaning 2015-04-17 13:43:38 -04:00
Jamie McDonald b6c3d2194c Update example target versions 2015-04-17 13:37:16 -04:00
Jamie McDonald 9d654fa2b3 Remove Gingerbread support (in-memory crop) 2015-04-17 13:36:49 -04:00
Jamie McDonald 80d0f0c754 Updated README: Gingerbread is over [skip ci] 2015-04-17 13:05:10 -04:00
Jamie McDonald d30306ebff Add Korean strings 2015-04-17 11:30:35 -04:00
Jamie McDonald b25f09ebbf Merge pull request #96 from clemp6r/master
Add french translation
2015-04-17 11:07:45 -04:00
Clément Plantier 655c2c668f add french translation 2015-04-14 10:31:21 +02:00
BoD 0df72f4627 'Show circle' feature.
This optionally draws a circle in the HighlightView, in the same style as the 'thirds'.
This is useful as many apps use circle avatars nowadays.  Also useful to crop images
that will be shown on a round watch.
2015-03-28 18:42:47 +01:00
nostra13 06137586b4 Travis: Update buildTools version 2015-02-22 22:58:38 +03:00
nostra13 058b870a13 Support Fragments from support-v4.
Update versions of buildTools and support-v4.
2015-02-19 17:49:43 +03:00
Jamie McDonald d7a3ae2fb8 Resize after decodeRegionCrop for max size params 2015-02-13 16:30:17 +01:00
34 changed files with 430 additions and 225 deletions

View File

@ -1,21 +1,13 @@
language: android
sudo: false
android:
components:
- build-tools-21.1.1
- android-21
- build-tools-23.0.1
- android-23
- extra-android-support
- 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 &
- extra-android-m2repository
script:
- ./gradlew :lib:connectedAndroidTest
- ./gradlew clean build

View File

@ -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
* Fix bug on some devices where image was displayed with 0 size
@ -5,5 +30,5 @@
## 0.9.9
* Downscale source images that are too big to load
* Optional always show crop handles
* Fix shading outside crop area on some API levels
* Add option to always show crop handles

View File

@ -1,52 +1,65 @@
> 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.
[![Build Status](https://travis-ci.org/jdamcd/android-crop.png)](https://travis-ci.org/jdamcd/android-crop)
[![build status](https://travis-ci.org/jdamcd/android-crop.svg)](https://travis-ci.org/jdamcd/android-crop)
[![maven central](https://img.shields.io/badge/maven%20central-1.0.1-brightgreen.svg)](http://search.maven.org/#artifactdetails%7Ccom.soundcloud.android%7Candroid-crop%7C1.0.1%7Caar.asc)
[![changelog](https://img.shields.io/badge/changelog-1.0.1-lightgrey.svg)](CHANGELOG.md)
## Goals
## Features
* Gradle build with AAR
* Gradle build & AAR
* Modern UI
* Backwards compatible to Gingerbread
* Backwards compatible to SDK 10
* Simple builder for configuration
* Example project
* More tests, less unused complexity
## Usage
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
`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):
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode == Crop.REQUEST_CROP && resultCode == RESULT_OK) {
doSomethingWithCroppedImage(outputUri);
}
```java
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
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
The library provides a utility method to start an image picker:
`Crop.pickImage(activity)`
```java
Crop.pickImage(activity)
```
#### Dependency
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?
@ -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).
Copyright 2014 [SoundCloud](https://soundcloud.com)
Apache License, Version 2.0
Copyright 2015 SoundCloud
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.

View File

@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0'
classpath 'com.android.tools.build:gradle:1.2.3'
}
}

View File

@ -3,12 +3,12 @@ apply plugin: 'com.android.application'
archivesBaseName = 'android-crop-example'
android {
compileSdkVersion 21
buildToolsVersion '21.1.2'
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
minSdkVersion 9
targetSdkVersion 21
minSdkVersion 10
targetSdkVersion 22
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION
}

View File

@ -1,17 +1,16 @@
package com.soundcloud.android.crop.example;
import com.soundcloud.android.crop.Crop;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.Toast;
import com.soundcloud.android.crop.Crop;
import java.io.File;
public class MainActivity extends Activity {
@ -51,8 +50,8 @@ public class MainActivity extends Activity {
}
private void beginCrop(Uri source) {
Uri outputUri = Uri.fromFile(new File(getCacheDir(), "cropped"));
new Crop(source).output(outputUri).asSquare().start(this);
Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
Crop.of(source, destination).asSquare().start(this);
}
private void handleCrop(int resultCode, Intent result) {

View File

@ -6,6 +6,7 @@
<style name="Widget.CropImageView" parent="android:Widget">
<item name="showThirds">true</item>
<item name="showCircle">false</item>
<item name="showHandles">always</item>
<item name="highlightColor">@color/highlight</item>
</style>

View File

@ -1,4 +1,4 @@
VERSION=0.9.10
VERSION=1.0.1
VERSION_CODE=1
signing.keyId=63A46540
@ -6,4 +6,4 @@ signing.secretKeyRingFile=
signing.password=
sonatypeUsername=jdamcd
sonatypePassword=
sonatypePassword=

View File

@ -6,12 +6,12 @@ apply plugin: 'signing'
archivesBaseName = 'android-crop'
android {
compileSdkVersion 21
buildToolsVersion '21.1.1'
compileSdkVersion 23
buildToolsVersion '23.0.1'
defaultConfig {
minSdkVersion 9
targetSdkVersion 21
minSdkVersion 10
targetSdkVersion 22
testApplicationId 'com.soundcloud.android.crop.test'
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
@ -19,9 +19,10 @@ android {
}
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.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 'com.google.dexmaker:dexmaker:1.0'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'

View File

@ -1,16 +1,16 @@
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.content.Intent;
import android.net.Uri;
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 {
private Activity activity;
@ -22,7 +22,7 @@ public class CropBuilderTest extends BaseTestCase {
activity = mock(Activity.class);
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() {
@ -30,8 +30,6 @@ public class CropBuilderTest extends BaseTestCase {
}
public void testOutputUriSetAsExtra() {
builder.output(Uri.parse("image:output"));
Intent intent = builder.getIntent(activity);
Uri output = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);

View File

@ -1,7 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.soundcloud.android.crop">
<application
android:allowBackup="true" />
</manifest>
<manifest package="com.soundcloud.android.crop" />

View File

@ -20,7 +20,7 @@ public class Crop {
public static final int REQUEST_PICK = 9162;
public static final int RESULT_ERROR = 404;
static interface Extra {
interface Extra {
String ASPECT_X = "aspect_x";
String ASPECT_Y = "aspect_y";
String MAX_X = "max_x";
@ -31,23 +31,19 @@ public class Crop {
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) {
cropIntent = new Intent();
cropIntent.setData(source);
public static Crop of(Uri source, Uri destination) {
return new Crop(source, 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;
private Crop(Uri source, Uri destination) {
cropIntent = new Intent();
cropIntent.setData(source);
cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, destination);
}
/**
@ -74,7 +70,7 @@ public class Crop {
/**
* Set maximum crop size
*
* @param width Max width
* @param width Max width
* @param height Max 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
*/
@ -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
*/
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
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void start(Context context, Fragment fragment) {
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
*/
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
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@ -125,6 +130,17 @@ public class Crop {
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
*
@ -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) {
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
*/
public static void pickImage(Activity activity, int requestCode) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT).setType("image/*");
try {
activity.startActivityForResult(intent, requestCode);
activity.startActivityForResult(getImagePicker(), requestCode);
} 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();
}
}

View File

@ -21,7 +21,6 @@ import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
@ -33,6 +32,7 @@ import android.os.Handler;
import android.provider.MediaStore;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import java.io.IOException;
import java.io.InputStream;
@ -44,7 +44,6 @@ import java.util.concurrent.CountDownLatch;
*/
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_LIMIT = 4096;
@ -71,11 +70,10 @@ public class CropImageActivity extends MonitoredActivity {
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.crop__activity_crop);
initViews();
setupWindowFlags();
setupViews();
setupFromIntent();
loadInput();
if (rotateBitmap == null) {
finish();
return;
@ -83,7 +81,17 @@ public class CropImageActivity extends MonitoredActivity {
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.context = this;
imageView.setRecycler(new ImageViewTouchBase.Recycler() {
@ -108,7 +116,7 @@ public class CropImageActivity extends MonitoredActivity {
});
}
private void setupFromIntent() {
private void loadInput() {
Intent intent = getIntent();
Bundle extras = intent.getExtras();
@ -190,7 +198,7 @@ public class CropImageActivity extends MonitoredActivity {
handler.post(new Runnable() {
public void run() {
if (imageView.getScale() == 1F) {
imageView.center(true, true);
imageView.center();
}
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() {
if (cropView == null || isSaving) {
return;
}
isSaving = true;
Bitmap croppedImage = null;
Bitmap croppedImage;
Rect r = cropView.getScaledCropRect(sampleSize);
int width = r.width();
int height = r.height();
int outWidth = width, outHeight = height;
int outWidth = width;
int outHeight = height;
if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
float ratio = (float) width / (float) height;
if ((float) maxX / (float) maxY > ratio) {
@ -282,27 +286,18 @@ public class CropImageActivity extends MonitoredActivity {
}
}
if (IN_MEMORY_CROP && rotateBitmap != null) {
croppedImage = inMemoryCrop(rotateBitmap, r, width, height, outWidth, outHeight);
if (croppedImage != null) {
imageView.setImageBitmapResetBase(croppedImage, true);
imageView.center(true, true);
imageView.highlightViews.clear();
}
} else {
try {
croppedImage = decodeRegionCrop(r);
} catch (IllegalArgumentException e) {
setResultException(e);
finish();
return;
}
try {
croppedImage = decodeRegionCrop(r, outWidth, outHeight);
} catch (IllegalArgumentException e) {
setResultException(e);
finish();
return;
}
if (croppedImage != null) {
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
imageView.center(true, true);
imageView.highlightViews.clear();
}
if (croppedImage != null) {
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
imageView.center();
imageView.highlightViews.clear();
}
saveImage(croppedImage);
}
@ -322,8 +317,7 @@ public class CropImageActivity extends MonitoredActivity {
}
}
@TargetApi(10)
private Bitmap decodeRegionCrop(Rect rect) {
private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
// Release memory now
clearImageView();
@ -350,6 +344,11 @@ public class CropImageActivity extends MonitoredActivity {
try {
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) {
// Rethrow with some extra information
throw new IllegalArgumentException("Rectangle " + rect + " is outside of the image ("
@ -358,7 +357,7 @@ public class CropImageActivity extends MonitoredActivity {
} catch (IOException e) {
Log.e("Error cropping image: " + e.getMessage(), e);
finish();
setResultException(e);
} catch (OutOfMemoryError e) {
Log.e("OOM cropping image: " + e.getMessage(), e);
setResultException(e);
@ -368,33 +367,6 @@ public class CropImageActivity extends MonitoredActivity {
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() {
imageView.clear();
if (rotateBitmap != null) {
@ -418,13 +390,10 @@ public class CropImageActivity extends MonitoredActivity {
CropUtil.closeSilently(outputStream);
}
if (!IN_MEMORY_CROP) {
// In-memory crop negates the rotation
CropUtil.copyExifRotation(
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
);
}
CropUtil.copyExifRotation(
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
);
setResultUri(saveUri);
}

View File

@ -3,6 +3,7 @@ package com.soundcloud.android.crop;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
@ -17,18 +18,16 @@ public class CropImageView extends ImageViewTouchBase {
private float lastX;
private float lastY;
private int motionEdge;
private int validPointerId;
@SuppressWarnings("UnusedDeclaration")
public CropImageView(Context context) {
super(context);
}
@SuppressWarnings("UnusedDeclaration")
public CropImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@SuppressWarnings("UnusedDeclaration")
public CropImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@ -85,7 +84,7 @@ public class CropImageView extends ImageViewTouchBase {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
public boolean onTouchEvent(@NonNull MotionEvent event) {
CropImageActivity cropImageActivity = (CropImageActivity) context;
if (cropImageActivity.isSaving()) {
return false;
@ -100,6 +99,8 @@ public class CropImageView extends ImageViewTouchBase {
motionHighlightView = hv;
lastX = event.getX();
lastY = event.getY();
// Prevent multiple touches from interfering with crop area re-sizing
validPointerId = event.getPointerId(event.getActionIndex());
motionHighlightView.setMode((edge == HighlightView.MOVE)
? HighlightView.ModifyMode.Move
: HighlightView.ModifyMode.Grow);
@ -113,29 +114,20 @@ public class CropImageView extends ImageViewTouchBase {
motionHighlightView.setMode(HighlightView.ModifyMode.None);
}
motionHighlightView = null;
center();
break;
case MotionEvent.ACTION_MOVE:
if (motionHighlightView != null) {
if (motionHighlightView != null && event.getPointerId(event.getActionIndex()) == validPointerId) {
motionHighlightView.handleMotion(motionEdge, event.getX()
- lastX, event.getY() - lastY);
lastX = event.getX();
lastY = event.getY();
ensureVisible(motionHighlightView);
}
break;
}
switch (event.getAction()) {
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 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.
if (getScale() == 1F) {
center(true, true);
center();
}
break;
}
@ -189,10 +181,10 @@ public class CropImageView extends ImageViewTouchBase {
}
@Override
protected void onDraw(Canvas canvas) {
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
for (HighlightView mHighlightView : highlightViews) {
mHighlightView.draw(canvas);
for (HighlightView highlightView : highlightViews) {
highlightView.draw(canvas);
}
}

View File

@ -159,7 +159,7 @@ class CropUtil {
public static void startBackgroundJob(MonitoredActivity activity,
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
ProgressDialog dialog = ProgressDialog.show(
activity, title, message, true, false);

View File

@ -66,6 +66,7 @@ class HighlightView {
private View viewContext; // View displaying image
private boolean showThirds;
private boolean showCircle;
private int highlightColor;
private ModifyMode modifyMode = ModifyMode.None;
@ -87,6 +88,7 @@ class HighlightView {
TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
try {
showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);
highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
DEFAULT_HIGHLIGHT_COLOR);
handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
@ -150,6 +152,10 @@ class HighlightView {
drawThirds(canvas);
}
if (showCircle) {
drawCircle(canvas);
}
if (handleMode == HandleMode.Always ||
(handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
drawHandles(canvas);
@ -209,6 +215,11 @@ class HighlightView {
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) {
if (mode != modifyMode) {
modifyMode = mode;

View File

@ -192,12 +192,10 @@ abstract class ImageViewTouchBase extends ImageView {
maxZoom = calculateMaxZoom();
}
// Center as much as possible in one or both axis. Centering is
// defined as follows: if the image is scaled down below the
// view's dimensions then center it (literally). If the image
// 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) {
// Center as much as possible in one or both axis. Centering is defined as follows:
// * If the image is scaled down below the view's dimensions then center it.
// * If the image is scaled larger than the view and is translated out of view then translate it back into view.
protected void center() {
final Bitmap bitmap = bitmapDisplayed.getBitmap();
if (bitmap == null) {
return;
@ -212,32 +210,37 @@ abstract class ImageViewTouchBase extends ImageView {
float deltaX = 0, deltaY = 0;
if (vertical) {
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;
}
}
deltaY = centerVertical(rect, height, deltaY);
deltaX = centerHorizontal(rect, width, deltaX);
postTranslate(deltaX, deltaY);
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() {
setScaleType(ImageView.ScaleType.MATRIX);
}
@ -313,7 +316,7 @@ abstract class ImageViewTouchBase extends ImageView {
suppMatrix.postScale(deltaScale, deltaScale, centerX, centerY);
setImageMatrix(getImageViewMatrix());
center(true, true);
center();
}
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);
}
setImageMatrix(getImageViewMatrix());
center(true, true);
center();
}
protected void postTranslate(float dx, float dy) {

View File

@ -4,11 +4,11 @@ class Log {
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);
}
public static final void e(String msg, Throwable e) {
public static void e(String msg, Throwable e) {
android.util.Log.e(TAG, msg, e);
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -2,7 +2,7 @@
<string name="crop__saving">Guardando imagen…</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__cancel" tools:ignore="ButtonCase">CANCELAR</string>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -3,7 +3,8 @@
<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>
</resources>

View File

@ -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>

View File

@ -5,6 +5,7 @@
<declare-styleable name="CropImageView">
<attr name="highlightColor" format="reference|color" />
<attr name="showThirds" format="boolean" />
<attr name="showCircle" format="boolean" />
<attr name="showHandles">
<enum name="changing" value="0" />
<enum name="always" value="1" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 299 KiB