Skip to content

fix(ios): honour numeric Image.compress options - #422

Draft
TheWygoda wants to merge 1 commit into
numandev1:mainfrom
TheWygoda:fix/ios-image-numeric-options
Draft

TheWygoda wants to merge 1 commit into
numandev1:mainfrom
TheWygoda:fix/ios-image-numeric-options

Conversation

@TheWygoda

@TheWygoda TheWygoda commented Oct 2, 2026 •

Copy link
Copy Markdown

Summary

On iOS every numeric option passed to Image.compress (maxWidth, maxHeight, quality, progressDivider) is silently replaced by its default, so images always come out bounded by 1280 px at quality 0.8. This is the bug reported in #417.

Cause: Nitro hands JS numbers to Swift as Double (AnyValue.number(Double)). Neither normalize() nor the NSDictionary round-trip in HybridCompressor.dictionary(from:) boxes them into NSNumber. ImageCompressorOptions.fromDictionary then reads them with as? Int / as? Float. Those casts never match a Double, so every value falls back to its ?? default.

These paths are not affected:

  • Android: React Native's native map converts the double in getInt.
  • Video: VideoMain already reads its numeric options through NSNumber.

Fix: read the four numeric options through NSNumber (.intValue / .floatValue), which bridges a Double, an Int and an NSNumber alike. Defaults and all other options are unchanged.

Test: the harness image test already compressed the 16×16 fixture with maxWidth/maxHeight: 8, but only asserted that the output was non-empty. It now asserts the 8 px bound, so it fails without this fix.

Changelog

[iOS] [Fixed] - Image.compress honours maxWidth, maxHeight, quality and progressDivider instead of always using their defaults

Test Plan

  • Ran the local JS PR gate, yarn test:pr: 13/13 tests pass, typecheck is clean, lint reports 0 errors (the 9 warnings are pre-existing, in other files).

  • Ran the harness on an iOS simulator, yarn test:harness:ios: 9/9 pass. Setup: iPhone 17 Pro on iOS 26.5, picked via RN_HARNESS_IOS_DEVICE / RN_HARNESS_IOS_VERSION; Xcode 26.6; example app built with the same xcodebuild command as build-ios.yml.

    • Control: the same build with the original ImageCompressorOptions.swift and the new assertion fails 1 test (8 pass) with expected 1280 to be less than or equal to 8. Without the fix, manual mode used the 1280 default bound and scaled the 16×16 fixture up to 1280 px.
  • Replayed the exact option hand-off in a standalone Swift script: a Nitro Double, through normalize() and the NSDictionary round-trips, into the patched ImageCompressorOptions.fromDictionary.

    Input Before After
    maxWidth/maxHeight: 480, quality: 0.7 1280 / 0.8 480 / 0.7
    No options 1280 / 0.8 1280 / 0.8 (defaults unchanged)
  • Applied the same change to 2.0.3 through a patch in an app (RN 0.86.3, react-native-nitro-modules 0.37.1, iOS 26.5 simulator). Compressing a 5712×4284 JPEG with maxWidth/maxHeight: 480 gave 1280×960 before and 480×361 after.

🤖 Generated with Claude Code

On iOS every numeric Image.compress option (maxWidth, maxHeight, quality,
progressDivider) was silently replaced by its default, so images always
came out bounded by 1280 px at quality 0.8.

Nitro hands JS numbers to Swift as Double, and ImageCompressorOptions read
them with `as? Int` / `as? Float`, which never match a Double. Read them
through NSNumber instead, as VideoMain already does for its options.

The harness image test already passed maxWidth/maxHeight 8 for a 16×16
fixture but only checked that the output was non-empty; it now asserts
the bound.

Fixes numandev1#417

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@TheWygoda
TheWygoda marked this pull request as ready for review October 2, 2026 11:18
@TheWygoda
TheWygoda marked this pull request as draft October 2, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant