Fix scan receipt rotation on Android devices with inconsistent EXIF handling#83819
Fix scan receipt rotation on Android devices with inconsistent EXIF handling#83819
Conversation
…F handling When a receipt photo is captured on Android, the camera stores raw pixel data with EXIF orientation metadata. The image loader (Glide) may or may not auto-apply this rotation depending on the device. Previously, only a crop action was applied without explicit rotation, causing sideways images on devices where Glide doesn't handle EXIF correctly. This adds a two-pass normalization approach: first strip EXIF metadata, then compare pixel dimensions with raw dimensions to detect whether auto-rotation occurred, and conditionally apply explicit rotation before cropping. Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
|
Fixed the two failing checks:
|
Extract normalizedImage.uri into a local const using an 'in' check so TypeScript properly narrows the type from string | undefined to string across the nested .then() closure boundary. Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
|
Fixed the failing typecheck. The previous run had 3 failures:
|
…ntationAwareImageSize Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
|
Fixed the failing Jest unit tests. The The TypeScript error was already resolved by the previous commit. |
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
The previously failing checks are now resolved:
All checks are now passing. The only remaining pending check is |
|
@MelvinBot pull main |
…RotationOnAndroid Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
|
Merged main into this branch. No conflicts. |
|
@MelvinBot |
…ture path After PR #83470 removed cropImageToAspectRatio from the native scan flow, photos captured in IOURequestStepScan are used directly without any EXIF rotation handling. This adds normalizePhotoOrientation to the native capture path using the same two-pass approach: strip EXIF to detect Glide auto-rotation, then explicitly rotate if needed. Co-authored-by: Aimane Chnaif <aimane-chnaif@users.noreply.github.com>
|
Good catch. After PR #83470 merged, I've added
The Note: the fix in |
Explanation of Change
On certain Android devices (e.g. Redmi Note 10S / Android 13), receipt photos captured via the scan camera display rotated 90 degrees (horizontally instead of vertically), even though the photo was taken in portrait orientation.
The root cause is that when a receipt photo is captured on Android, the camera stores raw pixel data in landscape orientation with EXIF metadata indicating the required rotation. The
getDeviceOrientationAwareImageSizefunction correctly detects the EXIF rotation and swaps width/height for crop calculation, but the actualcropOrRotateImagecall only receives a[{crop}]action — no explicit rotation is ever applied. Whether the image ends up correctly oriented depends entirely on whether the underlying image loader (Glide, used byexpo-image-manipulator) auto-applies the EXIF rotation, which is device-dependent. AftermanipulateAsyncsaves the result, EXIF metadata is stripped, so if Glide didn't rotate the pixels, the image displays sideways.This fix adds a two-pass normalization approach when EXIF rotation of 90 or 270 degrees is detected:
cropOrRotateImagewith empty actions to strip EXIF metadata and produce a clean intermediate fileThis approach works correctly regardless of device-specific Glide EXIF handling behavior and has no impact on iOS or web (where
rotationisundefined).Fixed Issues
$ #76161
PROPOSAL: #76161 (comment)
Tests
Offline tests
N/A — The scan receipt flow requires the camera and doesn't have a meaningful offline workflow. The image cropping/rotation happens locally before upload.
QA Steps
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
N/A — Camera scan is native-only
iOS: Native
iOS: mWeb Safari
N/A — Camera scan is native-only
MacOS: Chrome / Safari
N/A — Camera scan is native-only on web; web already handles EXIF correctly