Skip to content

Commit 74d475d

Browse files
Use ICC profile when available for JPEG decoding color transforms.
1 parent ea56484 commit 74d475d

File tree

51 files changed

+761
-112
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+761
-112
lines changed

src/ImageSharp/Advanced/AotCompilerTools.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,11 @@ private static void AotCompileImageDecoders<TPixel>()
277277
private static void AotCompileSpectralConverter<TPixel>()
278278
where TPixel : unmanaged, IPixel<TPixel>
279279
{
280-
default(SpectralConverter<TPixel>).GetPixelBuffer(default);
281-
default(GrayJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
282-
default(RgbJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
283-
default(TiffJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
284-
default(TiffOldJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
280+
default(SpectralConverter<TPixel>).GetPixelBuffer(default, default);
281+
default(GrayJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
282+
default(RgbJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
283+
default(TiffJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
284+
default(TiffOldJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
285285
}
286286

287287
/// <summary>
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4-
// <auto-generated />
5-
64
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
75

8-
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
6+
namespace SixLabors.ImageSharp.ColorProfiles.Icc;
97

10-
internal static class SrgbV4Profile
8+
internal static class CompactSrgbV4Profile
119
{
10+
private static readonly Lazy<IccProfile> LazyIccProfile = new(GetIccProfile);
11+
1212
// Generated using the sRGB-v4.icc profile found at https://github.com/saucecontrol/Compact-ICC-Profiles
13-
private static ReadOnlySpan<byte> Data => new byte[]
14-
{
13+
private static ReadOnlySpan<byte> Data =>
14+
[
1515
0, 0, 1, 224, 108, 99, 109, 115, 4, 32, 0, 0, 109, 110, 116, 114, 82, 71, 66, 32, 88, 89, 90, 32, 7, 226, 0, 3, 0,
1616
20, 0, 9, 0, 14, 0, 29, 97, 99, 115, 112, 77, 83, 70, 84, 0, 0, 0, 0, 115, 97, 119, 115, 99, 116, 114, 108, 0, 0,
1717
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 246, 214, 0, 1, 0, 0, 0, 0, 211, 45, 104, 97, 110, 100, 163, 178, 171,
@@ -29,11 +29,9 @@ internal static class SrgbV4Profile
2929
3, 143, 88, 89, 90, 32, 0, 0, 0, 0, 0, 0, 98, 150, 0, 0, 183, 137, 0, 0, 24, 218, 88, 89, 90, 32, 0, 0, 0,
3030
0, 0, 0, 36, 160, 0, 0, 15, 133, 0, 0, 182, 196, 112, 97, 114, 97, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 102, 105,
3131
0, 0, 242, 167, 0, 0, 13, 89, 0, 0, 19, 208, 0, 0, 10, 91,
32-
};
32+
];
3333

34-
private static readonly Lazy<IccProfile> LazyIccProfile = new(() => GetIccProfile());
35-
36-
public static IccProfile GetProfile() => LazyIccProfile.Value;
34+
public static IccProfile Profile => LazyIccProfile.Value;
3735

3836
private static IccProfile GetIccProfile()
3937
{
@@ -42,4 +40,3 @@ private static IccProfile GetIccProfile()
4240
return new IccProfile(buffer);
4341
}
4442
}
45-

src/ImageSharp/Formats/ColorProfileHandling.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,14 @@ public enum ColorProfileHandling
1313
/// </summary>
1414
Preserve,
1515

16+
/// <summary>
17+
/// Removes any embedded Standard sRGB ICC color profiles without transforming the pixels of the image.
18+
/// </summary>
19+
Compact,
20+
1621
/// <summary>
1722
/// Transforms the pixels of the image based on the conversion of any embedded ICC color profiles to sRGB V4 profile.
18-
/// The original profile is then replaced.
23+
/// The original profile is then removed.
1924
/// </summary>
2025
Convert
2126
}

src/ImageSharp/Formats/DecoderOptions.cs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Diagnostics.CodeAnalysis;
5+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
46
using SixLabors.ImageSharp.Processing;
57
using SixLabors.ImageSharp.Processing.Processors.Transforms;
68

@@ -62,9 +64,46 @@ public sealed class DecoderOptions
6264

6365
/// <summary>
6466
/// Gets a value that controls how ICC profiles are handled during decode.
65-
/// TODO: Implement this.
6667
/// </summary>
67-
internal ColorProfileHandling ColorProfileHandling { get; init; }
68+
public ColorProfileHandling ColorProfileHandling { get; init; }
6869

6970
internal void SetConfiguration(Configuration configuration) => this.configuration = configuration;
71+
72+
internal bool TryGetIccProfileForColorConversion(IccProfile? profile, [NotNullWhen(true)] out IccProfile? value)
73+
{
74+
value = null;
75+
76+
if (profile is null)
77+
{
78+
return false;
79+
}
80+
81+
if (IccProfileHeader.IsLikelySrgb(profile.Header))
82+
{
83+
return false;
84+
}
85+
86+
if (this.ColorProfileHandling == ColorProfileHandling.Preserve)
87+
{
88+
return false;
89+
}
90+
91+
value = profile;
92+
return true;
93+
}
94+
95+
internal bool CanRemoveIccProfile(IccProfile? profile)
96+
{
97+
if (profile is null)
98+
{
99+
return false;
100+
}
101+
102+
if (this.ColorProfileHandling == ColorProfileHandling.Compact && IccProfileHeader.IsLikelySrgb(profile.Header))
103+
{
104+
return true;
105+
}
106+
107+
return this.ColorProfileHandling == ColorProfileHandling.Convert;
108+
}
70109
}

src/ImageSharp/Formats/ImageDecoder.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream)
2424
s => this.Decode<TPixel>(options, s, default));
2525

2626
this.SetDecoderFormat(options.Configuration, image);
27+
HandleIccProfile(options, image);
2728

2829
return image;
2930
}
@@ -37,6 +38,7 @@ public Image Decode(DecoderOptions options, Stream stream)
3738
s => this.Decode(options, s, default));
3839

3940
this.SetDecoderFormat(options.Configuration, image);
41+
HandleIccProfile(options, image);
4042

4143
return image;
4244
}
@@ -52,6 +54,7 @@ public async Task<Image<TPixel>> DecodeAsync<TPixel>(DecoderOptions options, Str
5254
cancellationToken).ConfigureAwait(false);
5355

5456
this.SetDecoderFormat(options.Configuration, image);
57+
HandleIccProfile(options, image);
5558

5659
return image;
5760
}
@@ -66,6 +69,7 @@ public async Task<Image> DecodeAsync(DecoderOptions options, Stream stream, Canc
6669
cancellationToken).ConfigureAwait(false);
6770

6871
this.SetDecoderFormat(options.Configuration, image);
72+
HandleIccProfile(options, image);
6973

7074
return image;
7175
}
@@ -79,6 +83,7 @@ public ImageInfo Identify(DecoderOptions options, Stream stream)
7983
s => this.Identify(options, s, default));
8084

8185
this.SetDecoderFormat(options.Configuration, info);
86+
HandleIccProfile(options, info);
8287

8388
return info;
8489
}
@@ -93,6 +98,7 @@ public async Task<ImageInfo> IdentifyAsync(DecoderOptions options, Stream stream
9398
cancellationToken).ConfigureAwait(false);
9499

95100
this.SetDecoderFormat(options.Configuration, info);
101+
HandleIccProfile(options, info);
96102

97103
return info;
98104
}
@@ -315,4 +321,20 @@ internal void SetDecoderFormat(Configuration configuration, ImageInfo info)
315321
}
316322
}
317323
}
324+
325+
private static void HandleIccProfile(DecoderOptions options, Image image)
326+
{
327+
if (options.CanRemoveIccProfile(image.Metadata.IccProfile))
328+
{
329+
image.Metadata.IccProfile = null;
330+
}
331+
}
332+
333+
private static void HandleIccProfile(DecoderOptions options, ImageInfo image)
334+
{
335+
if (options.CanRemoveIccProfile(image.Metadata.IccProfile))
336+
{
337+
image.Metadata.IccProfile = null;
338+
}
339+
}
318340
}

src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using System.Buffers;
5+
using System.Numerics;
6+
using System.Runtime.InteropServices;
7+
using SixLabors.ImageSharp.ColorProfiles;
8+
using SixLabors.ImageSharp.ColorProfiles.Icc;
9+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
10+
411
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
512

613
internal abstract partial class JpegColorConverterBase
@@ -16,6 +23,10 @@ public CmykScalar(int precision)
1623
public override void ConvertToRgbInPlace(in ComponentValues values) =>
1724
ConvertToRgbInPlace(values, this.MaximumValue);
1825

26+
/// <inheritdoc/>
27+
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
28+
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
29+
1930
/// <inheritdoc/>
2031
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
2132
=> ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane);
@@ -75,5 +86,31 @@ public static void ConvertFromRgb(in ComponentValues values, float maxValue, Spa
7586
k[i] = maxValue - ktmp;
7687
}
7788
}
89+
90+
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
91+
{
92+
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
93+
Span<float> packed = memoryOwner.Memory.Span;
94+
95+
Span<float> c0 = values.Component0;
96+
Span<float> c1 = values.Component1;
97+
Span<float> c2 = values.Component2;
98+
Span<float> c3 = values.Component3;
99+
100+
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
101+
102+
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
103+
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
104+
105+
ColorConversionOptions options = new()
106+
{
107+
SourceIccProfile = profile,
108+
TargetIccProfile = CompactSrgbV4Profile.Profile,
109+
};
110+
ColorProfileConverter converter = new(options);
111+
converter.Convert<Cmyk, Rgb>(source, destination);
112+
113+
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
114+
}
78115
}
79116
}

src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
using System.Runtime.CompilerServices;
55
using System.Runtime.InteropServices;
66
using System.Runtime.Intrinsics;
7+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
78

89
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
910

@@ -46,6 +47,10 @@ public override void ConvertToRgbInPlace(in ComponentValues values)
4647
}
4748
}
4849

50+
/// <inheritdoc/>
51+
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
52+
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
53+
4954
/// <inheritdoc/>
5055
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
5156
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);

src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
using System.Runtime.CompilerServices;
55
using System.Runtime.InteropServices;
66
using System.Runtime.Intrinsics;
7+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
78

89
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
910

@@ -46,6 +47,10 @@ public override void ConvertToRgbInPlace(in ComponentValues values)
4647
}
4748
}
4849

50+
/// <inheritdoc/>
51+
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
52+
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
53+
4954
/// <inheritdoc/>
5055
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
5156
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);

src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
// Copyright (c) Six Labors.
1+
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

44
using System.Runtime.CompilerServices;
55
using System.Runtime.InteropServices;
66
using System.Runtime.Intrinsics;
7+
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
78

89
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
910

@@ -16,6 +17,10 @@ public CmykVector512(int precision)
1617
{
1718
}
1819

20+
/// <inheritdoc/>
21+
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
22+
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
23+
1924
/// <inheritdoc/>
2025
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
2126
{

0 commit comments

Comments
 (0)