Skip to content

Commit f6a87f0

Browse files
committed
fix(ios): normalize image orientation before crop
Image.crop() on iOS did not normalize non-.up input images before calling cgImage.cropping(to:). vision-camera v5's Photo.toImageAsync() constructs UIImage(cgImage:, scale: 1, orientation: sensorOrientation), so uiImage.size is in display space while cgImage is in sensor space. The caller's (startX, startY, endX, endY) rect then lands in the wrong region of cgImage, and UIImage(cgImage:) dropped the source orientation in the output. Fix: when imageOrientation != .up or scale != 1, draw the image through a scale=1 UIGraphicsImageRenderer to bake orientation into pixels before cropping, then wrap the cropped CGImage with explicit scale=1 and .up so downstream consumers see consistent state. The other four bugs from the original PR (crop rect size, renderer scale in resize/rotate/createBlankImage, and the rotate slow-path bounding box) are already fixed in 0.15.1, so this branch is rescoped to just the remaining orientation bug, which also resolves the merge conflicts.
1 parent 006c5b9 commit f6a87f0

1 file changed

Lines changed: 20 additions & 2 deletions

File tree

packages/react-native-nitro-image/ios/Public/NativeImage.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,24 @@ public extension NativeImage {
135135
guard targetHeight > 0 else {
136136
throw RuntimeError.error(withMessage: "Height cannot be less than 0! (startY: \(startY) - endY: \(endY) = \(targetHeight))")
137137
}
138-
guard let cgImage = uiImage.cgImage else {
138+
139+
// Normalize before cropping: if imageOrientation != .up, the caller's
140+
// (startX, startY, endX, endY) rect is in display space but uiImage.cgImage is
141+
// in sensor space, so the crop lands in the wrong region. Draw through a
142+
// renderer to bake orientation into pixels. Force scale=1 so pixel dims match
143+
// logical size.
144+
let normalized: UIImage
145+
if uiImage.imageOrientation == .up && uiImage.scale == 1 {
146+
normalized = uiImage
147+
} else {
148+
let format = UIGraphicsImageRendererFormat()
149+
format.scale = 1
150+
let renderer = UIGraphicsImageRenderer(size: uiImage.size, format: format)
151+
normalized = renderer.image { _ in
152+
uiImage.draw(at: .zero)
153+
}
154+
}
155+
guard let cgImage = normalized.cgImage else {
139156
throw RuntimeError.error(withMessage: "This image does not have an underlying .cgImage!")
140157
}
141158

@@ -146,7 +163,8 @@ public extension NativeImage {
146163
guard let croppedCgImage = cgImage.cropping(to: targetRect) else {
147164
throw RuntimeError.error(withMessage: "Failed to crop CGImage to \(targetRect)!")
148165
}
149-
let croppedUiImage = UIImage(cgImage: croppedCgImage)
166+
// Preserve scale=1 and .up orientation so downstream ops see consistent state.
167+
let croppedUiImage = UIImage(cgImage: croppedCgImage, scale: 1, orientation: .up)
150168
return HybridImage(uiImage: croppedUiImage)
151169
}
152170

0 commit comments

Comments
 (0)