capacitor-plugins icon indicating copy to clipboard operation
capacitor-plugins copied to clipboard

[Bug]: Wrong iOS photo exif data

Open sakonn opened this issue 1 year ago • 1 comments

Capacitor Version

💊 Capacitor Doctor 💊

Latest Dependencies:

@capacitor/cli: 6.1.2 @capacitor/core: 6.1.2 @capacitor/android: 6.1.2 @capacitor/ios: 6.1.2

Installed Dependencies:

@capacitor/cli: 6.1.2 @capacitor/core: 5.5.1 @capacitor/android: 5.5.1 @capacitor/ios: 5.5.1

[success] iOS looking great! 👌 [success] Android looking great! 👌

Other API Details

npm version: 10.8.2
node version: v20.17.0
pod version: 1.13.0
iOS version: 17.4.1
ios device: iPhone SE 2nd generation

Platforms Affected

  • [X] iOS
  • [ ] Android
  • [ ] Web

Current Behavior

I am developing a data collection app where data is collected primarily in the form of photos which are later processed. To make postprocessing more straightforward it is required that they are taken in portrait mode (not in landscape). So, I am trying to figure out a way to restrict taking pictures which are landscape-oriented. I have not found any suitable setting for that, so now I am trying to disable saving photos that are wider than higher (landscape by this logic) based on the photo metadata. Photos are taken by the capacitor camera plugin when taking the photos I have found out that information about the photos taken in the iOS app is incorrect.

Portrait photo taken in the ios app: 20241009-080808_4780201379629_overviewImage

And the related exif metadata about this photo which are returned by the plugin:

{
    "Flash": 16,
    "SubsecTimeOriginal": "675",
    "PixelXDimension": 4032,
    "MeteringMode": 5,
    "ApertureValue": 1.6959938131099002,
    "BrightnessValue": -3.0617721648310905,
    "ColorSpace": 65535,
    "FocalLenIn35mmFilm": 28,
    "SceneType": 1,
    "ExposureBiasValue": 0,
    "PixelYDimension": 3024,
    "Orientation": 1,
    "ExposureTime": 0.06666666666666667,
    "CompositeImage": 2,
    "ExposureProgram": 2,
    "WhiteBalance": 0,
    "ShutterSpeedValue": 3.9085506496196545,
    "LensModel": "iPhone SE (2nd generation) back camera 3.99mm f/1.8",
    "ExifVersion": "0232",
    "ISOSpeedRatings": [
      800
    ],
    "LensSpecification": [
      3.99,
      3.99,
      1.8,
      1.8
    ],
    "LensMake": "Apple",
    "SensingMethod": 2,
    "DateTimeDigitized": "2024:10:09 08:08:04",
    "ExposureMode": 0,
    "FNumber": 1.8,
    "DateTimeOriginal": "2024:10:09 08:08:04",
    "FocalLength": 3.99,
    "SubsecTimeDigitized": "675",
    "OffsetTimeOriginal": "+02:00",
    "SubjectArea": [
      2013,
      1511,
      2116,
      1330
    ],
    "OffsetTimeDigitized": "+02:00",
    "OffsetTime": "+02:00"
  },

Landscape photo taken in the ios app: 20241009-080905_4780201379629_facingImage

And the related exif metadata about this photo which are returned by the plugin:

{
    "Flash": 16,
    "SubsecTimeOriginal": "524",
    "PixelXDimension": 4032,
    "MeteringMode": 5,
    "ApertureValue": 1.6959938131099002,
    "BrightnessValue": -3.343055762488874,
    "ColorSpace": 65535,
    "FocalLenIn35mmFilm": 28,
    "SceneType": 1,
    "ExposureBiasValue": 0,
    "PixelYDimension": 3024,
    "Orientation": 1,
    "ExposureTime": 0.06666666666666667,
    "CompositeImage": 2,
    "ExposureProgram": 2,
    "WhiteBalance": 0,
    "ShutterSpeedValue": 3.9085506496196545,
    "LensModel": "iPhone SE (2nd generation) back camera 3.99mm f/1.8",
    "ExifVersion": "0232",
    "ISOSpeedRatings": [
      1250
    ],
    "LensSpecification": [
      3.99,
      3.99,
      1.8,
      1.8
    ],
    "LensMake": "Apple",
    "SensingMethod": 2,
    "DateTimeDigitized": "2024:10:09 08:09:01",
    "ExposureMode": 0,
    "FNumber": 1.8,
    "DateTimeOriginal": "2024:10:09 08:09:01",
    "FocalLength": 3.99,
    "SubsecTimeDigitized": "524",
    "OffsetTimeOriginal": "+02:00",
    "SubjectArea": [
      2013,
      1511,
      2116,
      1330
    ],
    "OffsetTimeDigitized": "+02:00",
    "OffsetTime": "+02:00"
  }

In my code, I am trying to compare PixelXDimension and PixelYDimension from the metadata and calculate if the photo is landscape or portrait. The problem is that these values are the same for both photos although they have different orientations.

On the contrary, the same photos taken with the android app return the expected result. Landscape photo metadata from an android app:

    "Orientation": "0",
    "PixelXDimension": "4624",
    "PixelYDimension": "3472",

Portrait photo metadata from an android app:

    "Orientation": "0",
    "PixelXDimension": "3472",
    "PixelYDimension": "4624",

Expected Behavior

I expect to get proper photo dimensions based on the photo orientation. So if the photo is taken landscape I expect to get the XDimension wider then the YDimension. And if the photo is taken as a portrait than the YDimension should be bigger than XDimension.

Project Reproduction

https://github.com/sakonn/probable-octo-invention

Additional Information

No response

sakonn avatar Oct 09 '24 07:10 sakonn

This issue relates to Camera functionality, which is part of @capacitor/camera, and as such as been transferred to the appropriate repository.

eric-horodyski avatar Oct 11 '24 19:10 eric-horodyski

Will there by a solution for this?

kettenbach-it avatar Dec 04 '24 09:12 kettenbach-it

This issue has been labeled as type: bug. This label is added to issues that that have been reproduced and are being tracked in our internal issue tracker.

ionitron-bot[bot] avatar Dec 05 '24 13:12 ionitron-bot[bot]

Hey @sakonn, Can you share the entire Android JSONs as you did for iOS? BR.

OS-ricardomoreirasilva avatar Dec 06 '24 15:12 OS-ricardomoreirasilva

Hello @OS-ricardomoreirasilva, thanks for your interest. Here is the output from the Android phone. The photos were taken in the app build from the example repository which is linked in the issue description.

Here is the outcome of the photo taken as a portrait. The ImageLength and PixelYDimension are bigger as expected.

{
  "format": "jpeg",
  "exif": {
    "ApertureValue": "167/100",
    "BrightnessValue": "0/100",
    "ColorSpace": "1",
    "ComponentsConfiguration": "???",
    "Compression": "6",
    "DateTime": "2024:12:06 18:50:20",
    "DateTimeDigitized": "2024:12:06 18:50:20",
    "DateTimeOriginal": "2024:12:06 18:50:20",
    "ExifVersion": "0220",
    "ExposureBiasValue": "0/1",
    "ExposureMode": "0",
    "ExposureProgram": "0",
    "Flash": "16",
    "FlashpixVersion": "0100",
    "FocalLength": "4730/1000",
    "FocalLengthIn35mmFilm": "0",
    "FNumber": "1.79",
    "ImageLength": "4624",
    "ImageWidth": "3472",
    "InteroperabilityIndex": "R98",
    "JPEGInterchangeFormat": "812",
    "JPEGInterchangeFormatLength": "39323",
    "LightSource": "21",
    "Make": "XXXXX",
    "MaxApertureValue": "167/100",
    "MeteringMode": "2",
    "Model": "XXXXX",
    "Orientation": "0",
    "PhotographicSensitivity": "125",
    "PixelXDimension": "3472",
    "PixelYDimension": "4624",
    "ResolutionUnit": "2",
    "SceneCaptureType": "0",
    "SceneType": "0",
    "SensingMethod": "0",
    "ShutterSpeedValue": "5058/1000",
    "SubSecTime": "609213",
    "SubSecTimeDigitized": "609213",
    "SubSecTimeOriginal": "609213",
    "WhiteBalance": "0",
    "XResolution": "72/1",
    "YCbCrPositioning": "1",
    "YResolution": "72/1"
  },
  "path": "file:///storage/emulated/0/Android/data/io.ionic.starter/files/Pictures/JPEG_20241206_185014_2081679850067582195.jpg",
  "webPath": "http://192.168.1.7:8100/_capacitor_file_/storage/emulated/0/Android/data/io.ionic.starter/files/Pictures/JPEG_20241206_185014_2081679850067582195.jpg",
  "saved": false
}

And here is the outcome of the photo taken as a landscape. The ImageWidth and PixelXDimension are bigger as expected.

{
  "format": "jpeg",
  "exif": {
    "ApertureValue": "167/100",
    "BrightnessValue": "0/100",
    "ColorSpace": "1",
    "ComponentsConfiguration": "???",
    "Compression": "6",
    "DateTime": "2024:12:06 18:50:51",
    "DateTimeDigitized": "2024:12:06 18:50:51",
    "DateTimeOriginal": "2024:12:06 18:50:51",
    "ExifVersion": "0220",
    "ExposureBiasValue": "0/1",
    "ExposureMode": "0",
    "ExposureProgram": "0",
    "Flash": "16",
    "FlashpixVersion": "0100",
    "FocalLength": "4730/1000",
    "FocalLengthIn35mmFilm": "0",
    "FNumber": "1.79",
    "ImageLength": "3472",
    "ImageWidth": "4624",
    "InteroperabilityIndex": "R98",
    "JPEGInterchangeFormat": "812",
    "JPEGInterchangeFormatLength": "34979",
    "LightSource": "21",
    "Make": "XXXXXX",
    "MaxApertureValue": "167/100",
    "MeteringMode": "2",
    "Model": "XXXXX",
    "Orientation": "0",
    "PhotographicSensitivity": "125",
    "PixelXDimension": "4624",
    "PixelYDimension": "3472",
    "ResolutionUnit": "2",
    "SceneCaptureType": "0",
    "SceneType": "0",
    "SensingMethod": "0",
    "ShutterSpeedValue": "5058/1000",
    "SubSecTime": "467467",
    "SubSecTimeDigitized": "467467",
    "SubSecTimeOriginal": "467467",
    "WhiteBalance": "0",
    "XResolution": "72/1",
    "YCbCrPositioning": "1",
    "YResolution": "72/1"
  },
  "path": "file:///storage/emulated/0/Android/data/io.ionic.starter/files/Pictures/JPEG_20241206_185045_8653160463096438101.jpg",
  "webPath": "http://192.168.1.7:8100/_capacitor_file_/storage/emulated/0/Android/data/io.ionic.starter/files/Pictures/JPEG_20241206_185045_8653160463096438101.jpg",
  "saved": false
}

In this situation, the autorotate feature was disabled on my phone.

sakonn avatar Dec 06 '24 17:12 sakonn

So I did a bit more digging into the issue itself and would like to share you with some findings. Comparing the JSONs you share for both Android and iOS, my initial suspicions were confirmed: the issue is not a difference between Android and iOS but between the different EXIF versions those cameras use (as you can see by the ExifVersion fields, Android is using v0220 and iOS v0232). From what I found, the issue of swapped PixelXDimension and PixelYDimension in portrait pictures with EXIF 0232 compared to EXIF 0220 is likely due to the way EXIF 0232 handles image orientation metadata versus how EXIF 0220 does. Why?

  1. In EXIF 0220, the PixelXDimension and PixelYDimension fields often directly reflect the raw dimensions of the image data as stored in the file. The Orientation tag is included but is sometimes ignored or inconsistently used by older software. However, in EXIF 0232, the Orientation tag is more rigorously utilised, and modern software or devices often adjust metadata accordingly to reflect the image’s intended display orientation.
  2. Consequently, applications reading EXIF 0220 may treat the image as always in landscape mode, regardless of the Orientation tag. This ensures the dimensions remain unaltered. Opposite to this, in EXIF 0232, for portrait images, the Orientation tag specifies that the image should be rotated (e.g., 90° clockwise for portrait mode). Some software adjusts the PixelXDimension and PixelYDimension values to reflect how the image is meant to be displayed, rather than its raw storage orientation.

Considering all this, and regarding that the values are expected from an EXIF perspective, the presented values do make sense and changing them might lead to unexpected issues.

OS-ricardomoreirasilva avatar Dec 09 '24 17:12 OS-ricardomoreirasilva

Thank you for your answer. So, the solution would be to write the alternative image handling based on the EXIF version the device is using? Do you have any sources to read more about how the EXIF handles the Orientation metadata value?

sakonn avatar Dec 27 '24 08:12 sakonn

That approach appears correct, but I'd dig a bit into understanding how tools like ExifTool or ImageMagick work with EXIF data to have a bigger picture. Here you have some "official" sources on EXIF:

  • https://www.loc.gov/preservation/digital/formats/fdd/fdd000146.shtml
  • https://www.loc.gov/preservation/digital/formats/fdd/fdd000618.shtml
  • https://exiftool.org/TagNames/EXIF.html

OS-ricardomoreirasilva avatar Dec 30 '24 10:12 OS-ricardomoreirasilva

Okay, thank you very much for the answer. I will take a look and see if the implementation is worth the efford.

sakonn avatar Jan 05 '25 18:01 sakonn

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of the plugin, please create a new issue and ensure the template is fully filled out.

ionitron-bot[bot] avatar Jan 20 '25 19:01 ionitron-bot[bot]