Why does an image captured using camera intent gets rotated on some devices on Android? Why does an image captured using camera intent gets rotated on some devices on Android? android android

Why does an image captured using camera intent gets rotated on some devices on Android?


Most phone cameras are landscape, meaning if you take the photo in portrait, the resulting photos will be rotated 90 degrees. In this case, the camera software should populate the Exif data with the orientation that the photo should be viewed in.

Note that the below solution depends on the camera software/device manufacturer populating the Exif data, so it will work in most cases, but it is not a 100% reliable solution.

ExifInterface ei = new ExifInterface(photoPath);int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION,                                     ExifInterface.ORIENTATION_UNDEFINED);Bitmap rotatedBitmap = null;switch(orientation) {    case ExifInterface.ORIENTATION_ROTATE_90:        rotatedBitmap = rotateImage(bitmap, 90);        break;    case ExifInterface.ORIENTATION_ROTATE_180:        rotatedBitmap = rotateImage(bitmap, 180);        break;    case ExifInterface.ORIENTATION_ROTATE_270:        rotatedBitmap = rotateImage(bitmap, 270);        break;    case ExifInterface.ORIENTATION_NORMAL:    default:        rotatedBitmap = bitmap;}

Here is the rotateImage method:

public static Bitmap rotateImage(Bitmap source, float angle) {    Matrix matrix = new Matrix();    matrix.postRotate(angle);    return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(),                               matrix, true);}


By combining Jason Robinson's answer with Felix's answer and filling the missing parts, here is the final complete solution for this issue that will do the following after testing it on Android Android 4.1 (Jelly Bean), Android 4.4 (KitKat) and Android 5.0 (Lollipop).

Steps

  1. Scale down the image if it was bigger than 1024x1024.

  2. Rotate the image to the right orientation only if it was rotate 90, 180 or 270 degree.

  3. Recycle the rotated image for memory purposes.

Here is the code part:

Call the following method with the current Context and the image URI that you want to fix

/** * This method is responsible for solving the rotation issue if exist. Also scale the images to * 1024x1024 resolution * * @param context       The current context * @param selectedImage The Image URI * @return Bitmap image results * @throws IOException */public static Bitmap handleSamplingAndRotationBitmap(Context context, Uri selectedImage)        throws IOException {    int MAX_HEIGHT = 1024;    int MAX_WIDTH = 1024;    // First decode with inJustDecodeBounds=true to check dimensions    final BitmapFactory.Options options = new BitmapFactory.Options();    options.inJustDecodeBounds = true;    InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);    BitmapFactory.decodeStream(imageStream, null, options);    imageStream.close();    // Calculate inSampleSize    options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT);    // Decode bitmap with inSampleSize set    options.inJustDecodeBounds = false;    imageStream = context.getContentResolver().openInputStream(selectedImage);    Bitmap img = BitmapFactory.decodeStream(imageStream, null, options);    img = rotateImageIfRequired(context, img, selectedImage);    return img;}

Here is the CalculateInSampleSize method from the pre mentioned source:

/**  * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding  * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates  * the closest inSampleSize that will result in the final decoded bitmap having a width and  * height equal to or larger than the requested width and height. This implementation does not  * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but  * results in a larger bitmap which isn't as useful for caching purposes.  *  * @param options   An options object with out* params already populated (run through a decode*  *                  method with inJustDecodeBounds==true  * @param reqWidth  The requested width of the resulting bitmap  * @param reqHeight The requested height of the resulting bitmap  * @return The value to be used for inSampleSize  */private static int calculateInSampleSize(BitmapFactory.Options options,                                         int reqWidth, int reqHeight) {    // Raw height and width of image    final int height = options.outHeight;    final int width = options.outWidth;    int inSampleSize = 1;    if (height > reqHeight || width > reqWidth) {        // Calculate ratios of height and width to requested height and width        final int heightRatio = Math.round((float) height / (float) reqHeight);        final int widthRatio = Math.round((float) width / (float) reqWidth);        // Choose the smallest ratio as inSampleSize value, this will guarantee a final image        // with both dimensions larger than or equal to the requested height and width.        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;        // This offers some additional logic in case the image has a strange        // aspect ratio. For example, a panorama may have a much larger        // width than height. In these cases the total pixels might still        // end up being too large to fit comfortably in memory, so we should        // be more aggressive with sample down the image (=larger inSampleSize).        final float totalPixels = width * height;        // Anything more than 2x the requested pixels we'll sample down further        final float totalReqPixelsCap = reqWidth * reqHeight * 2;        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {            inSampleSize++;        }    }    return inSampleSize;}

Then comes the method that will check the current image orientation to decide the rotation angle

 /** * Rotate an image if required. * * @param img           The image bitmap * @param selectedImage Image URI * @return The resulted Bitmap after manipulation */private static Bitmap rotateImageIfRequired(Context context, Bitmap img, Uri selectedImage) throws IOException {InputStream input = context.getContentResolver().openInputStream(selectedImage);ExifInterface ei;if (Build.VERSION.SDK_INT > 23)    ei = new ExifInterface(input);else    ei = new ExifInterface(selectedImage.getPath());    int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);    switch (orientation) {        case ExifInterface.ORIENTATION_ROTATE_90:            return rotateImage(img, 90);        case ExifInterface.ORIENTATION_ROTATE_180:            return rotateImage(img, 180);        case ExifInterface.ORIENTATION_ROTATE_270:            return rotateImage(img, 270);        default:            return img;    }}

Finally the rotation method itself

private static Bitmap rotateImage(Bitmap img, int degree) {    Matrix matrix = new Matrix();    matrix.postRotate(degree);    Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);    img.recycle();    return rotatedImg;}

-Don't forget to vote up for those guys answers for their efforts and Shirish Herwade who asked this helpful question.


It's easy to detect the image orientation and replace the bitmap using:

 /** * Rotate an image if required. * @param img * @param selectedImage * @return */private static Bitmap rotateImageIfRequired(Context context,Bitmap img, Uri selectedImage) {    // Detect rotation    int rotation = getRotation(context, selectedImage);    if (rotation != 0) {        Matrix matrix = new Matrix();        matrix.postRotate(rotation);        Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);        img.recycle();        return rotatedImg;    }    else{        return img;    }}/** * Get the rotation of the last image added. * @param context * @param selectedImage * @return */private static int getRotation(Context context,Uri selectedImage) {    int rotation = 0;    ContentResolver content = context.getContentResolver();    Cursor mediaCursor = content.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,                                       new String[] { "orientation", "date_added" },                                       null, null, "date_added desc");    if (mediaCursor != null && mediaCursor.getCount() != 0) {        while(mediaCursor.moveToNext()){            rotation = mediaCursor.getInt(0);            break;        }    }    mediaCursor.close();    return rotation;}

To avoid Out of memories with big images, I'd recommend you to rescale the image using:

private static final int MAX_HEIGHT = 1024;private static final int MAX_WIDTH = 1024;public static Bitmap decodeSampledBitmap(Context context, Uri selectedImage)    throws IOException {    // First decode with inJustDecodeBounds=true to check dimensions    final BitmapFactory.Options options = new BitmapFactory.Options();    options.inJustDecodeBounds = true;    InputStream imageStream = context.getContentResolver().openInputStream(selectedImage);    BitmapFactory.decodeStream(imageStream, null, options);    imageStream.close();    // Calculate inSampleSize    options.inSampleSize = calculateInSampleSize(options, MAX_WIDTH, MAX_HEIGHT);    // Decode bitmap with inSampleSize set    options.inJustDecodeBounds = false;    imageStream = context.getContentResolver().openInputStream(selectedImage);    Bitmap img = BitmapFactory.decodeStream(imageStream, null, options);    img = rotateImageIfRequired(img, selectedImage);    return img;}

It's not posible to use ExifInterface to get the orientation because an Android OS issue:https://code.google.com/p/android/issues/detail?id=19268

And here is calculateInSampleSize

/** * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates * the closest inSampleSize that will result in the final decoded bitmap having a width and * height equal to or larger than the requested width and height. This implementation does not * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but * results in a larger bitmap which isn't as useful for caching purposes. * * @param options   An options object with out* params already populated (run through a decode* *                  method with inJustDecodeBounds==true * @param reqWidth  The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @return The value to be used for inSampleSize */public static int calculateInSampleSize(BitmapFactory.Options options,                                        int reqWidth, int reqHeight) {    // Raw height and width of image    final int height = options.outHeight;    final int width = options.outWidth;    int inSampleSize = 1;    if (height > reqHeight || width > reqWidth) {        // Calculate ratios of height and width to requested height and width        final int heightRatio = Math.round((float) height / (float) reqHeight);        final int widthRatio = Math.round((float) width / (float) reqWidth);        // Choose the smallest ratio as inSampleSize value, this will guarantee a final image        // with both dimensions larger than or equal to the requested height and width.        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;        // This offers some additional logic in case the image has a strange        // aspect ratio. For example, a panorama may have a much larger        // width than height. In these cases the total pixels might still        // end up being too large to fit comfortably in memory, so we should        // be more aggressive with sample down the image (=larger inSampleSize).        final float totalPixels = width * height;        // Anything more than 2x the requested pixels we'll sample down further        final float totalReqPixelsCap = reqWidth * reqHeight * 2;        while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {            inSampleSize++;        }    }    return inSampleSize;}