From 8d1937d916c1453901411fce34591e55918c68ac Mon Sep 17 00:00:00 2001 From: Saul Date: Tue, 8 Dec 2015 18:46:12 +0100 Subject: [PATCH 1/4] Fix Rotation when the image is displayed (min sdk 19) --- example/build.gradle | 6 +- lib/build.gradle | 6 +- .../com/soundcloud/android/crop/CropUtil.java | 232 +++++++++++++----- 3 files changed, 170 insertions(+), 74 deletions(-) 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/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/CropUtil.java b/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java index 04e83224..f0601557 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; /* @@ -89,70 +88,11 @@ public static boolean copyExifRotation(File sourceFile, File destFile) { @Nullable public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) { - if (uri == null) return null; - - 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); - } - } - } - } catch (IllegalArgumentException e) { - // Google Drive images - return getFromMediaUriPfd(context, resolver, uri); - } catch (SecurityException ignored) { - // Nothing we can do - } finally { - if (cursor != null) cursor.close(); - } - } - return null; - } - - private static String getTempFilename(Context context) throws IOException { - File outputDir = context.getCacheDir(); - File outputFile = File.createTempFile("image", "tmp", outputDir); - return outputFile.getAbsolutePath(); - } - - @Nullable - private static File getFromMediaUriPfd(Context context, ContentResolver resolver, Uri uri) { - if (uri == null) return null; - - FileInputStream input = null; - FileOutputStream output = null; - try { - ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "r"); - FileDescriptor fd = pfd.getFileDescriptor(); - input = new FileInputStream(fd); - - String tempFilename = getTempFilename(context); - output = new FileOutputStream(tempFilename); - - int read; - byte[] bytes = new byte[4096]; - while ((read = input.read(bytes)) != -1) { - output.write(bytes, 0, read); + if (uri != null) { + String path = getPath(context, uri); + if (path != null && isLocal(path)) { + return new File(path); } - return new File(tempFilename); - } catch (IOException ignored) { - // Nothing we can do - } finally { - closeSilently(input); - closeSilently(output); } return null; } @@ -215,4 +155,160 @@ public void onActivityStarted(MonitoredActivity activity) { } } + /** + * 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 + */ + private static String getPath(final Context context, final Uri uri) { + + 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]; + } + + // 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); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri) || isGoogleDriveUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * 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 }; + + 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); + } + } finally { + 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://")); + } } From c6bcb84002eafb6317aa011104447f888bfc0dc3 Mon Sep 17 00:00:00 2001 From: Saul Date: Tue, 8 Dec 2015 18:57:36 +0100 Subject: [PATCH 2/4] Fix Rotation when the image is saved --- .../android/crop/CropImageActivity.java | 69 ++++++++++++++++--- .../com/soundcloud/android/crop/CropUtil.java | 2 +- 2 files changed, 62 insertions(+), 9 deletions(-) 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..379b38e6 100644 --- a/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java +++ b/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java @@ -24,6 +24,7 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import android.media.ExifInterface; import android.net.Uri; import android.opengl.GLES10; import android.os.Build; @@ -304,19 +305,71 @@ private void onSaveClicked() { private void saveImage(Bitmap croppedImage) { if (croppedImage != null) { - final Bitmap b = croppedImage; - CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving), - new Runnable() { - public void run() { - saveOutput(b); - } - }, handler - ); + try { + ExifInterface exif = new ExifInterface(CropUtil.getPath(this,sourceUri)); + int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); + final Bitmap b = rotateBitmap(croppedImage, orientation); + CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving), + new Runnable() { + public void run() { + if (b != null) { + saveOutput(b); + b.recycle(); + } + } + }, handler + ); + } catch (IOException e) { + e.printStackTrace(); + } } else { finish(); } } + private Bitmap rotateBitmap(Bitmap bitmap, int orientation) { + Matrix matrix = new Matrix(); + switch (orientation) { + case ExifInterface.ORIENTATION_NORMAL: + return bitmap; + case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: + matrix.setScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_180: + matrix.setRotate(180); + break; + case ExifInterface.ORIENTATION_FLIP_VERTICAL: + matrix.setRotate(180); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_TRANSPOSE: + matrix.setRotate(90); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_90: + matrix.setRotate(90); + break; + case ExifInterface.ORIENTATION_TRANSVERSE: + matrix.setRotate(-90); + matrix.postScale(-1, 1); + break; + case ExifInterface.ORIENTATION_ROTATE_270: + matrix.setRotate(-90); + break; + default: + return bitmap; + } + try { + //Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + //bitmap.recycle(); + //return bmRotated; + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } catch (OutOfMemoryError e) { + e.printStackTrace(); + return null; + } + } + private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) { // Release memory now clearImageView(); 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 f0601557..866400c0 100644 --- a/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java +++ b/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java @@ -167,7 +167,7 @@ public void onActivityStarted(MonitoredActivity activity) { * @param uri The Uri to query. * @author paulburke */ - private static String getPath(final Context context, final Uri uri) { + public static String getPath(final Context context, final Uri uri) { final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; From 3c17628bee5d2a0b9041debb9e8fe277153f8cac Mon Sep 17 00:00:00 2001 From: Saul Date: Tue, 8 Dec 2015 19:11:26 +0100 Subject: [PATCH 3/4] withJpgQuality() added (from braintrapp/android-crop) --- .../android/crop/example/MainActivity.java | 6 +++--- .../main/java/com/soundcloud/android/crop/Crop.java | 13 +++++++++++++ .../soundcloud/android/crop/CropImageActivity.java | 11 ++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) 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..67a1c611 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,7 +1,5 @@ package com.soundcloud.android.crop.example; -import com.soundcloud.android.crop.Crop; - import android.app.Activity; import android.content.Intent; import android.net.Uri; @@ -11,6 +9,8 @@ import android.widget.ImageView; import android.widget.Toast; +import com.soundcloud.android.crop.Crop; + import java.io.File; public class MainActivity extends Activity { @@ -51,7 +51,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent result) private void beginCrop(Uri source) { Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped")); - Crop.of(source, destination).asSquare().start(this); + Crop.of(source, destination).asSquare().withJpgQuality(90).start(this); } private void handleCrop(int resultCode, Intent result) { 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 379b38e6..c0c51b31 100644 --- a/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java +++ b/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java @@ -429,12 +429,21 @@ private void clearImageView() { } private void saveOutput(Bitmap croppedImage) { + 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); + } + } + if (saveUri != null) { OutputStream outputStream = null; try { outputStream = 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); From 255ff89c30b4e275545145d5fe0e8c07325e3c06 Mon Sep 17 00:00:00 2001 From: Saul Date: Tue, 8 Dec 2015 22:59:56 +0100 Subject: [PATCH 4/4] Fix Rotation Errors, Photo Action, Clean code --- .../android/crop/example/MainActivity.java | 26 +++- example/src/main/res/menu/activity_main.xml | 3 + .../android/crop/CropImageActivity.java | 123 +++++----------- .../com/soundcloud/android/crop/CropUtil.java | 132 ++++++++---------- 4 files changed, 121 insertions(+), 163 deletions(-) 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 67a1c611..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 @@ -4,6 +4,8 @@ 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; @@ -16,12 +18,17 @@ 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().withJpgQuality(90).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/src/main/java/com/soundcloud/android/crop/CropImageActivity.java b/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java index c0c51b31..30d57a48 100644 --- a/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java +++ b/lib/src/main/java/com/soundcloud/android/crop/CropImageActivity.java @@ -24,7 +24,6 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; -import android.media.ExifInterface; import android.net.Uri; import android.opengl.GLES10; import android.os.Build; @@ -296,80 +295,27 @@ 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) { - try { - ExifInterface exif = new ExifInterface(CropUtil.getPath(this,sourceUri)); - int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); - final Bitmap b = rotateBitmap(croppedImage, orientation); - CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__saving), - new Runnable() { - public void run() { - if (b != null) { - saveOutput(b); - b.recycle(); - } - } - }, handler - ); - } catch (IOException e) { - e.printStackTrace(); - } + 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); } else { finish(); } } - private Bitmap rotateBitmap(Bitmap bitmap, int orientation) { - Matrix matrix = new Matrix(); - switch (orientation) { - case ExifInterface.ORIENTATION_NORMAL: - return bitmap; - case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: - matrix.setScale(-1, 1); - break; - case ExifInterface.ORIENTATION_ROTATE_180: - matrix.setRotate(180); - break; - case ExifInterface.ORIENTATION_FLIP_VERTICAL: - matrix.setRotate(180); - matrix.postScale(-1, 1); - break; - case ExifInterface.ORIENTATION_TRANSPOSE: - matrix.setRotate(90); - matrix.postScale(-1, 1); - break; - case ExifInterface.ORIENTATION_ROTATE_90: - matrix.setRotate(90); - break; - case ExifInterface.ORIENTATION_TRANSVERSE: - matrix.setRotate(-90); - matrix.postScale(-1, 1); - break; - case ExifInterface.ORIENTATION_ROTATE_270: - matrix.setRotate(-90); - break; - default: - return bitmap; - } - try { - //Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - //bitmap.recycle(); - //return bmRotated; - return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); - } catch (OutOfMemoryError e) { - e.printStackTrace(); - return null; - } - } - private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) { // Release memory now clearImageView(); @@ -390,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); @@ -397,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 (" @@ -433,38 +390,31 @@ private void saveOutput(Bitmap croppedImage) { Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { - if (extras.containsKey(Crop.Extra.JPG_QUALITY)) { + if (extras.containsKey(Crop.Extra.JPG_QUALITY)) jpgQuality = extras.getInt(Crop.Extra.JPG_QUALITY); - } } - if (saveUri != null) { - OutputStream outputStream = null; - try { - outputStream = getContentResolver().openOutputStream(saveUri); + OutputStream outputStream = null; + try { + if (saveUri != null) { + outputStream = this.getContentResolver().openOutputStream(saveUri); if (outputStream != null) { 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(); } }); @@ -495,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 866400c0..8e4dbf28 100644 --- a/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java +++ b/lib/src/main/java/com/soundcloud/android/crop/CropUtil.java @@ -72,20 +72,6 @@ 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) { @@ -97,64 +83,6 @@ public static File getFromMediaUri(Context context, ContentResolver resolver, Ur return null; } - public static void startBackgroundJob(MonitoredActivity activity, - String title, String message, Runnable job, Handler handler) { - // 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); - new Thread(new BackgroundJob(activity, job, dialog, handler)).start(); - } - - private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable { - - private final MonitoredActivity activity; - private final ProgressDialog dialog; - private final Runnable job; - private final Handler handler; - private final Runnable cleanupRunner = new Runnable() { - public void run() { - activity.removeLifeCycleListener(BackgroundJob.this); - if (dialog.getWindow() != null) dialog.dismiss(); - } - }; - - public BackgroundJob(MonitoredActivity activity, Runnable job, - ProgressDialog dialog, Handler handler) { - this.activity = activity; - this.dialog = dialog; - this.job = job; - this.activity.addLifeCycleListener(this); - this.handler = handler; - } - - public void run() { - try { - job.run(); - } finally { - handler.post(cleanupRunner); - } - } - - @Override - public void onActivityDestroyed(MonitoredActivity activity) { - // We get here only when the onDestroyed being called before - // the cleanupRunner. So, run it now and remove it from the queue - cleanupRunner.run(); - handler.removeCallbacks(cleanupRunner); - } - - @Override - public void onActivityStopped(MonitoredActivity activity) { - dialog.hide(); - } - - @Override - public void onActivityStarted(MonitoredActivity activity) { - dialog.show(); - } - } - /** * 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 @@ -246,7 +174,7 @@ else if ("file".equalsIgnoreCase(uri.getScheme())) { * @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) { + String[] selectionArgs) { Cursor cursor = null; final String column = "_data"; final String[] projection = { column }; @@ -311,4 +239,62 @@ private static boolean isGoogleDriveUri(Uri uri) { 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 + // the thread will be done before the activity getting destroyed + ProgressDialog dialog = ProgressDialog.show( + activity, title, message, true, false); + new Thread(new BackgroundJob(activity, job, dialog, handler)).start(); + } + + private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter implements Runnable { + + private final MonitoredActivity activity; + private final ProgressDialog dialog; + private final Runnable job; + private final Handler handler; + private final Runnable cleanupRunner = new Runnable() { + public void run() { + activity.removeLifeCycleListener(BackgroundJob.this); + if (dialog.getWindow() != null) dialog.dismiss(); + } + }; + + public BackgroundJob(MonitoredActivity activity, Runnable job, + ProgressDialog dialog, Handler handler) { + this.activity = activity; + this.dialog = dialog; + this.job = job; + this.activity.addLifeCycleListener(this); + this.handler = handler; + } + + public void run() { + try { + job.run(); + } finally { + handler.post(cleanupRunner); + } + } + + @Override + public void onActivityDestroyed(MonitoredActivity activity) { + // We get here only when the onDestroyed being called before + // the cleanupRunner. So, run it now and remove it from the queue + cleanupRunner.run(); + handler.removeCallbacks(cleanupRunner); + } + + @Override + public void onActivityStopped(MonitoredActivity activity) { + dialog.hide(); + } + + @Override + public void onActivityStarted(MonitoredActivity activity) { + dialog.show(); + } + } }