diff --git a/example/build.gradle b/example/build.gradle
index 33f163f5..174af8cb 100644
--- a/example/build.gradle
+++ b/example/build.gradle
@@ -4,11 +4,11 @@ archivesBaseName = 'android-crop-example'
android {
compileSdkVersion 23
- buildToolsVersion '23.0.1'
+ buildToolsVersion '23.0.2'
defaultConfig {
- minSdkVersion 10
- targetSdkVersion 22
+ minSdkVersion 19
+ targetSdkVersion 23
versionCode Integer.parseInt(project.VERSION_CODE)
versionName project.VERSION
}
diff --git a/example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java b/example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java
index 079775aa..f75d22b7 100644
--- a/example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java
+++ b/example/src/main/java/com/soundcloud/android/crop/example/MainActivity.java
@@ -1,27 +1,34 @@
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.os.Environment;
+import android.provider.MediaStore;
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 {
private ImageView resultView;
+ private int REQUEST_PHOTO = 1;
+ private File PHOTO_CAPTURED, PHOTO_CROPPED;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultView = (ImageView) findViewById(R.id.result_image);
+
+ PHOTO_CAPTURED = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "PhotoCaptured.jpg");
+ PHOTO_CROPPED = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "PhotoCropped.jpg");
}
@Override
@@ -36,22 +43,36 @@ public boolean onOptionsItemSelected(MenuItem item) {
resultView.setImageDrawable(null);
Crop.pickImage(this);
return true;
+ } else if (item.getItemId() == R.id.action_camera) {
+ resultView.setImageDrawable(null);
+ takePhoto();
+ return true;
}
return super.onOptionsItemSelected(item);
}
+ private void takePhoto() {
+ final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(PHOTO_CAPTURED));
+ startActivityForResult(intent, REQUEST_PHOTO);
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result) {
if (requestCode == Crop.REQUEST_PICK && resultCode == RESULT_OK) {
beginCrop(result.getData());
+ } else if (requestCode == REQUEST_PHOTO && resultCode == RESULT_OK) {
+ beginCrop(Uri.fromFile(PHOTO_CAPTURED));
} else if (requestCode == Crop.REQUEST_CROP) {
handleCrop(resultCode, result);
}
}
private void beginCrop(Uri source) {
- Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
- Crop.of(source, destination).asSquare().start(this);
+ Crop.of(source, Uri.fromFile(PHOTO_CROPPED))
+ .withMaxSize(1600,1600)
+ .withJpgQuality(90)
+ .start(this);
}
private void handleCrop(int resultCode, Intent result) {
@@ -61,5 +82,4 @@ private void handleCrop(int resultCode, Intent result) {
Toast.makeText(this, Crop.getError(result).getMessage(), Toast.LENGTH_SHORT).show();
}
}
-
}
diff --git a/example/src/main/res/menu/activity_main.xml b/example/src/main/res/menu/activity_main.xml
index ede18fd7..77854524 100644
--- a/example/src/main/res/menu/activity_main.xml
+++ b/example/src/main/res/menu/activity_main.xml
@@ -6,4 +6,7 @@
android:title="@string/action_select"
android:showAsAction="always" />
+
\ No newline at end of file
diff --git a/lib/build.gradle b/lib/build.gradle
index 20e1a767..f05475db 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -7,11 +7,11 @@ archivesBaseName = 'android-crop'
android {
compileSdkVersion 23
- buildToolsVersion '23.0.1'
+ buildToolsVersion '23.0.2'
defaultConfig {
- minSdkVersion 10
- targetSdkVersion 22
+ minSdkVersion 19
+ targetSdkVersion 23
testApplicationId 'com.soundcloud.android.crop.test'
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
diff --git a/lib/src/main/java/com/soundcloud/android/crop/Crop.java b/lib/src/main/java/com/soundcloud/android/crop/Crop.java
index 564f5b52..3bf46d74 100644
--- a/lib/src/main/java/com/soundcloud/android/crop/Crop.java
+++ b/lib/src/main/java/com/soundcloud/android/crop/Crop.java
@@ -25,6 +25,7 @@ interface Extra {
String ASPECT_Y = "aspect_y";
String MAX_X = "max_x";
String MAX_Y = "max_y";
+ String JPG_QUALITY = "jpg_quality";
String ERROR = "error";
}
@@ -79,6 +80,18 @@ public Crop withMaxSize(int width, int height) {
return this;
}
+ /**
+ * Set JPG quality
+ *
+ * @param quality JPG Quality (10< q <100)
+ */
+ public Crop withJpgQuality(int quality) {
+ if ( (quality>=10) || (quality<=100) ) {
+ cropIntent.putExtra(Extra.JPG_QUALITY, quality);
+ }
+ return this;
+ }
+
/**
* Send the crop Intent from an Activity
*
diff --git a/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java b/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java
index 97a0466a..30d57a48 100644
--- a/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java
+++ b/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java
@@ -295,23 +295,22 @@ private void onSaveClicked() {
}
if (croppedImage != null) {
- imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
- imageView.center();
+ //imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
+ //imageView.center();
imageView.highlightViews.clear();
}
saveImage(croppedImage);
}
- private void saveImage(Bitmap croppedImage) {
- if (croppedImage != null) {
- final Bitmap b = croppedImage;
+ private void saveImage(final Bitmap b) {
+ if (b != null) {
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving),
new Runnable() {
public void run() {
saveOutput(b);
+ b.recycle();
}
- }, handler
- );
+ }, handler);
} else {
finish();
}
@@ -337,6 +336,14 @@ private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
RectF adjusted = new RectF();
matrix.mapRect(adjusted, new RectF(rect));
+ //if the cutting box are rectangle( outWidth != outHeight ),and the exifRotation is 90 or 270,
+ //the outWidth and outHeight should be interchanged
+ if (exifRotation==90 || exifRotation==270) {
+ int temp=outWidth;
+ outWidth=outHeight;
+ outHeight=temp;
+ }
+
// Adjust to account for origin at 0,0
adjusted.offset(adjusted.left < 0 ? width : 0, adjusted.top < 0 ? height : 0);
rect = new Rect((int) adjusted.left, (int) adjusted.top, (int) adjusted.right, (int) adjusted.bottom);
@@ -344,11 +351,14 @@ private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
try {
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
- if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
- Matrix matrix = new Matrix();
+ Matrix matrix = new Matrix();
+ if (rect.width() > outWidth || rect.height() > outHeight) {
matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
- croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
}
+ //If the picture's exifRotation !=0 ,they should be rotated to 0 degrees
+ //If the picture need not to be scale, they also need to be rotate to 0 degrees
+ matrix.postRotate(exifRotation);
+ 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 ("
@@ -376,33 +386,35 @@ private void clearImageView() {
}
private void saveOutput(Bitmap croppedImage) {
- if (saveUri != null) {
- OutputStream outputStream = null;
- try {
- outputStream = getContentResolver().openOutputStream(saveUri);
+ int jpgQuality = 90;
+ Intent intent = getIntent();
+ Bundle extras = intent.getExtras();
+ if (extras != null) {
+ if (extras.containsKey(Crop.Extra.JPG_QUALITY))
+ jpgQuality = extras.getInt(Crop.Extra.JPG_QUALITY);
+ }
+
+ OutputStream outputStream = null;
+ try {
+ if (saveUri != null) {
+ outputStream = this.getContentResolver().openOutputStream(saveUri);
if (outputStream != null) {
- croppedImage.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
+ croppedImage.compress(Bitmap.CompressFormat.JPEG, jpgQuality, outputStream);
}
- } catch (IOException e) {
- setResultException(e);
- Log.e("Cannot open file: " + saveUri, e);
- } finally {
- CropUtil.closeSilently(outputStream);
+ croppedImage.recycle();
}
- CropUtil.copyExifRotation(
- CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
- CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
- );
-
setResultUri(saveUri);
+ } catch(IOException e) {
+ setResultException(e);
+ Log.e("Error saving file: " + saveUri, e);
+ } finally {
+ CropUtil.closeSilently(outputStream);
}
- final Bitmap b = croppedImage;
handler.post(new Runnable() {
public void run() {
imageView.clear();
- b.recycle();
}
});
@@ -433,5 +445,4 @@ private void setResultUri(Uri uri) {
private void setResultException(Throwable throwable) {
setResult(Crop.RESULT_ERROR, new Intent().putExtra(Crop.Extra.ERROR, throwable));
}
-
}
diff --git a/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java b/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java
index 04e83224..8e4dbf28 100644
--- a/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java
+++ b/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java
@@ -18,21 +18,20 @@
import android.app.ProgressDialog;
import android.content.ContentResolver;
+import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.media.ExifInterface;
import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
import android.os.Handler;
-import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
-import android.text.TextUtils;
import java.io.Closeable;
import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
/*
@@ -73,90 +72,174 @@ public static int getExifRotation(File imageFile) {
}
}
- public static boolean copyExifRotation(File sourceFile, File destFile) {
- if (sourceFile == null || destFile == null) return false;
- try {
- ExifInterface exifSource = new ExifInterface(sourceFile.getAbsolutePath());
- ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());
- exifDest.setAttribute(ExifInterface.TAG_ORIENTATION, exifSource.getAttribute(ExifInterface.TAG_ORIENTATION));
- exifDest.saveAttributes();
- return true;
- } catch (IOException e) {
- Log.e("Error copying Exif data", e);
- return false;
+ @Nullable
+ public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {
+ if (uri != null) {
+ String path = getPath(context, uri);
+ if (path != null && isLocal(path)) {
+ return new File(path);
+ }
}
+ return null;
}
- @Nullable
- public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {
- if (uri == null) return null;
+ /**
+ * Get a file path from a Uri. This will get the the path for Storage Access
+ * Framework Documents, as well as the _data field for the MediaStore and
+ * other file-based ContentProviders.
+ *
+ * Callers should check whether the path is local before assuming it
+ * represents a local file.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @author paulburke
+ */
+ public static String getPath(final Context context, final Uri uri) {
- if (SCHEME_FILE.equals(uri.getScheme())) {
- return new File(uri.getPath());
- } else if (SCHEME_CONTENT.equals(uri.getScheme())) {
- final String[] filePathColumn = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME };
- Cursor cursor = null;
- try {
- cursor = resolver.query(uri, filePathColumn, null, null, null);
- if (cursor != null && cursor.moveToFirst()) {
- final int columnIndex = (uri.toString().startsWith("content://com.google.android.gallery3d")) ?
- cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME) :
- cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
- // Picasa images on API 13+
- if (columnIndex != -1) {
- String filePath = cursor.getString(columnIndex);
- if (!TextUtils.isEmpty(filePath)) {
- return new File(filePath);
- }
- }
+ final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ // DocumentProvider
+ if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
}
- } catch (IllegalArgumentException e) {
- // Google Drive images
- return getFromMediaUriPfd(context, resolver, uri);
- } catch (SecurityException ignored) {
- // Nothing we can do
- } finally {
- if (cursor != null) cursor.close();
+
+ // TODO handle non-primary volumes
+ }
+ // DownloadsProvider
+ else if (isDownloadsDocument(uri)) {
+
+ final String id = DocumentsContract.getDocumentId(uri);
+ final Uri contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+ return getDataColumn(context, contentUri, null, null);
+ }
+ // MediaProvider
+ else if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[] {
+ split[1]
+ };
+
+ return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
- return null;
- }
+ // MediaStore (and general)
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
- private static String getTempFilename(Context context) throws IOException {
- File outputDir = context.getCacheDir();
- File outputFile = File.createTempFile("image", "tmp", outputDir);
- return outputFile.getAbsolutePath();
- }
+ // Return the remote address
+ if (isGooglePhotosUri(uri) || isGoogleDriveUri(uri))
+ return uri.getLastPathSegment();
- @Nullable
- private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) {
- if (uri == null) return null;
+ return getDataColumn(context, uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
- FileInputStream input = null;
- FileOutputStream output = null;
- try {
- ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r");
- FileDescriptor fd = pfd.getFileDescriptor();
- input = new FileInputStream(fd);
+ return null;
+ }
- String tempFilename = getTempFilename(context);
- output = new FileOutputStream(tempFilename);
+ /**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ * @return The value of the _data column, which is typically a file path.
+ */
+ private static String getDataColumn(Context context, Uri uri, String selection,
+ String[] selectionArgs) {
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = { column };
- int read;
- byte[] bytes = new byte[4096];
- while ((read = input.read(bytes)) != -1) {
- output.write(bytes, 0, read);
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int column_index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(column_index);
}
- return new File(tempFilename);
- } catch (IOException ignored) {
- // Nothing we can do
} finally {
- closeSilently(input);
- closeSilently(output);
+ if (cursor != null)
+ cursor.close();
}
return null;
}
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ */
+ private static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ */
+ private static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ */
+ private static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is Google Photos.
+ */
+ private static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is Google Drive.
+ */
+ private static boolean isGoogleDriveUri(Uri uri) {
+ return "com.google.android.apps.docs.storage".equals(uri.getAuthority());
+ }
+
+ /**
+ * @return Whether the URI is a local one.
+ */
+ private static boolean isLocal(String url) {
+ return (url != null && !url.startsWith("http://") && !url.startsWith("https://"));
+ }
+
public static void startBackgroundJob(MonitoredActivity activity,
String title, String message, Runnable job, Handler handler) {
// Make the progress dialog uncancelable, so that we can guarantee
@@ -214,5 +297,4 @@ public void onActivityStarted(MonitoredActivity activity) {
dialog.show();
}
}
-
}