From 4ea04bb576226b54fa64cedd70abd3448ba1b0bf Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 11 Apr 2017 22:50:27 -0400 Subject: [PATCH] Re-orients images when downscaled according to EXIF data if it's specified. Closes #106 --- app/build.gradle | 1 + .../tusky/DownsizeImageTask.java | 84 ++++++++++++++++++- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index bb8cac27d..7d39791f2 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,6 +34,7 @@ dependencies { compile 'com.android.support:recyclerview-v7:25.3.1' compile 'com.android.support:support-v13:25.3.1' compile 'com.android.support:design:25.3.1' + compile 'com.android.support:exifinterface:25.3.1' compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.pkmmte.view:circularimageview:1.1' compile 'com.github.peter9870:sparkbutton:master' diff --git a/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java b/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java index b964e3654..e9fcc11a9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java +++ b/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java @@ -18,11 +18,14 @@ package com.keylesspalace.tusky; import android.content.ContentResolver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Matrix; import android.net.Uri; import android.os.AsyncTask; +import android.support.media.ExifInterface; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -39,13 +42,85 @@ class DownsizeImageTask extends AsyncTask { this.listener = listener; } + private static Bitmap reorientBitmap(Bitmap bitmap, int orientation) { + Matrix matrix = new Matrix(); + switch (orientation) { + default: + 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; + } + } + try { + Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), + bitmap.getHeight(), matrix, true); + bitmap.recycle(); + return result; + } catch (OutOfMemoryError e) { + return null; + } + } + + private static int getOrientation(Uri uri, ContentResolver contentResolver) { + InputStream inputStream; + try { + inputStream = contentResolver.openInputStream(uri); + } catch (FileNotFoundException e) { + return ExifInterface.ORIENTATION_UNDEFINED; + } + if (inputStream == null) { + return ExifInterface.ORIENTATION_UNDEFINED; + } + ExifInterface exifInterface; + try { + exifInterface = new ExifInterface(inputStream); + } catch (IOException e) { + IOUtils.closeQuietly(inputStream); + return ExifInterface.ORIENTATION_UNDEFINED; + } + int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + IOUtils.closeQuietly(inputStream); + return orientation; + } + private static int calculateInSampleSize(int width, int height, int requiredScale) { int inSampleSize = 1; if (height > requiredScale || width > requiredScale) { final int halfHeight = height / 2; final int halfWidth = width / 2; - // Calculate the largest inSampleSize value that is a power of 2 and keeps both - // height and width larger than the requested height and width. + /* Calculate the largest inSampleSize value that is a power of 2 and keeps both height + * and width larger than the requested height and width. */ while (halfHeight / inSampleSize >= requiredScale && halfWidth / inSampleSize >= requiredScale) { inSampleSize *= 2; @@ -71,6 +146,8 @@ class DownsizeImageTask extends AsyncTask { int beforeWidth = options.outWidth; int beforeHeight = options.outHeight; IOUtils.closeQuietly(inputStream); + // Get EXIF data, for orientation info. + int orientation = getOrientation(uri, contentResolver); // Then use that information to determine how much to compress. ByteArrayOutputStream stream = new ByteArrayOutputStream(); /* Unfortunately, there isn't a determined worst case compression ratio for image @@ -79,7 +156,7 @@ class DownsizeImageTask extends AsyncTask { * many cases, so it should only iterate once, but the loop is used to be absolutely * sure it gets downsized to below the limit. */ int iterations = 0; - int scaledImageSize = 4096; + int scaledImageSize = 1024; do { stream.reset(); try { @@ -101,6 +178,7 @@ class DownsizeImageTask extends AsyncTask { if (scaledBitmap == null) { return false; } + reorientBitmap(scaledBitmap, orientation); Bitmap.CompressFormat format; /* It's not likely the user will give transparent images over the upload limit, but * if they do, make sure the transparency is retained. */