Enable colorimetric normalization of JPEG image data during decode#2922
Conversation
There was a problem hiding this comment.
Pull Request Overview
Adds colorimetric normalization support for JPEG (and JPEG-compressed TIFF) data by embedding ICC-based conversions into the decoding pipeline.
- Introduces
DecoderOptions.ColorProfileHandlingto specify Preserve, Compact, or Convert behavior. - Extends grayscale and CMYK JPEG converters with
ConvertToRgbInPlaceWithIccpaths and integrates ICC removal inImageDecoder. - Renames and updates YCbCr types to
YCbCrTransform, and adjusts color-profile classes for the new workflows.
Reviewed Changes
Copilot reviewed 59 out of 59 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs | Add ICC-aware override and repeat grayscale channel in vectorized path |
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs | Add ICC-aware override and repeat grayscale channel in vectorized path |
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs | Add ICC-aware override and repeat grayscale channel in vectorized path |
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs | Implement scalar ICC conversion with buffer packing/unpacking |
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs | Add ICC-aware override for CMYK vectorized converter |
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs | Add ICC-aware override for CMYK vectorized converter |
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs | Add ICC-aware override for CMYK vectorized converter |
| src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs | Implement scalar ICC conversion for CMYK |
| src/ImageSharp/Formats/ImageDecoder.cs | Invoke HandleIccProfile after decode/identify to strip profiles |
| src/ImageSharp/Formats/DecoderOptions.cs | Expose ColorProfileHandling and add profile‐selection helpers |
| src/ImageSharp/Formats/ColorProfileHandling.cs | Define Compact and update docs for enum |
| src/ImageSharp/ColorProfiles/YccK.cs | Rename matrix references to YCbCrTransform |
| src/ImageSharp/ColorProfiles/YCbCrTransform.cs | Rename YCbCrMatrix to YCbCrTransform and update ctor/docs |
| src/ImageSharp/ColorProfiles/YCbCr.cs | Update property references to YCbCrTransform |
| src/ImageSharp/ColorProfiles/Y.cs | Update transform references for Y channel |
| src/ImageSharp/ColorProfiles/Rgb.cs | Replace ToScaledVector3 calls with AsVector3Unsafe |
| src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs | Rename constants to YCbCrTransform |
| src/ImageSharp/ColorProfiles/Icc/CompactSrgbV4Profile.cs | Rename class to CompactSrgbV4Profile and adjust namespace |
| src/ImageSharp/ColorProfiles/ColorConversionOptions.cs | Change YCbCrMatrix property to YCbCrTransform |
| src/ImageSharp/Advanced/AotCompilerTools.cs | Update AOT stubs to match new GetPixelBuffer signatures |
Comments suppressed due to low confidence (5)
src/ImageSharp/ColorProfiles/ColorConversionOptions.cs:55
- Update this XML summary to reflect the renamed property (
YCbCrTransform) and its semantics (e.g., "Gets the YCbCr transform used to perform conversions...").
/// Gets the YCbCr matrix to used to perform conversions from/to RGB.
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs:22
- The
Configurationtype isn’t imported in this file, causing a compilation error; addusing SixLabors.ImageSharp;at the top so thatConfigurationis recognized.
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
src/ImageSharp/Formats/ImageDecoder.cs:325
- [nitpick] This logic is duplicated in two overloads; consider consolidating into a single generic or helper method to reduce code duplication.
private static void HandleIccProfile(DecoderOptions options, Image image)
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs:54
- Allocating a new buffer for every decode may impact performance; consider reusing pooled buffers or using stackalloc for small images to reduce allocation overhead.
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
src/ImageSharp/Formats/DecoderOptions.cs:72
- The out parameter
valueis overly generic and nullable despite theNotNullWhen(true)annotation; rename it to a descriptive name (e.g.out IccProfile profileToConvert) and consider making it non-nullable when returned true.
internal bool TryGetIccProfileForColorConversion(IccProfile? profile, [NotNullWhen(true)] out IccProfile? value)
|
(Sorry, misclicking!) Happy to take a look but will be in a couple of weeks after my break |
| { | ||
| private static readonly Vector3 TruncatedD50 = new(0.9642029F, 1F, 0.8249054F); | ||
|
|
||
| // sRGB v4 Preference |
| } | ||
|
|
||
| // Fallback: best-guess heuristic | ||
| return |
There was a problem hiding this comment.
This reminded me that profiles with absolute intent aren't currently handled correctly, but doesn't look like they throw exceptions either yet? (Not that I can find such a profile.)
Could be worth including intent in this check?
There was a problem hiding this comment.
I think, in this case, it's not wise to introduce a none sRGB relevant check here. If we attempt to convert an example in the wild someone can raise an issue and we can complete the converter.
|
|
||
| internal void SetConfiguration(Configuration configuration) => this.configuration = configuration; | ||
|
|
||
| internal bool TryGetIccProfileForColorConversion(IccProfile? profile, [NotNullWhen(true)] out IccProfile? value) |
There was a problem hiding this comment.
This return bool isn't currently used, easier to return the IccProfile? itself?
There was a problem hiding this comment.
Not at the moment but I think it will be used in other formats.
|
I've scanned over the ICC profile stuff, and if the goal is to detect an sRGB profile and bypass using it since sRGB calculations are already implemented programmatically as the default behaviour, then it makes sense to me. I've also double-checked the sRGB profile IDs just to be sure 👍 |
|
Thanks @waacton very much appreciated! |
|
Let's merge this! |
Prerequisites
Description
This PR adds support for normalizing color data in JPEG files (and TIFF files with JPEG compression) to sRGB during decoding.
A new
DecoderOptions.ColorProfileHandlingproperty controls this behavior with the following options:Several internal helper methods have been introduced to allow decoders to determine and apply the correct behavior.
All shared JPEG color converters now use a common scalar interleaving path to manipulate planar data for
ColorProfileConverter. There's likely room for SIMD-based performance improvements here (AVX-512?).@waacton - might be of interest to you since it introduces questions around deriving RGB working spaces from ICC profiles which you are likely to better understand than I.
@saucecontrol - tagging you since your compact sRGB v4 profile implementation was invaluable (and shamelessly borrowed 😁).