ivpusic / react-native-image-crop-picker

iOS/Android image picker with support for camera, video, configurable compression, multiple images and cropping
MIT License
6.12k stars 1.56k forks source link

Android - Inverted width and height for portrait images #1659

Open scgough opened 3 years ago

scgough commented 3 years ago

Version

Tell us which versions you are using:

Platform

Android

Expected behaviour

When selecting a portrait image from the gallery, the width and height should be reported correctly.

Actual behaviour

The width and height are switched. The image is visually correct on-screen (i.e. viewing at the correct dimensions in portrait) but then if you try and process it further in your own app trouble arises from the incorrect dimensions.

Landscape images seem to be fine.

Steps to reproduce

  1. Take a portrait picture on your device

  2. Select it using the picker in this library

  3. Look at the reported width and height πŸ˜…

More info

At the moment, I'm not sure if this is a bug with Android itself / by design and I'm not sure if it is isolated to certain makes/models of device. I can reproduce this using Samsung and Google Pixel 4a devices.

I actually think the issue is that the Android camera app applies an orientation of 90 degrees to a portrait image and at some point along the line this causes the width and height to switch. From what I've seen this 'issue' has been around for a looooong time.

Possible work around

I've made this change to the file below and it is working but feels rather hacky....I'd love to get some feedback! It checks for the original image orientation and sets a bool for invertDims to decide what to report back as the width and height in the WritableMap image result.

I've added in getRotationInDegreesForOrientationTag (previously in the Compression.java file).

PickerModule.java

int getRotationInDegreesForOrientationTag(int orientationTag) {
        switch(orientationTag){
            case ExifInterface.ORIENTATION_ROTATE_90:
                return 90;
            case ExifInterface.ORIENTATION_ROTATE_270:
                return -90;
            case ExifInterface.ORIENTATION_ROTATE_180:
                return 180;
            default:
                return 0;
        }
    }

    private WritableMap getImage(final Activity activity, String path) throws Exception {
        WritableMap image = new WritableNativeMap();

        if (path.startsWith("http://") || path.startsWith("https://")) {
            throw new Exception("Cannot select remote files");
        }
        BitmapFactory.Options original = validateImage(path);

        //--- NEW CODE ---
        ExifInterface originalExif = new ExifInterface(path);
        int originalOrientation = originalExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
        int rotationAngleInDegrees = getRotationInDegreesForOrientationTag(originalOrientation);
        boolean invertDims = rotationAngleInDegrees == 90 || rotationAngleInDegrees == 270;
       //--------------------

        // if compression options are provided image will be compressed. If none options is provided,
        // then original image will be returned
        File compressedImage = compression.compressImage(this.reactContext, options, path, original);
        String compressedImagePath = compressedImage.getPath();
        BitmapFactory.Options options = validateImage(compressedImagePath);
        long modificationDate = new File(path).lastModified();

        image.putString("path", "file://" + compressedImagePath);
        image.putInt("width", invertDims ? options.outHeight : options.outWidth);   //<-- NEW CHECK
        image.putInt("height", invertDims ? options.outWidth : options.outHeight);  //<-- NEW CHECK
       ...

I'm also happy to add a PR for this if the consensus is it should go futher.

marcoizzo commented 2 years ago

+1

Exilz commented 2 years ago

Same issue here, in the meantime I downgraded to 0.35.3. It looks like the problem is gone for me.

Tymofiev commented 2 years ago

The same on my environment, if scale a bit - problem disappears. Works fine on version 0.36.0.

GaylordP commented 2 years ago

Same for 0.37.3 :/

vlad-timotei commented 2 years ago

The issue persists in 0.37.3.

I tried to apply @scgough 's patch but I think the patch doesn't apply to the latest code base, as it didn't work any more, but your idea was life saving, so thanks! I tried a similar approach, but also altering the bitmap generated for the original image.

I'm not sure if this approach is the best one, but so far it works on all tested devices. Let me know if this works for you and I could open a PR.

Here's the patch:


--- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
@@ -19,6 +19,7 @@ import android.webkit.MimeTypeMap;

 import androidx.core.app.ActivityCompat;
 import androidx.core.content.FileProvider;
+import androidx.exifinterface.media.ExifInterface;

 import com.facebook.react.bridge.ActivityEventListener;
 import com.facebook.react.bridge.Callback;
@@ -585,13 +586,18 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
         return path;
     }

-    private BitmapFactory.Options validateImage(String path) throws Exception {
+    private BitmapFactory.Options validateImage(String path, boolean invert) throws Exception {
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inJustDecodeBounds = true;
         options.inPreferredConfig = Bitmap.Config.RGB_565;
         options.inDither = true;

         BitmapFactory.decodeFile(path, options);
+        if (invert) {
+            int temp = options.outHeight;
+            options.outHeight = options.outWidth;
+            options.outWidth = temp;
+        }

         if (options.outMimeType == null || options.outWidth == 0 || options.outHeight == 0) {
             throw new Exception("Invalid image selected");
@@ -606,18 +612,23 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
         if (path.startsWith("http://") || path.startsWith("https://")) {
             throw new Exception("Cannot select remote files");
         }
-        BitmapFactory.Options original = validateImage(path);
+        ExifInterface originalExif = new ExifInterface(path);
+        int originalOrientation = originalExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
+        boolean invertDimensions = originalOrientation == ExifInterface.ORIENTATION_ROTATE_90 || originalOrientation == ExifInterface.ORIENTATION_ROTATE_270;
+        BitmapFactory.Options original = validateImage(path, invertDimensions);

         // if compression options are provided image will be compressed. If none options is provided,
         // then original image will be returned
         File compressedImage = compression.compressImage(this.reactContext, options, path, original);
         String compressedImagePath = compressedImage.getPath();
-        BitmapFactory.Options options = validateImage(compressedImagePath);
+        BitmapFactory.Options options = validateImage(compressedImagePath, false);
         long modificationDate = new File(path).lastModified();

         image.putString("path", "file://" + compressedImagePath);
-        image.putInt("width", options.outWidth);
-        image.putInt("height", options.outHeight);
+
+        image.putInt("width", invertDimensions ? options.outHeight : options.outWidth);
+        image.putInt("height", invertDimensions ? options.outWidth : options.outHeight);
+
         image.putString("mime", options.outMimeType);
         image.putInt("size", (int) new File(compressedImagePath).length());
         image.putString("modificationDate", String.valueOf(modificationDate));```
gigadeplex commented 2 years ago

why isn't this fixed yet? they seem to be patching every damn thing else.

moriax commented 2 years ago

we have the same problem on a Galaxy Z Fold2 5G

godot007 commented 1 year ago

try this

rubenLooplan commented 8 months ago

+1

Wicat21 commented 7 months ago

+1

DanushkaPerusinghe commented 3 months ago

+1