@@ -3,6 +3,7 @@ import type {LayoutChangeEvent} from 'react-native';
33import { View } from 'react-native' ;
44import { Gesture , GestureDetector , GestureHandlerRootView } from 'react-native-gesture-handler' ;
55import type { GestureUpdateEvent , PanGestureChangeEventPayload , PanGestureHandlerEventPayload } from 'react-native-gesture-handler' ;
6+ import ImageSize from 'react-native-image-size' ;
67import Animated , { useAnimatedStyle , useSharedValue } from 'react-native-reanimated' ;
78import Image from '@components/Image' ;
89import RESIZE_MODES from '@components/Image/resizeModes' ;
@@ -58,6 +59,7 @@ function ReceiptCropView({imageUri, onCropChange, initialCrop, isAuthTokenRequir
5859 const cropY = useSharedValue ( 0 ) ;
5960 const cropWidth = useSharedValue ( 0 ) ;
6061 const cropHeight = useSharedValue ( 0 ) ;
62+ const isImageInitialized = useRef ( false ) ;
6163
6264 // Track previous values to detect changes and recalculate crop on resize
6365 const prevDisplayValuesRef = useRef < {
@@ -71,6 +73,25 @@ function ReceiptCropView({imageUri, onCropChange, initialCrop, isAuthTokenRequir
7173
7274 const isCropInitialized = imageSize . width > 0 && imageSize . height > 0 && containerSize . width > 0 && containerSize . height > 0 ;
7375
76+ // Use react-native-image-size to get actual original image dimensions (not downsampled display dimensions).
77+ // On Android, expo-image's onLoad reports Glide-downsampled dimensions, which would make crop coordinates
78+ // wrong for expo-image-manipulator (which operates on full-resolution images). See AvatarCropModal for the same pattern.
79+ useEffect ( ( ) => {
80+ if ( ! imageUri || isAuthTokenRequired ) {
81+ return ;
82+ }
83+ ImageSize . getSize ( imageUri ) . then ( ( { width, height, rotation : originalRotation } ) => {
84+ // On Android, ImageSize returns rotation; when image is rotated 90/270°, swap width/height for layout.
85+ if ( originalRotation === 90 || originalRotation === 270 ) {
86+ setImageSize ( { width : height , height : width } ) ;
87+ } else {
88+ setImageSize ( { width, height} ) ;
89+ }
90+
91+ isImageInitialized . current = true ;
92+ } ) ;
93+ } , [ imageUri , isAuthTokenRequired ] ) ;
94+
7495 // Calculate scale factors to convert display coordinates to image coordinates
7596 const { scaleX, scaleY, displayWidth, displayHeight, imageOffsetX, imageOffsetY} = useMemo ( ( ) => {
7697 if ( ! containerSize . width || ! containerSize . height || ! imageSize . width || ! imageSize . height ) {
@@ -244,7 +265,9 @@ function ReceiptCropView({imageUri, onCropChange, initialCrop, isAuthTokenRequir
244265 if ( ! hasImageDimensions ) {
245266 setHasImageDimensions ( true ) ;
246267 }
247- setImageSize ( { width, height} ) ;
268+ if ( ! isImageInitialized . current ) {
269+ setImageSize ( { width, height} ) ;
270+ }
248271 } ,
249272 [ hasImageDimensions , imageSize . width , imageSize . height ] ,
250273 ) ;
0 commit comments