fix(ios): normalize image orientation before crop#117
Conversation
64b9896 to
43a7280
Compare
|
Thanks for the PR this is great stuff!! I think we'll be adding some Harness tests for the CI to ensure that we test bugs like these and catch them in CI. I expect the Harness tests CI to go red on current main and then green with your PR - @riteshshukla04 will post an update here soon :) |
|
I added harness on main . So please sync your branch with main . That way the tests can run and we can see the fixes |
c4c1cf5 to
3282c2d
Compare
|
Hi @riteshshukla04 @mrousavy - rebased onto current main, so this branch now includes the harness tests from #121. CI should now run against these fixes. Let me know if you'd like any other changes - thanks! |
|
Thanks for the great library! I can confirm that cropping does not work correctly on iOS. Thanks @rayabelcode for the fix! Waiting for the fix to be merged. |
|
@mrousavy thanks for the great library! Is there any chance the crop and resize fixes will be merged soon? They don't work correctly on iOS. |
|
Hey - there are merge conflicts in this PR, I cannot merge anything :) |
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.
3282c2d to
f6a87f0
Compare
|
Fixed - rebased on latest main and rescoped. I think it's ready to merge @mrousavy. |
|
Wait but didn't my latest commits on main now fix this bug? CI is now green... If there is still a bug on main I think we should first make sure to reproduce that bug via a Harness test that shows that the CI is actually red, then your PR can make it go green. Without tests I cant really blindly merge that |
Builds a non-.up image via a fast-flag rotate (cgImage untouched, orientation flipped) and crops a display-space rect. Without orientation normalization iOS clamps the rect to the sensor buffer and returns the wrong size, so this fails on main and passes with the crop fix. Android is unaffected and passes on both.
|
Thanks for looking. I checked current guard let cgImage = uiImage.cgImage else { ... } // raw, sensor-space cgImage
...
let croppedUiImage = UIImage(cgImage: croppedCgImage) // defaults to .up, drops orientationThere's no You're right that this needs a failing test first, and I think CI stays green because the harness builds every image with I've pushed a repro to the PR ( it('crops in display space when the image has a non-up orientation', () => {
const base = makeImage(40, 20);
const oriented = base.rotate(90, true); // cgImage stays 40x20, displays as 20x40
expect(oriented.width).toBe(20);
expect(oriented.height).toBe(40);
const cropped = oriented.crop(0, 0, 20, 40);
expect(cropped.width).toBe(20);
expect(cropped.height).toBe(40);
});On |
Rescoped + rebased on latest
mainto clear the merge conflicts.0.15.1already shipped fixes for four of the five bugs from the original PR (crop rect size, renderer scale inresize/rotateslow path /createBlankImage, and therotateslow-path bounding box on iOS and Android). This branch is now reduced to the one bug that is still present in0.15.1.The remaining bug:
cropdrops orientation (iOS)Image.crop()does not normalize non-.upinput images beforecgImage.cropping(to:). vision-camera v5'sPhoto.toImageAsync()constructsUIImage(cgImage:, scale: 1, orientation: sensorOrientation), souiImage.sizeis in display space whilecgImageis in sensor space. The caller's(startX, startY, endX, endY)rect then lands in the wrong region ofcgImage, andUIImage(cgImage:)drops the source orientation in the output.Fix
When
imageOrientation != .uporscale != 1, draw the image through ascale = 1UIGraphicsImageRendererto bake orientation into pixels before cropping, then wrap the croppedCGImagewith explicitscale: 1, orientation: .upso downstream consumers see consistent state. When the image is already.upatscale == 1, it takes the original no-copy path.Testing
Verified on iPhone 15 Pro (3x) through a vision-camera v5 card-scanning pipeline (
Photo.toImageAsync()->crop->resize->saveToTemporaryFileAsync). Before: the cropped region was wrong and the output was sensor-oriented. After: the crop produces the requested region in the correct orientation.The branch is one commit ahead of
mainwith no other changes, so it merges cleanly.