fix(Android): decode wide-gamut sources into sRGB - #407
Open
toyaji wants to merge 1 commit into
Open
Conversation
Display P3 / Adobe RGB files were decoded keeping their wide color space, and Bitmap.compress() does not reliably tag the JPEG/WebP/HEIC output with the ICC profile. Non-color-managed viewers then render those pixels as sRGB and they look oversaturated (commonly reported on Samsung devices, which shoot P3 by default). Pin every BitmapFactory decode to sRGB via inPreferredColorSpace (API 26+, no-op on older devices). Matches the iOS side, which already fixes its render range to sRGB.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Android,
BitmapFactorydecodes a Display P3 / Adobe RGB source keeping its wide color space, and the subsequentBitmap.compress()does not reliably tag the JPEG/WebP/HEIC output with an ICC profile (it varies by format and API level). A non-color-managed consumer then interprets those wide-gamut pixel values as sRGB, so the result looks oversaturated / hue-shifted compared to the source as shown in the system gallery.This is most visible with photos from devices that shoot Display P3 by default (widely reported on Samsung). iOS is unaffected because
UIImage+scale.malready pins its render range to sRGB (UIGraphicsImageRendererFormat.preferredRange = .standard); Android had no equivalent.Change
Pin every
BitmapFactorydecode in the common Android implementation to sRGB viaBitmapFactory.Options.inPreferredColorSpace, so wide-gamut sources are color-managed down to sRGB at decode time and the output is self-describing sRGB regardless of the encoder's ICC behaviour. This brings Android in line with the iOS side.BitmapFactory.Options.preferSrgbColorSpace()extension (ext/BitmapCompressExt.kt)CommonHandler.compress(byteArray…),CommonHandler.handleFile,HeifHandler.makeOptioninPreferredColorSpaceexists from API 26; the helper is a no-op below that, where the platform has no color management anyway. No public API change.Verification
The repo has no Android instrumentation-test harness, so this was verified on a physical device (Galaxy S21, Android 15):
FlutterImageCompress.compressWithFile(..., format: jpeg)and scanned the output bytes for the ICCDisplay P3signature.main(before)(Happy to add an integration test under
packages/flutter_image_compress/example/integration_test/if you want one — would need a small P3 fixture asset.)Open question
Should this be unconditional (matches iOS, "just works" for the common case) or gated behind a new opt-in parameter? I went with unconditional for parity, but can rework it into a
keepColorSpace/targetColorSpaceoption if you'd prefer to preserve the current passthrough behaviour by default.