Image Orientation - Android

I have been struggling with this bug on and off for the last month or so. Everytime that I think I have fixed it it seems to come back in some form.

It is the old Android "Image Rotated 90 degrees" bug. I have read countless Posts on here (StackOverFlow) aswell as tried numerous methods but just cannot seem to fix it.

I am still getting images that are rotated incorrectly.

In my application a user chooses his/her profile Picture, which is then set to an ImageView. The image is chosen from the Phones Gallery

Two days ago I implemented the Following Code, this worked for all the images I tested it with on my Phone. However when one of my Beta testers tried it, his images were once again rotated. He sent me the images for testing but they were displaying fine on my Phone. Hence why I am getting more and more frustrated.

This is the method I am using to get the Images orientation:

// Gets an Images Orientation
public static int getOrientationEXIF(Context context, Uri uri) {

    int orientation = 0;

    try {

        ExifInterface exif = new ExifInterface(uri.getPath());

        orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);

        switch (orientation) {

            case ExifInterface.ORIENTATION_ROTATE_90:
                orientation = 90;
                return orientation;

            case ExifInterface.ORIENTATION_ROTATE_180:
                orientation = 180;
                return orientation;

        }

    } catch (IOException e) {
        e.printStackTrace();
    }

    return 0;
}

I then get a rotated Bitmap using this Method:

// Rotate a Bitmap
public static Bitmap rotate(float rotationValue, String filePath) {
    Bitmap original= BitmapFactory.decodeFile(filePath);

    int width = original.getWidth();

    int height = original.getHeight();

    Matrix matrix = new Matrix();

    matrix.postRotate(rotationValue);

    Bitmap rotated = Bitmap.createBitmap(original, 0, 0, width, height, matrix, true);

    return rotated;
}

I am just not sure what to do anymore.

I would really like it if someone could help me figure this out

Thank you in advance


UPDATE

I just saw the following line of Code in my Log after implementing the suggested Methods:

JHEAD can't open 'file:/external/images/media/3885'

I am not sure what this means


UPDATE #2

I think I may have fixed the problem, I got the proper image path for the file.


Solution 1:

You need to account for all orientations not just 90 or 180. I am using this

    File curFile = new File("path-to-file"); // ... This is an image file from my device.
    Bitmap rotatedBitmap;

            try {
                ExifInterface exif = new ExifInterface(curFile.getPath());
                int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
                int rotationInDegrees = exifToDegrees(rotation);
                Matrix matrix = new Matrix();
                if (rotation != 0f) {matrix.preRotate(rotationInDegrees);}
                rotatedBitmap = Bitmap.createBitmap(bitmap,0,0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);


            }catch(IOException ex){
                Log.e(TAG, "Failed to get Exif data", ex);
            }

and:

 /**
 * Gets the Amount of Degress of rotation using the exif integer to determine how much
 * we should rotate the image.
 * @param exifOrientation - the Exif data for Image Orientation
 * @return - how much to rotate in degress
 */
private static int exifToDegrees(int exifOrientation) {
    if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) { return 90; }
    else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {  return 180; }
    else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {  return 270; }
    return 0;
}

Solution 2:

This problem really sucks ! I notice this is an issue when choosing photos rather than taking them. I found the answer buried in the code for this cropping lib which seemed to always display things correctly https://github.com/jdamcd/android-crop while cropping (despite it sometimes returning things with wrong orientation afterwards). Anyways, first start off by choosing the photo the way I choose in this answer: Pick image from fragment always return resultcode 0 in some devices

Next do this where you need to:

private void setRotationVariables(Uri uri)
{
   m_rotationInDegrees = ImageOrientationUtil.getExifRotation(ImageOrientationUtil
        .getFromMediaUri(
            this,
            getContentResolver(),
            uri));
}

Here is the class:

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
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;


public class ImageOrientationUtil
{

private static final String SCHEME_FILE = "file";
private static final String SCHEME_CONTENT = "content";

public static void closeSilently(@Nullable Closeable c) {
    if (c == null) return;
    try {
        c.close();
    } catch (Throwable t) {
        // Do nothing
    }
}

public static int getExifRotation(File imageFile) {
    if (imageFile == null) return 0;
    try {
        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
        // We only recognize a subset of orientation tag values
        switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                return 90;
            case ExifInterface.ORIENTATION_ROTATE_180:
                return 180;
            case ExifInterface.ORIENTATION_ROTATE_270:
                return 270;
            default:
                return ExifInterface.ORIENTATION_UNDEFINED;
        }
    } catch (IOException e) {
      //  Log.e("Error getting Exif data", e);
        return 0;
    }
}

@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);
        }
        return new File(tempFilename);
    } catch (IOException ignored) {
        // Nothing we can do
    } finally {
        closeSilently(input);
        closeSilently(output);
    }
    return null;
}

}

Solution 3:

mochilogic answer is very good but his comment is also correct: sorry, " do this where you need to " is so vague.."

I cannot add all of this in comment to mochilogic answer so I will write this here: If you dont like touse it in setRotationVariables(data.getData) - this is another way to use the class ImageOrientationUtil from his answer and this method:

            private void setRotationVariables(Uri uri)
            {
                m_rotationInDegrees = ImageOrientationUtil.getExifRotation           
               (ImageOrientationUtil.getFromMediaUri(
                this,
                getContentResolver(),
               uri));
              }

You can send Uri from the galery to this method make it return the correct rotation in degrees by memebr as he does or by value as I did:

  private static int setRotationVariables(Uri uri) {
    int rotationInDegrees = ImageOrientationUtil.getExifRotation(ImageOrientationUtil
            .getFileFromMediaUri(
                    appCtx,
                    appCtx.getContentResolver(),
                    uri));
    Log.d(TAG, "setRotationVariables:" + "according to original Image Uri Exif details we need to rotate in "+rotationInDegrees + " Degrees");
    return rotationInDegrees;
}

and then on the calling function after you scale your Uri to bitmap you can create bitmap using this rotationInDegrees with a matrix.

you can see it in my code here in this method I take Uri and scale it and rotate it and then returns it as bitmap.

but first - basicaly this is what you need:

    int rotationDegree = setRotationVariables(uri);

   if (rotationDegree > 0) {
        Matrix matrix = new Matrix();
        matrix.setRotate(rotationDegree);
        Log.w(TAG, "recreating bitmap with rotation of " + rotationDegree + " degrees" );
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }

This is the full method code if anyones need it..

    public static Bitmap getScaledBitmapFromUri(Uri uri) throws FileNotFoundException, IOException {
           final int TRY_SCALE_TO_THIS_SIZE = 1024;
            Log.d(TAG, "getScaledBitmapFromUri:: calling       setRotationVariables() to figure rotationDegree");
           int rotationDegree = setRotationVariables(uri);
           Context ctx = MessagingApp.getContext();
           InputStream input = ctx.getContentResolver().openInputStream(uri);

           BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
           onlyBoundsOptions.inJustDecodeBounds = true;
           onlyBoundsOptions.inDither = true;//optional
           onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
           BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
           input.close();

           if ( (onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1) )
                return null;
           int BiggestOriginalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
           //we will add 1 to Math.round (BiggestOriginalSize / (double)TRY_SCALE_TO_THIS_SIZE) in order to harden the scaling(we need smaller images for this project!)
    double ratio = (BiggestOriginalSize > TRY_SCALE_TO_THIS_SIZE) ? (1 + Math.round(BiggestOriginalSize / (double) TRY_SCALE_TO_THIS_SIZE)) : 1.0;
    Log.w(TAG, "getScaledBitmapFromUri:: originalSize: " + BiggestOriginalSize + "PX, TRY_SCALE_TO_THIS_SIZE (if original is bigger):" + TRY_SCALE_TO_THIS_SIZE +"PX");

    BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
    bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);   //this one will give abstract results (sometimes bigger then TRY_SCALE_TO_THIS_SIZE)
    Log.w(TAG, format("bitmapOptions.inSampleSize: " + bitmapOptions.inSampleSize));
    bitmapOptions.inJustDecodeBounds = false;   //check this out!!! maybe delete?

    bitmapOptions.inDither = true;//optional
    bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    //bitmapOptions.rogetrotationInDegrees
    input = ctx.getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
    //bitmap = findExactInSampleSize(onlyBoundsOptions, TRY_SCALE_TO_THIS_SIZE, bitmap);   // this one will never give bigger length then TRY_SCALE_TO_THIS_SIZE
    if (rotationDegree > 0) {
        Matrix matrix = new Matrix();
        matrix.setRotate(rotationDegree);
        Log.w(TAG, "recreating bitmap with rotation of " + rotationDegree + " degrees" );
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    }
    Log.w(TAG, "after decodeStream : bitmap.getWidth(): " + bitmap.getWidth() + "PX, bitmap.getHeight(): " + bitmap.getHeight() +"PX.");
    input.close();
    return bitmap;
}