nmarci89 f8aaee5694
Fix P3-to-sRGB color conversion to operate in linear light (#181720)
<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

### Problem
The P3-to-sRGB color conversion applies an affine matrix directly to
gamma-encoded values in both the C++ engine (`dl_color.cc`) and Dart
(`painting.dart`). This is mathematically incorrect — color space
conversion matrices must operate in linear light. The result is P3
colors that appear nearly identical to sRGB instead of showing the full
gamut difference.
For example, P3 `#1ECAD3` (30/255, 202/255, 211/255) converts to:
- **Old (wrong):** extendedSRGB (-0.12, 0.86, 0.87)
- **New (correct):** extendedSRGB (-0.38, 0.81, 0.84)
The red channel error of 0.26 is clearly visible — colors appear washed
out instead of vivid.

### Fix
Replace the single affine matrix multiply with the correct 3-step
pipeline:
1. **Linearize** — decode gamma via sRGB EOTF
2. **Transform** — apply 3x3 P3-to-sRGB matrix in linear space
3. **Encode** — re-apply gamma via sRGB OETF
Extended range (negative values from out-of-gamut colors) is handled by
mirroring the transfer function.
**C++ (`dl_color.cc`):** Replace affine matrix with `p3ToExtendedSrgb()`
using `double` precision.
**Dart (`painting.dart`):** Replace `_MatrixColorTransform` with
`_P3ToSrgbTransform` and `_SrgbToP3Transform` classes. Also fixes the
sRGB-to-P3 direction.

### Performance
Negligible. The C++ conversion runs once per paint setup in `ReadColor`,
not per frame or per pixel. The Dart conversion runs once per
`Color.withValues()` call.

### Tests
- **C++:** Added `ColorSpaceP3ToExtendedSRGBLinearLight` test in
`dl_color_unittests.cc`
- **Dart:** Added 3 tests in `colors_test.dart`: mid-range
P3→extendedSRGB, P3 green→extendedSRGB, sRGB→P3 round-trip
All new tests fail with the old code and pass with the fix. Existing
tests continue to pass.

## Issue:
https://github.com/flutter/flutter/issues/181717

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing. **New tests pass, can't
run golden tests!**

---------

Co-authored-by: gaaclarke <30870216+gaaclarke@users.noreply.github.com>
2026-01-30 20:53:45 +00:00
..