ImageManipulator resize thinks portrait images are landscape on Galaxy S phones

I’m happy to post this as a Github issue if it’s warranted. Wanted to check in here first in case perhaps I’m using ImageManipulator wrong or if there’s perhaps a way to pass along orientation information to prevent this. Also curious if anyone finds any other phones that have this issue (I can only prove it’s a problem on the S6 and S8 and that it’s not a problem on the Moto G4 Play so far).

As I understand it from the docs (ImageManipulator - Expo Documentation), if I call ImageManipulator.manipulate(image.uri, { height: 640 }), it will resize the image so the height is 640px and the width is correspondingly smaller/ larger, but the image is still the same aspect ratio. So a 3x4 portrait image would get resized down to 480x640 in this example. In our case, we’re trying to get into a 640x640 box to adhere to our API’s upload limit for images.

I get my original image, check if the height is larger than the width, and call ImageManipulator.manipulate(image.uri, { height: 640 }). If it’s the other way around, I call ImageManipulator.manipulate(image.uri, { width: 640 }). On iOS, this seems to work fine for all images. On Android, it always seems to work for landscape photos. For portrait photos, it works on some Android phones but not others. On the ever-popular Galaxy S8, it seems to think the portrait image is actually landscape, and resizes it accordingly.

I have created a Snack to demonstrate the issue here: image manipulator android orientation issue - Snack.

On my Moto G4 Play (Android 7.0), a portrait image resizes correctly:

On the Galaxy S8 (Android 8.1) and Galaxy S6 Active (Android 7.0), the resized image is flipped on its side. Additionally, it thinks the height is the width, and vice versa, so when it resizes with { height: 640}, it’s actually resizing the smaller of the two dimensions, and I end up with a 853x640 image.

For fun, I tried to see what happened when I specified the exact size I wanted in ImageManipulator (so, tell it that for a 3:4 image, I want 480x640)- this is changed with the useMethod2 flag in my snack. Since it thinks up-down is left-right, I get an appropriately-warped image:

Note that there is no problem displaying the image in the correct aspect ratio on any of the phones if the original image is used. Additionally, our production SDK 25 app uses the pre-ImageManipulator workaround for resizing outlined here (Image resizing without detaching - #9 by llamaluvr) without issue on the S8 (I still need to validate this on SDK 30). I’m also going to try it on this crusty old S6 on my desk once it charges a bit UPDATE confirmed it’s an issue on the S6, as well.

Bad news for the old pre-ImageManipulator solution (using RN’s ImageEditor): it seems to have broken on the Galaxy S8 somewhere between SDK 25 and SDK 30. It now returns the error y + height must be <= bitmap.height() (this is apparently from the native Android code). Someone suggested a fix on a now mysteriously-deleted StackOverflow question, but it relies on essentially a random magic number and seems likely to be unreliable: https://webcache.googleusercontent.com/search?q=cache:7Krrjfs0DBEJ:https://stackoverflow.com/questions/50864347/y-height-must-be-bitmap-height-react-native-galaxy-s8/51564767+&cd=1&hl=en&ct=clnk&gl=us&client=safari

Have a potential partial workaround - back to using ImageManipulator. If you resize and detect that the width is still larger than my max dimensions, then I rotate 90 degrees and then attempt the resize again. The only time this doesn’t work is when the image was taking with the phone in portrait mode and the phone is upside down. Nothing will be lost in the image, but it will be upside down. It does appear that I could mitigate this issue by looking at the exif data.

static async resizeImage(pickedImage) {
    //if (Platform.OS === 'ios') {
      let resizeObj = {};
      if (pickedImage.height > pickedImage.width) {
        resizeObj = { height: 640 };
      } else {
        resizeObj = { width: 640 };
      }
      let manipResult = await ImageManipulator.manipulate(
        pickedImage.uri,
        [{ resize: resizeObj }],
        {
          format: 'jpeg',
        }
      );

      // Galaxy S8 workaround - S8 tries to resize as if it's a portrait even though it's a landscape
      // We flip 90 degrees and then try to resize again and it works...
      // Except if the image was taken upside down. Even though the S8 will flip it in the preview,
      // After resize it will still be upside down.
      if (Platform.OS === 'android' && manipResult.width > 640) {
        manipResult = await ImageManipulator.manipulate(
          pickedImage.uri,
          [{ rotate: 90 }, { resize: resizeObj }],
          {
            format: 'jpeg',
          }
        );
      }

      return {
        uri: manipResult.uri,
        name: 'photo.jpg',
        type: 'image/jpeg',
        width: manipResult.width,
        height: manipResult.height,
      };
  }

I’m increasingly confident this is a bug, so heads-up I submitted it here: https://github.com/expo/expo/issues/2512

1 Like

Thanks, Keith! As always, we really appreciate your meticulously detailed reports.

Cheers!

1 Like

This topic was automatically closed 15 days after the last reply. New replies are no longer allowed.