Browse Source

Merge pull request #2922 from SixLabors/js/jpeg-icc-normalize

Enable colorimetric normalization of JPEG image data during decode
pull/2936/head
James Jackson-South 8 months ago
committed by GitHub
parent
commit
c19a82e2cc
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 10
      src/ImageSharp/Advanced/AotCompilerTools.cs
  2. 14
      src/ImageSharp/ColorProfiles/ColorConversionOptions.cs
  3. 19
      src/ImageSharp/ColorProfiles/Icc/CompactSrgbV4Profile.cs
  4. 6
      src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs
  5. 6
      src/ImageSharp/ColorProfiles/Rgb.cs
  6. 4
      src/ImageSharp/ColorProfiles/Y.cs
  7. 8
      src/ImageSharp/ColorProfiles/YCbCr.cs
  8. 10
      src/ImageSharp/ColorProfiles/YCbCrTransform.cs
  9. 8
      src/ImageSharp/ColorProfiles/YccK.cs
  10. 7
      src/ImageSharp/Formats/ColorProfileHandling.cs
  11. 43
      src/ImageSharp/Formats/DecoderOptions.cs
  12. 22
      src/ImageSharp/Formats/ImageDecoder.cs
  13. 37
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs
  14. 7
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs
  15. 7
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs
  16. 7
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs
  17. 61
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs
  18. 18
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs
  19. 18
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs
  20. 20
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs
  21. 52
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs
  22. 7
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs
  23. 7
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs
  24. 7
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs
  25. 48
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs
  26. 5
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs
  27. 5
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs
  28. 5
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs
  29. 48
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs
  30. 5
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs
  31. 5
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs
  32. 5
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs
  33. 97
      src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs
  34. 42
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs
  35. 33
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  36. 19
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs
  37. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs
  38. 16
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  39. 23
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  40. 12
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  41. 13
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  42. 15
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs
  43. 2
      src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs
  44. 40
      src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs
  45. 3
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  46. 1
      tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs
  47. 6
      tests/ImageSharp.Tests/ColorProfiles/Icc/TestIccProfiles.cs
  48. 6
      tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs
  49. 51
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  50. 3
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  51. 2
      tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs
  52. 3
      tests/ImageSharp.Tests/TestDataIcc/Profiles/issue-129.icc
  53. 1
      tests/ImageSharp.Tests/TestImages.cs
  54. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png
  55. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png
  56. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png
  57. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png
  58. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png
  59. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png
  60. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png
  61. 3
      tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png
  62. 3
      tests/Images/Input/Jpg/icc-profiles/issue_2723.jpg

10
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -277,11 +277,11 @@ internal static class AotCompilerTools
private static void AotCompileSpectralConverter<TPixel>()
where TPixel : unmanaged, IPixel<TPixel>
{
default(SpectralConverter<TPixel>).GetPixelBuffer(default);
default(GrayJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
default(RgbJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
default(TiffJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
default(TiffOldJpegSpectralConverter<TPixel>).GetPixelBuffer(default);
default(SpectralConverter<TPixel>).GetPixelBuffer(default, default);
default(GrayJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
default(RgbJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
default(TiffJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
default(TiffOldJpegSpectralConverter<TPixel>).GetPixelBuffer(default, default);
}
/// <summary>

14
src/ImageSharp/ColorProfiles/ColorConversionOptions.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.ColorProfiles;
public class ColorConversionOptions
{
private Matrix4x4 adaptationMatrix;
private YCbCrMatrix yCbCrMatrix;
private YCbCrTransform yCbCrTransform;
/// <summary>
/// Initializes a new instance of the <see cref="ColorConversionOptions"/> class.
@ -22,7 +22,7 @@ public class ColorConversionOptions
public ColorConversionOptions()
{
this.AdaptationMatrix = KnownChromaticAdaptationMatrices.Bradford;
this.YCbCrMatrix = KnownYCbCrMatrices.BT601;
this.YCbCrTransform = KnownYCbCrMatrices.BT601;
}
/// <summary>
@ -53,13 +53,13 @@ public class ColorConversionOptions
/// <summary>
/// Gets the YCbCr matrix to used to perform conversions from/to RGB.
/// </summary>
public YCbCrMatrix YCbCrMatrix
public YCbCrTransform YCbCrTransform
{
get => this.yCbCrMatrix;
get => this.yCbCrTransform;
init
{
this.yCbCrMatrix = value;
this.TransposedYCbCrMatrix = value.Transpose();
this.yCbCrTransform = value;
this.TransposedYCbCrTransform = value.Transpose();
}
}
@ -88,7 +88,7 @@ public class ColorConversionOptions
}
}
internal YCbCrMatrix TransposedYCbCrMatrix { get; private set; }
internal YCbCrTransform TransposedYCbCrTransform { get; private set; }
internal Matrix4x4 InverseAdaptationMatrix { get; private set; }
}

19
src/ImageSharp/ColorProfiles/Icc/SrgbV4Profile.Generated.cs → src/ImageSharp/ColorProfiles/Icc/CompactSrgbV4Profile.cs

@ -1,17 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
// <auto-generated />
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Conversion.Icc;
namespace SixLabors.ImageSharp.ColorProfiles.Icc;
internal static class SrgbV4Profile
internal static class CompactSrgbV4Profile
{
private static readonly Lazy<IccProfile> LazyIccProfile = new(GetIccProfile);
// Generated using the sRGB-v4.icc profile found at https://github.com/saucecontrol/Compact-ICC-Profiles
private static ReadOnlySpan<byte> Data => new byte[]
{
private static ReadOnlySpan<byte> Data =>
[
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,
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,
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
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,
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,
0, 0, 242, 167, 0, 0, 13, 89, 0, 0, 19, 208, 0, 0, 10, 91,
};
];
private static readonly Lazy<IccProfile> LazyIccProfile = new(() => GetIccProfile());
public static IccProfile GetProfile() => LazyIccProfile.Value;
public static IccProfile Profile => LazyIccProfile.Value;
private static IccProfile GetIccProfile()
{
@ -42,4 +40,3 @@ internal static class SrgbV4Profile
return new IccProfile(buffer);
}
}

6
src/ImageSharp/ColorProfiles/KnownYCbCrMatrices.cs

@ -15,7 +15,7 @@ public static class KnownYCbCrMatrices
/// <summary>
/// ITU-R BT.601 (SD video standard).
/// </summary>
public static readonly YCbCrMatrix BT601 = new(
public static readonly YCbCrTransform BT601 = new(
new Matrix4x4(
0.299000F, 0.587000F, 0.114000F, 0F,
-0.168736F, -0.331264F, 0.500000F, 0F,
@ -31,7 +31,7 @@ public static class KnownYCbCrMatrices
/// <summary>
/// ITU-R BT.709 (HD video, sRGB standard).
/// </summary>
public static readonly YCbCrMatrix BT709 = new(
public static readonly YCbCrTransform BT709 = new(
new Matrix4x4(
0.212600F, 0.715200F, 0.072200F, 0F,
-0.114572F, -0.385428F, 0.500000F, 0F,
@ -47,7 +47,7 @@ public static class KnownYCbCrMatrices
/// <summary>
/// ITU-R BT.2020 (UHD/4K video standard).
/// </summary>
public static readonly YCbCrMatrix BT2020 = new(
public static readonly YCbCrTransform BT2020 = new(
new Matrix4x4(
0.262700F, 0.678000F, 0.059300F, 0F,
-0.139630F, -0.360370F, 0.500000F, 0F,

6
src/ImageSharp/ColorProfiles/Rgb.cs

@ -98,7 +98,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
/// <returns>The <see cref="Vector4"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToScaledVector4()
=> new(this.ToScaledVector3(), 1F);
=> new(this.AsVector3Unsafe(), 1F);
/// <inheritdoc/>
public static void ToScaledVector4(ReadOnlySpan<Rgb> source, Span<Vector4> destination)
@ -154,7 +154,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(this.ToScaledVector4()));
// Then convert to xyz
return new CieXyz(Vector3.Transform(linear.ToScaledVector3(), GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace)));
return new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), GetRgbToCieXyzMatrix(options.SourceRgbWorkingSpace)));
}
/// <inheritdoc/>
@ -171,7 +171,7 @@ public readonly struct Rgb : IProfileConnectingSpace<Rgb, CieXyz>
Rgb linear = FromScaledVector4(options.SourceRgbWorkingSpace.Expand(rgb.ToScaledVector4()));
// Then convert to xyz
destination[i] = new CieXyz(Vector3.Transform(linear.ToScaledVector3(), matrix));
destination[i] = new CieXyz(Vector3.Transform(linear.AsVector3Unsafe(), matrix));
}
}

4
src/ImageSharp/ColorProfiles/Y.cs

@ -90,8 +90,8 @@ public readonly struct Y : IColorProfile<Y, Rgb>
/// <inheritdoc/>
public static Y FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
Matrix4x4 m = options.YCbCrMatrix.Forward;
float offset = options.YCbCrMatrix.Offset.X;
Matrix4x4 m = options.YCbCrTransform.Forward;
float offset = options.YCbCrTransform.Offset.X;
return new(Vector3.Dot(source.AsVector3Unsafe(), new Vector3(m.M11, m.M12, m.M13)) + offset);
}

8
src/ImageSharp/ColorProfiles/YCbCr.cs

@ -130,8 +130,8 @@ public readonly struct YCbCr : IColorProfile<YCbCr, Rgb>
public static YCbCr FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
Vector3 rgb = source.AsVector3Unsafe();
Matrix4x4 m = options.TransposedYCbCrMatrix.Forward;
Vector3 offset = options.TransposedYCbCrMatrix.Offset;
Matrix4x4 m = options.TransposedYCbCrTransform.Forward;
Vector3 offset = options.TransposedYCbCrTransform.Offset;
return new YCbCr(Vector3.Transform(rgb, m) + offset, true);
}
@ -152,8 +152,8 @@ public readonly struct YCbCr : IColorProfile<YCbCr, Rgb>
/// <inheritdoc/>
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
{
Matrix4x4 m = options.TransposedYCbCrMatrix.Inverse;
Vector3 offset = options.TransposedYCbCrMatrix.Offset;
Matrix4x4 m = options.TransposedYCbCrTransform.Inverse;
Vector3 offset = options.TransposedYCbCrTransform.Offset;
Vector3 normalized = this.AsVector3Unsafe() - offset;
return Rgb.FromScaledVector3(Vector3.Transform(normalized, m));

10
src/ImageSharp/ColorProfiles/YcbCrMatrix.cs → src/ImageSharp/ColorProfiles/YCbCrTransform.cs

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.ColorProfiles;
/// <summary>
/// <para>
/// Represents a YCbCr color matrix containing forward and inverse transformation matrices,
/// Represents a YCbCr color transform containing forward and inverse transformation matrices,
/// and the chrominance offsets to apply for full-range encoding
/// </para>
/// <para>
@ -17,10 +17,10 @@ namespace SixLabors.ImageSharp.ColorProfiles;
/// working spaces will produce incorrect conversions.
/// </para>
/// </summary>
public readonly struct YCbCrMatrix
public readonly struct YCbCrTransform
{
/// <summary>
/// Initializes a new instance of the <see cref="YCbCrMatrix"/> struct.
/// Initializes a new instance of the <see cref="YCbCrTransform"/> struct.
/// </summary>
/// <param name="forward">
/// The forward transformation matrix from RGB to YCbCr. The matrix must include the
@ -34,7 +34,7 @@ public readonly struct YCbCrMatrix
/// The chrominance offsets to be added after the forward conversion,
/// and subtracted before the inverse conversion. Usually <c>(0, 0.5, 0.5)</c>.
/// </param>
public YCbCrMatrix(Matrix4x4 forward, Matrix4x4 inverse, Vector3 offset)
public YCbCrTransform(Matrix4x4 forward, Matrix4x4 inverse, Vector3 offset)
{
this.Forward = forward;
this.Inverse = inverse;
@ -56,6 +56,6 @@ public readonly struct YCbCrMatrix
/// </summary>
public Vector3 Offset { get; }
internal YCbCrMatrix Transpose()
internal YCbCrTransform Transpose()
=> new(Matrix4x4.Transpose(this.Forward), Matrix4x4.Transpose(this.Inverse), this.Offset);
}

8
src/ImageSharp/ColorProfiles/YccK.cs

@ -131,8 +131,8 @@ public readonly struct YccK : IColorProfile<YccK, Rgb>
/// <inheritdoc/>
public Rgb ToProfileConnectingSpace(ColorConversionOptions options)
{
Matrix4x4 m = options.TransposedYCbCrMatrix.Inverse;
Vector3 offset = options.TransposedYCbCrMatrix.Offset;
Matrix4x4 m = options.TransposedYCbCrTransform.Inverse;
Vector3 offset = options.TransposedYCbCrTransform.Offset;
Vector3 normalized = this.AsVector3Unsafe() - offset;
return Rgb.FromScaledVector3(Vector3.Transform(normalized, m) * (1F - this.K));
@ -141,8 +141,8 @@ public readonly struct YccK : IColorProfile<YccK, Rgb>
/// <inheritdoc/>
public static YccK FromProfileConnectingSpace(ColorConversionOptions options, in Rgb source)
{
Matrix4x4 m = options.TransposedYCbCrMatrix.Forward;
Vector3 offset = options.TransposedYCbCrMatrix.Offset;
Matrix4x4 m = options.TransposedYCbCrTransform.Forward;
Vector3 offset = options.TransposedYCbCrTransform.Offset;
Vector3 rgb = source.AsVector3Unsafe();
float k = 1F - MathF.Max(rgb.X, MathF.Max(rgb.Y, rgb.Z));

7
src/ImageSharp/Formats/ColorProfileHandling.cs

@ -13,9 +13,14 @@ public enum ColorProfileHandling
/// </summary>
Preserve,
/// <summary>
/// Removes any embedded Standard sRGB ICC color profiles without transforming the pixels of the image.
/// </summary>
Compact,
/// <summary>
/// Transforms the pixels of the image based on the conversion of any embedded ICC color profiles to sRGB V4 profile.
/// The original profile is then replaced.
/// The original profile is then removed.
/// </summary>
Convert
}

43
src/ImageSharp/Formats/DecoderOptions.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Transforms;
@ -62,9 +64,46 @@ public sealed class DecoderOptions
/// <summary>
/// Gets a value that controls how ICC profiles are handled during decode.
/// TODO: Implement this.
/// </summary>
internal ColorProfileHandling ColorProfileHandling { get; init; }
public ColorProfileHandling ColorProfileHandling { get; init; }
internal void SetConfiguration(Configuration configuration) => this.configuration = configuration;
internal bool TryGetIccProfileForColorConversion(IccProfile? profile, [NotNullWhen(true)] out IccProfile? value)
{
value = null;
if (profile is null)
{
return false;
}
if (IccProfileHeader.IsLikelySrgb(profile.Header))
{
return false;
}
if (this.ColorProfileHandling == ColorProfileHandling.Preserve)
{
return false;
}
value = profile;
return true;
}
internal bool CanRemoveIccProfile(IccProfile? profile)
{
if (profile is null)
{
return false;
}
if (this.ColorProfileHandling == ColorProfileHandling.Compact && IccProfileHeader.IsLikelySrgb(profile.Header))
{
return true;
}
return this.ColorProfileHandling == ColorProfileHandling.Convert;
}
}

22
src/ImageSharp/Formats/ImageDecoder.cs

@ -24,6 +24,7 @@ public abstract class ImageDecoder : IImageDecoder
s => this.Decode<TPixel>(options, s, default));
this.SetDecoderFormat(options.Configuration, image);
HandleIccProfile(options, image);
return image;
}
@ -37,6 +38,7 @@ public abstract class ImageDecoder : IImageDecoder
s => this.Decode(options, s, default));
this.SetDecoderFormat(options.Configuration, image);
HandleIccProfile(options, image);
return image;
}
@ -52,6 +54,7 @@ public abstract class ImageDecoder : IImageDecoder
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.Configuration, image);
HandleIccProfile(options, image);
return image;
}
@ -66,6 +69,7 @@ public abstract class ImageDecoder : IImageDecoder
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.Configuration, image);
HandleIccProfile(options, image);
return image;
}
@ -79,6 +83,7 @@ public abstract class ImageDecoder : IImageDecoder
s => this.Identify(options, s, default));
this.SetDecoderFormat(options.Configuration, info);
HandleIccProfile(options, info);
return info;
}
@ -93,6 +98,7 @@ public abstract class ImageDecoder : IImageDecoder
cancellationToken).ConfigureAwait(false);
this.SetDecoderFormat(options.Configuration, info);
HandleIccProfile(options, info);
return info;
}
@ -315,4 +321,20 @@ public abstract class ImageDecoder : IImageDecoder
}
}
}
private static void HandleIccProfile(DecoderOptions options, Image image)
{
if (options.CanRemoveIccProfile(image.Metadata.IccProfile))
{
image.Metadata.IccProfile = null;
}
}
private static void HandleIccProfile(DecoderOptions options, ImageInfo image)
{
if (options.CanRemoveIccProfile(image.Metadata.IccProfile))
{
image.Metadata.IccProfile = null;
}
}
}

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

@ -1,6 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
@ -16,6 +23,10 @@ internal abstract partial class JpegColorConverterBase
public override void ConvertToRgbInPlace(in ComponentValues values) =>
ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane);
@ -75,5 +86,31 @@ internal abstract partial class JpegColorConverterBase
k[i] = maxValue - ktmp;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

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

@ -1,9 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -46,6 +47,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);

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

@ -1,9 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -46,6 +47,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane);

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

@ -1,9 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -16,6 +17,10 @@ internal abstract partial class JpegColorConverterBase
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{

61
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -17,21 +22,65 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(values.Component0, this.MaximumValue);
=> ConvertToRgbInPlace(in values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgbScalar(values, rLane, gLane, bLane);
internal static void ConvertToRgbInPlace(Span<float> values, float maxValue)
internal static void ConvertToRgbInPlace(in ComponentValues values, float maxValue)
{
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0);
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1);
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2);
float scale = 1F / maxValue;
for (nuint i = 0; i < (nuint)values.Component0.Length; i++)
{
float c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
ref float valuesRef = ref MemoryMarshal.GetReference(values);
float scale = 1 / maxValue;
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
ref float c0Base = ref MemoryMarshal.GetReference(c0);
ref float c1Base = ref MemoryMarshal.GetReference(c1);
ref float c2Base = ref MemoryMarshal.GetReference(c2);
for (nuint i = 0; i < (uint)values.Length; i++)
float scale = 1F / maxValue;
for (nuint i = 0; i < (nuint)values.Component0.Length; i++)
{
Unsafe.Add(ref valuesRef, i) *= scale;
ref float c = ref Unsafe.Add(ref c0Base, i);
c *= scale;
}
Span<Y> source = MemoryMarshal.Cast<float, Y>(values.Component0);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Y, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
internal static void ConvertFromRgbScalar(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)

18
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -17,20 +18,33 @@ internal abstract partial class JpegColorConverterBase
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlace(in ComponentValues values)
{
ref Vector128<float> c0Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector128<float> c1Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector128<float> c2Base =
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
Vector128<float> scale = Vector128.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector128Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector128<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 *= scale;
Vector128<float> c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}

18
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -23,17 +24,30 @@ internal abstract partial class JpegColorConverterBase
ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
Vector256<float> scale = Vector256.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector256Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 *= scale;
Vector256<float> c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

20
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -17,20 +18,33 @@ internal abstract partial class JpegColorConverterBase
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{
ref Vector512<float> c0Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector512<float> c1Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector512<float> c2Base =
ref Unsafe.As<float, Vector512<float>>(ref MemoryMarshal.GetReference(values.Component2));
// Used for the color conversion
Vector512<float> scale = Vector512.Create(1 / this.MaximumValue);
nuint n = values.Component0.Vector512Count<float>();
for (nuint i = 0; i < n; i++)
{
ref Vector512<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 *= scale;
Vector512<float> c = Unsafe.Add(ref c0Base, i) * scale;
Unsafe.Add(ref c0Base, i) = c;
Unsafe.Add(ref c1Base, i) = c;
Unsafe.Add(ref c2Base, i) = c;
}
}
@ -66,7 +80,7 @@ internal abstract partial class JpegColorConverterBase
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> GrayScaleScalar.ConvertToRgbInPlace(values.Component0, this.MaximumValue);
=> GrayScaleScalar.ConvertToRgbInPlace(in values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)

52
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs

@ -1,6 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
@ -16,15 +24,53 @@ internal abstract partial class JpegColorConverterBase
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, rLane, gLane, bLane);
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue);
Span<Rgb> source = MemoryMarshal.Cast<float, Rgb>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
ColorProfileConverter converter = new(options);
converter.Convert<Rgb, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
internal static void ConvertToRgbInPlace(ComponentValues values, float maxValue)
{
GrayScaleScalar.ConvertToRgbInPlace(values.Component0, maxValue);
GrayScaleScalar.ConvertToRgbInPlace(values.Component1, maxValue);
GrayScaleScalar.ConvertToRgbInPlace(values.Component2, maxValue);
ref float c0Base = ref MemoryMarshal.GetReference(values.Component0);
ref float c1Base = ref MemoryMarshal.GetReference(values.Component1);
ref float c2Base = ref MemoryMarshal.GetReference(values.Component2);
float scale = 1F / maxValue;
for (nuint i = 0; i < (nuint)values.Component0.Length; i++)
{
Unsafe.Add(ref c0Base, i) *= scale;
Unsafe.Add(ref c1Base, i) *= scale;
Unsafe.Add(ref c2Base, i) *= scale;
}
}
internal static void ConvertFromRgb(ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)

7
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs

@ -1,9 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -40,6 +41,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

7
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs

@ -1,9 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -40,6 +41,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

7
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs

@ -1,9 +1,10 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -40,6 +41,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertFromRgbVectorized(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

48
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs

@ -1,6 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
@ -22,6 +29,10 @@ internal abstract partial class JpegColorConverterBase
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane);
@ -49,6 +60,43 @@ internal abstract partial class JpegColorConverterBase
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 3);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
// Although YCbCr is a defined ICC color space, in practice ICC profiles
// do not implement transforms from it.
// Therefore, we first convert JPEG YCbCr to RGB manually, then perform
// color-managed conversion to the target profile.
//
// The YCbCr => RGB conversion is based on BT.601 and is independent of any embedded ICC profile.
// Since the same RGB working space is used during conversion to and from XYZ,
// colorimetric accuracy is preserved.
ColorProfileConverter converter = new();
PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue);
Span<YCbCr> source = MemoryMarshal.Cast<float, YCbCr>(packed);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed);
converter.Convert<YCbCr, Rgb>(source, destination);
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new(options);
converter.Convert<Rgb, Rgb>(destination, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{
Span<float> y = values.Component0;

5
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -66,6 +67,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

5
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -66,6 +67,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

5
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -17,6 +18,10 @@ internal abstract partial class JpegColorConverterBase
{
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values)
{

48
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs

@ -1,6 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Numerics;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.ColorProfiles;
using SixLabors.ImageSharp.ColorProfiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
internal abstract partial class JpegColorConverterBase
@ -22,6 +29,10 @@ internal abstract partial class JpegColorConverterBase
public override void ConvertToRgbInPlace(in ComponentValues values)
=> ConvertToRgpInPlace(values, this.MaximumValue, this.HalfValue);
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
=> ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane);
@ -73,5 +84,42 @@ internal abstract partial class JpegColorConverterBase
y[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b);
}
}
public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue)
{
using IMemoryOwner<float> memoryOwner = configuration.MemoryAllocator.Allocate<float>(values.Component0.Length * 4);
Span<float> packed = memoryOwner.Memory.Span;
Span<float> c0 = values.Component0;
Span<float> c1 = values.Component1;
Span<float> c2 = values.Component2;
Span<float> c3 = values.Component3;
PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue);
ColorProfileConverter converter = new();
Span<Cmyk> source = MemoryMarshal.Cast<float, Cmyk>(packed);
// YccK is not a defined ICC color space — it's a JPEG-specific encoding used in Adobe-style CMYK JPEGs.
// ICC profiles expect colorimetric CMYK values, so we must first convert YccK to CMYK using a hardcoded inverse transform.
// This transform assumes Rec.601 YCbCr coefficients and an inverted K channel.
//
// The YccK => Cmyk conversion is independent of any embedded ICC profile.
// Since the same RGB working space is used during conversion to and from XYZ,
// colorimetric accuracy is preserved.
converter.Convert<YccK, Cmyk>(MemoryMarshal.Cast<Cmyk, YccK>(source), source);
Span<Rgb> destination = MemoryMarshal.Cast<float, Rgb>(packed)[..source.Length];
ColorConversionOptions options = new()
{
SourceIccProfile = profile,
TargetIccProfile = CompactSrgbV4Profile.Profile,
};
converter = new(options);
converter.Convert<Cmyk, Rgb>(source, destination);
UnpackDeinterleave3(MemoryMarshal.Cast<float, Vector3>(packed)[..source.Length], c0, c1, c2);
}
}
}

5
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -75,6 +76,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

5
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -75,6 +76,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
public override void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane)
{

5
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs

@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -75,6 +76,10 @@ internal abstract partial class JpegColorConverterBase
}
}
/// <inheritdoc/>
public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile)
=> YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue);
/// <inheritdoc/>
protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values)
=> YccKScalar.ConvertToRgpInPlace(values, this.MaximumValue, this.HalfValue);

97
src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterBase.cs

@ -2,7 +2,11 @@
// Licensed under the Six Labors Split License.
#nullable disable
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components;
@ -80,6 +84,14 @@ internal abstract partial class JpegColorConverterBase
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct</param>
public abstract void ConvertToRgbInPlace(in ComponentValues values);
/// <summary>
/// Converts planar jpeg component values in <paramref name="values"/> to RGB color space in-place using the given ICC profile.
/// </summary>
/// <param name="configuration">The configuration instance to use for the conversion.</param>
/// <param name="values">The input/output as a stack-only <see cref="ComponentValues"/> struct.</param>
/// <param name="profile">The ICC profile to use for the conversion.</param>
public abstract void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile);
/// <summary>
/// Converts RGB lanes to jpeg component values.
/// </summary>
@ -89,6 +101,91 @@ internal abstract partial class JpegColorConverterBase
/// <param name="bLane">Blue colors lane.</param>
public abstract void ConvertFromRgb(in ComponentValues values, Span<float> rLane, Span<float> gLane, Span<float> bLane);
public static void PackedNormalizeInterleave3(
ReadOnlySpan<float> xLane,
ReadOnlySpan<float> yLane,
ReadOnlySpan<float> zLane,
Span<float> packed,
float scale)
{
DebugGuard.IsTrue(packed.Length % 3 == 0, "Packed length must be divisible by 3.");
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!");
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!");
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 3, xLane.Length, nameof(packed));
// TODO: Investigate SIMD version of this.
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane);
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane);
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane);
ref float packedRef = ref MemoryMarshal.GetReference(packed);
for (nuint i = 0; i < (nuint)xLane.Length; i++)
{
nuint baseIdx = i * 3;
Unsafe.Add(ref packedRef, baseIdx) = Unsafe.Add(ref xLaneRef, i) * scale;
Unsafe.Add(ref packedRef, baseIdx + 1) = Unsafe.Add(ref yLaneRef, i) * scale;
Unsafe.Add(ref packedRef, baseIdx + 2) = Unsafe.Add(ref zLaneRef, i) * scale;
}
}
public static void UnpackDeinterleave3(
ReadOnlySpan<Vector3> packed,
Span<float> xLane,
Span<float> yLane,
Span<float> zLane)
{
DebugGuard.IsTrue(packed.Length == xLane.Length, nameof(packed), "Channels must be of same size!");
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!");
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!");
// TODO: Investigate SIMD version of this.
ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<Vector3, float>(packed));
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane);
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane);
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane);
for (nuint i = 0; i < (nuint)packed.Length; i++)
{
nuint baseIdx = i * 3;
Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx);
Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx + 1);
Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, baseIdx + 2);
}
}
public static void PackedInvertNormalizeInterleave4(
ReadOnlySpan<float> xLane,
ReadOnlySpan<float> yLane,
ReadOnlySpan<float> zLane,
ReadOnlySpan<float> wLane,
Span<float> packed,
float maxValue)
{
DebugGuard.IsTrue(packed.Length % 4 == 0, "Packed length must be divisible by 4.");
DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!");
DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!");
DebugGuard.IsTrue(wLane.Length == xLane.Length, nameof(wLane), "Channels must be of same size!");
DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 4, xLane.Length, nameof(packed));
float scale = 1F / maxValue;
// TODO: Investigate SIMD version of this.
ref float xLaneRef = ref MemoryMarshal.GetReference(xLane);
ref float yLaneRef = ref MemoryMarshal.GetReference(yLane);
ref float zLaneRef = ref MemoryMarshal.GetReference(zLane);
ref float wLaneRef = ref MemoryMarshal.GetReference(wLane);
ref float packedRef = ref MemoryMarshal.GetReference(packed);
for (nuint i = 0; i < (nuint)xLane.Length; i++)
{
nuint baseIdx = i * 4;
Unsafe.Add(ref packedRef, baseIdx) = (maxValue - Unsafe.Add(ref xLaneRef, i)) * scale;
Unsafe.Add(ref packedRef, baseIdx + 1) = (maxValue - Unsafe.Add(ref yLaneRef, i)) * scale;
Unsafe.Add(ref packedRef, baseIdx + 2) = (maxValue - Unsafe.Add(ref zLaneRef, i)) * scale;
Unsafe.Add(ref packedRef, baseIdx + 3) = (maxValue - Unsafe.Add(ref wLaneRef, i)) * scale;
}
}
/// <summary>
/// Returns the <see cref="JpegColorConverterBase"/>s for all supported color spaces and precisions.
/// </summary>

42
src/ImageSharp/Formats/Jpeg/Components/Decoder/ArithmeticScanDecoder.cs

@ -5,6 +5,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -54,12 +55,12 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
private ArithmeticDecodingTable[] acDecodingTables;
// Don't make this a ReadOnlySpan<byte>, as the values need to get updated.
private readonly byte[] fixedBin = { 113, 0, 0, 0 };
private readonly byte[] fixedBin = [113, 0, 0, 0];
private readonly CancellationToken cancellationToken;
private static readonly int[] ArithmeticTable =
{
[
Pack(0x5a1d, 1, 1, 1),
Pack(0x2586, 14, 2, 0),
Pack(0x1114, 16, 3, 0),
@ -177,9 +178,9 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
// This last entry is used for fixed probability estimate of 0.5
// as suggested in Section 10.3 Table 5 of ITU-T Rec. T.851.
Pack(0x5a1d, 113, 113, 0)
};
];
private readonly List<ArithmeticStatistics> statistics = new();
private readonly List<ArithmeticStatistics> statistics = [];
/// <summary>
/// Initializes a new instance of the <see cref="ArithmeticScanDecoder"/> class.
@ -234,11 +235,8 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
private ref byte GetFixedBinReference() => ref MemoryMarshal.GetArrayDataReference(this.fixedBin);
/// <summary>
/// Decodes the entropy coded data.
/// </summary>
/// <param name="scanComponentCount">Component count in the current scan.</param>
public void ParseEntropyCodedData(int scanComponentCount)
/// <inheritdoc/>
public void ParseEntropyCodedData(int scanComponentCount, IccProfile iccProfile)
{
this.cancellationToken.ThrowIfCancellationRequested();
@ -254,7 +252,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
}
else
{
this.ParseBaselineData();
this.ParseBaselineData(iccProfile);
}
if (this.scanBuffer.HasBadMarker())
@ -310,7 +308,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
return statistic;
}
private void ParseBaselineData()
private void ParseBaselineData(IccProfile iccProfile)
{
for (int i = 0; i < this.components.Length; i++)
{
@ -326,13 +324,13 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
if (this.scanComponentCount != 1)
{
this.spectralConverter.PrepareForDecoding();
this.ParseBaselineDataInterleaved();
this.ParseBaselineDataInterleaved(iccProfile);
this.spectralConverter.CommitConversion();
}
else if (this.frame.ComponentCount == 1)
{
this.spectralConverter.PrepareForDecoding();
this.ParseBaselineDataSingleComponent();
this.ParseBaselineDataSingleComponent(iccProfile);
this.spectralConverter.CommitConversion();
}
else
@ -345,8 +343,9 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
{
this.CheckProgressiveData();
foreach (ArithmeticDecodingComponent component in this.components)
for (int i = 0; i < this.components.Length; i++)
{
ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[i];
if (this.SpectralStart == 0 && this.SuccessiveHigh == 0)
{
component.DcPredictor = 0;
@ -422,7 +421,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
}
}
private void ParseBaselineDataInterleaved()
private void ParseBaselineDataInterleaved(IccProfile iccProfile)
{
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
@ -463,7 +462,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
{
// It is very likely that some spectral data was decoded before we've encountered 'end of scan'
// so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
return;
}
@ -485,11 +484,11 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
}
// Convert from spectral to actual pixels via given converter.
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
}
}
private void ParseBaselineDataSingleComponent()
private void ParseBaselineDataSingleComponent(IccProfile iccProfile)
{
ArithmeticDecodingComponent component = this.frame.Components[0] as ArithmeticDecodingComponent;
int mcuLines = this.frame.McusPerColumn;
@ -516,7 +515,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
{
// It is very likely that some spectral data was decoded before we've encountered 'end of scan'
// so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
return;
}
@ -531,7 +530,7 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
}
// Convert from spectral to actual pixels via given converter.
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
}
}
@ -1108,8 +1107,9 @@ internal class ArithmeticScanDecoder : IJpegScanDecoder
this.todo = this.restartInterval;
foreach (ArithmeticDecodingComponent component in this.components)
for (int i = 0; i < this.components.Length; i++)
{
ArithmeticDecodingComponent component = (ArithmeticDecodingComponent)this.components[i];
component.DcPredictor = 0;
component.DcContext = 0;
component.DcStatistics?.Reset();

33
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -5,6 +5,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -109,7 +110,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
public int SuccessiveLow { get; set; }
/// <inheritdoc/>
public void ParseEntropyCodedData(int scanComponentCount)
public void ParseEntropyCodedData(int scanComponentCount, IccProfile iccProfile)
{
this.cancellationToken.ThrowIfCancellationRequested();
@ -123,7 +124,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
if (!this.frame.Progressive)
{
this.ParseBaselineData();
this.ParseBaselineData(iccProfile);
}
else
{
@ -145,18 +146,18 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
this.spectralConverter.InjectFrameData(frame, jpegData);
}
private void ParseBaselineData()
private void ParseBaselineData(IccProfile iccProfile)
{
if (this.scanComponentCount != 1)
{
this.spectralConverter.PrepareForDecoding();
this.ParseBaselineDataInterleaved();
this.ParseBaselineDataInterleaved(iccProfile);
this.spectralConverter.CommitConversion();
}
else if (this.frame.ComponentCount == 1)
{
this.spectralConverter.PrepareForDecoding();
this.ParseBaselineDataSingleComponent();
this.ParseBaselineDataSingleComponent(iccProfile);
this.spectralConverter.CommitConversion();
}
else
@ -165,7 +166,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
}
}
private void ParseBaselineDataInterleaved()
private void ParseBaselineDataInterleaved(IccProfile iccProfile)
{
int mcu = 0;
int mcusPerColumn = this.frame.McusPerColumn;
@ -184,7 +185,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
for (int k = 0; k < this.scanComponentCount; k++)
{
int order = this.frame.ComponentOrder[k];
var component = this.components[order] as JpegComponent;
JpegComponent component = this.components[order] as JpegComponent;
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.AcTableId];
@ -205,7 +206,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
{
// It is very likely that some spectral data was decoded before we've encountered 'end of scan'
// so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
return;
}
@ -227,13 +228,13 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
}
// Convert from spectral to actual pixels via given converter
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
}
}
private void ParseBaselineDataNonInterleaved()
{
var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
JpegComponent component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
ref JpegBitReader buffer = ref this.scanBuffer;
int w = component.WidthInBlocks;
@ -266,7 +267,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
}
}
private void ParseBaselineDataSingleComponent()
private void ParseBaselineDataSingleComponent(IccProfile iccProfile)
{
JpegComponent component = this.frame.Components[0];
int mcuLines = this.frame.McusPerColumn;
@ -293,7 +294,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
{
// It is very likely that some spectral data was decoded before we've encountered 'end of scan'
// so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
return;
}
@ -308,7 +309,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
}
// Convert from spectral to actual pixels via given converter
this.spectralConverter.ConvertStrideBaseline();
this.spectralConverter.ConvertStrideBaseline(iccProfile);
}
}
@ -394,7 +395,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
for (int k = 0; k < this.scanComponentCount; k++)
{
int order = this.frame.ComponentOrder[k];
var component = this.components[order] as JpegComponent;
JpegComponent component = this.components[order] as JpegComponent;
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DcTableId];
int h = component.HorizontalSamplingFactor;
@ -435,7 +436,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
private void ParseProgressiveDataNonInterleaved()
{
var component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
JpegComponent component = this.components[this.frame.ComponentOrder[0]] as JpegComponent;
ref JpegBitReader buffer = ref this.scanBuffer;
int w = component.WidthInBlocks;
@ -772,7 +773,7 @@ internal class HuffmanScanDecoder : IJpegScanDecoder
}
/// <summary>
/// Build the huffman table using code lengths and code values.
/// Build the Huffman table using code lengths and code values.
/// </summary>
/// <param name="type">Table type.</param>
/// <param name="index">Table index.</param>

19
src/ImageSharp/Formats/Jpeg/Components/Decoder/IJpegScanDecoder.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
/// <summary>
@ -11,38 +13,41 @@ internal interface IJpegScanDecoder
/// <summary>
/// Sets the reset interval.
/// </summary>
int ResetInterval { set; }
public int ResetInterval { set; }
/// <summary>
/// Gets or sets the spectral selection start.
/// </summary>
int SpectralStart { get; set; }
public int SpectralStart { get; set; }
/// <summary>
/// Gets or sets the spectral selection end.
/// </summary>
int SpectralEnd { get; set; }
public int SpectralEnd { get; set; }
/// <summary>
/// Gets or sets the successive approximation high bit end.
/// </summary>
int SuccessiveHigh { get; set; }
public int SuccessiveHigh { get; set; }
/// <summary>
/// Gets or sets the successive approximation low bit end.
/// </summary>
int SuccessiveLow { get; set; }
public int SuccessiveLow { get; set; }
/// <summary>
/// Decodes the entropy coded data.
/// </summary>
/// <param name="scanComponentCount">Component count in the current scan.</param>
void ParseEntropyCodedData(int scanComponentCount);
/// <param name="iccProfile">
/// The ICC profile to use for color conversion. If null, the default color space.
/// </param>
public void ParseEntropyCodedData(int scanComponentCount, IccProfile? iccProfile);
/// <summary>
/// Sets the JpegFrame and its components and injects the frame data into the spectral converter.
/// </summary>
/// <param name="frame">The frame.</param>
/// <param name="jpegData">The raw JPEG data.</param>
void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
}

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBitReader.cs

@ -76,13 +76,13 @@ internal struct JpegBitReader
/// Whether a RST marker has been detected, I.E. One that is between RST0 and RST7
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public bool HasRestartMarker() => HasRestart(this.Marker);
public readonly bool HasRestartMarker() => HasRestart(this.Marker);
/// <summary>
/// Whether a bad marker has been detected, I.E. One that is not between RST0 and RST7
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker();
public readonly bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker();
[MethodImpl(InliningOptions.AlwaysInline)]
public void FillBuffer()
@ -132,7 +132,7 @@ internal struct JpegBitReader
public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits -= nbits, nbits);
[MethodImpl(InliningOptions.ShortMethod)]
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits);
public readonly int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits);
[MethodImpl(InliningOptions.AlwaysInline)]
private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);

16
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -1,6 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
/// <summary>
@ -11,8 +13,9 @@ internal abstract class SpectralConverter
/// <summary>
/// Supported scaled spectral block sizes for scaled IDCT decoding.
/// </summary>
private static readonly int[] ScaledBlockSizes = new int[]
{
private static readonly int[] ScaledBlockSizes =
[
// 8 => 1, 1/8 of the original size
1,
@ -21,7 +24,7 @@ internal abstract class SpectralConverter
// 8 => 4, 1/2 of the original size
4,
};
];
/// <summary>
/// Gets a value indicating whether this converter has converted spectral
@ -50,13 +53,16 @@ internal abstract class SpectralConverter
/// Converts single spectral jpeg stride to color stride in baseline
/// decoding mode.
/// </summary>
/// <param name="iccProfile">
/// The ICC profile to use for color conversion. If <see langword="null"/>, then the default color space is used.
/// </param>
/// <remarks>
/// Called once per decoded spectral stride in <see cref="HuffmanScanDecoder"/>
/// only for baseline interleaved jpeg images.
/// Spectral 'stride' doesn't particularly mean 'single stride'.
/// Actual stride height depends on the subsampling factor of the given image.
/// </remarks>
public abstract void ConvertStrideBaseline();
public abstract void ConvertStrideBaseline(IccProfile? iccProfile);
/// <summary>
/// Marks current converter state as 'converted'.
@ -83,7 +89,7 @@ internal abstract class SpectralConverter
/// Calculates image size with optional scaling.
/// </summary>
/// <remarks>
/// Does not apply scalling if <paramref name="targetSize"/> is null.
/// Does not apply scaling if <paramref name="targetSize"/> is null.
/// </remarks>
/// <param name="size">Size of the image.</param>
/// <param name="targetSize">Target size of the image.</param>

23
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -4,6 +4,7 @@
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
@ -98,9 +99,10 @@ internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
/// For non-baseline interleaved jpeg this method does a 'lazy' spectral
/// conversion from spectral to color.
/// </remarks>
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Pixel buffer.</returns>
public Buffer2D<TPixel> GetPixelBuffer(CancellationToken cancellationToken)
public Buffer2D<TPixel> GetPixelBuffer(IccProfile iccProfile, CancellationToken cancellationToken)
{
if (!this.Converted)
{
@ -111,7 +113,7 @@ internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
for (int step = 0; step < steps; step++)
{
cancellationToken.ThrowIfCancellationRequested();
this.ConvertStride(step);
this.ConvertStride(step, iccProfile);
}
}
@ -124,7 +126,8 @@ internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
/// Converts single spectral jpeg stride to color stride.
/// </summary>
/// <param name="spectralStep">Spectral stride index.</param>
private void ConvertStride(int spectralStep)
/// <param name="iccProfile">Optional ICC profile for color conversion.</param>
private void ConvertStride(int spectralStep, IccProfile iccProfile)
{
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep);
@ -141,7 +144,15 @@ internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
JpegColorConverterBase.ComponentValues values = new(this.componentProcessors, y);
this.colorConverter.ConvertToRgbInPlace(values);
if (iccProfile != null)
{
this.colorConverter.ConvertToRgbInPlaceWithIcc(this.Configuration, in values, iccProfile);
}
else
{
this.colorConverter.ConvertToRgbInPlace(in values);
}
values = values.Slice(0, width); // slice away Jpeg padding
Span<byte> r = this.rgbBuffer.Slice(0, width);
@ -222,11 +233,11 @@ internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
}
/// <inheritdoc/>
public override void ConvertStrideBaseline()
public override void ConvertStrideBaseline(IccProfile iccProfile)
{
// Convert next pixel stride using single spectral `stride'
// Note that zero passing eliminates extra virtual call
this.ConvertStride(spectralStep: 0);
this.ConvertStride(spectralStep: 0, iccProfile);
foreach (ComponentProcessor cpp in this.componentProcessors)
{

12
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -127,7 +127,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
/// Gets the only supported precisions
/// </summary>
// Refers to assembly's static data segment, no allocation occurs.
private static ReadOnlySpan<byte> SupportedPrecisions => new byte[] { 8, 12 };
private static ReadOnlySpan<byte> SupportedPrecisions => [8, 12];
/// <summary>
/// Gets the frame
@ -201,9 +201,11 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
this.InitXmpProfile();
this.InitDerivedMetadataProperties();
_ = this.Options.TryGetIccProfileForColorConversion(this.Metadata.IccProfile, out IccProfile profile);
return new Image<TPixel>(
this.configuration,
spectralConverter.GetPixelBuffer(cancellationToken),
spectralConverter.GetPixelBuffer(profile, cancellationToken),
this.Metadata);
}
@ -666,7 +668,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
/// </summary>
private void InitIccProfile()
{
if (this.hasIcc)
if (this.hasIcc && this.Metadata.IccProfile == null)
{
IccProfile profile = new(this.iccData);
if (profile.CheckIsValid())
@ -1512,7 +1514,9 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
arithmeticScanDecoder.InitDecodingTables(this.arithmeticDecodingTables);
}
this.scanDecoder.ParseEntropyCodedData(selectorsCount);
this.InitIccProfile();
_ = this.Options.TryGetIccProfileForColorConversion(this.Metadata.IccProfile, out IccProfile profile);
this.scanDecoder.ParseEntropyCodedData(selectorsCount, profile);
}
/// <summary>

13
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
@ -73,7 +74,11 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
jpegDecoder.LoadTables(this.jpegTables, scanDecoderGray);
jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken);
_ = this.options.GeneralOptions.TryGetIccProfileForColorConversion(
jpegDecoder.Metadata?.IccProfile,
out IccProfile? profile);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(profile, cancellationToken);
JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverterGray.Configuration, buffer, decompressedBuffer);
break;
}
@ -87,7 +92,11 @@ internal sealed class JpegTiffCompression : TiffBaseDecompressor
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken);
_ = this.options.GeneralOptions.TryGetIccProfileForColorConversion(
jpegDecoder.Metadata?.IccProfile,
out IccProfile? profile);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(profile, cancellationToken);
JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverter.Configuration, buffer, decompressedBuffer);
break;
}

15
src/ImageSharp/Formats/Tiff/Compression/Decompressors/OldJpegTiffCompression.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
@ -57,7 +58,13 @@ internal sealed class OldJpegTiffCompression : TiffBaseDecompressor
jpegDecoder.ParseStream(stream, spectralConverterGray, cancellationToken);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(cancellationToken);
_ = this.options.GeneralOptions.TryGetIccProfileForColorConversion(
jpegDecoder.Metadata?.IccProfile,
out IccProfile? profile);
using Buffer2D<L8> decompressedBuffer = spectralConverterGray.GetPixelBuffer(
profile,
cancellationToken);
JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverterGray.Configuration, buffer, decompressedBuffer);
break;
}
@ -69,7 +76,11 @@ internal sealed class OldJpegTiffCompression : TiffBaseDecompressor
jpegDecoder.ParseStream(stream, spectralConverter, cancellationToken);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(cancellationToken);
_ = this.options.GeneralOptions.TryGetIccProfileForColorConversion(
jpegDecoder.Metadata?.IccProfile,
out IccProfile? profile);
using Buffer2D<Rgb24> decompressedBuffer = spectralConverter.GetPixelBuffer(profile, cancellationToken);
JpegCompressionUtils.CopyImageBytesToBuffer(spectralConverter.Configuration, buffer, decompressedBuffer);
break;
}

2
src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs

@ -202,7 +202,7 @@ public sealed class IccProfile : IDeepCloneable<IccProfile>
if (this.data is null)
{
this.entries = Array.Empty<IccTagDataEntry>();
this.entries = [];
return;
}

40
src/ImageSharp/Metadata/Profiles/ICC/IccProfileHeader.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable disable
@ -11,6 +11,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Icc;
/// </summary>
public sealed class IccProfileHeader
{
private static readonly Vector3 TruncatedD50 = new(0.9642029F, 1F, 0.8249054F);
// sRGB v2 Preference
private static readonly IccProfileId StandardRgbV2 = new(0x3D0EB2DE, 0xAE9397BE, 0x9B6726CE, 0x8C0A43CE);
// sRGB v4 Preference
private static readonly IccProfileId StandardRgbV4 = new(0x34562ABF, 0x994CCD06, 0x6D2C5721, 0xD0D68C5D);
// sRGB v4 Appearance
private static readonly IccProfileId StandardRgbV4A = new(0xDF1132A1, 0x746E97B0, 0xAD85719, 0xBE711E08);
/// <summary>
/// Gets or sets the profile size in bytes (will be ignored when writing a profile).
/// </summary>
@ -97,4 +108,31 @@ public sealed class IccProfileHeader
/// Gets or sets the profile ID (hash).
/// </summary>
public IccProfileId Id { get; set; }
internal static bool IsLikelySrgb(IccProfileHeader header)
{
// Reject known perceptual-appearance profile
// This profile employs perceptual rendering intents to maintain color appearance across different
// devices and media, which can lead to variations from standard sRGB representations.
if (header.Id == StandardRgbV4A)
{
return false;
}
// Accept known sRGB profile IDs
if (header.Id == StandardRgbV2 || header.Id == StandardRgbV4)
{
return true;
}
// Fallback: best-guess heuristic
return
header.FileSignature == "acsp" &&
header.DataColorSpace == IccColorSpaceType.Rgb &&
(header.ProfileConnectionSpace == IccColorSpaceType.CieXyz || header.ProfileConnectionSpace == IccColorSpaceType.CieLab) &&
(header.Class == IccProfileClass.DisplayDevice || header.Class == IccProfileClass.ColorSpace) &&
header.PcsIlluminant == TruncatedD50 &&
(header.Version.Major == 2 || header.Version.Major == 4) &&
!string.Equals(header.CmmType, "ADBE", StringComparison.Ordinal);
}
}

3
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs

@ -5,6 +5,7 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Tests;
using SDSize = System.Drawing.Size;
@ -50,7 +51,7 @@ public class DecodeJpegParseStreamOnly
// There's no way to eliminate it as spectral conversion is built into the scan decoding loop for memory footprint reduction
private sealed class NoopSpectralConverter : SpectralConverter
{
public override void ConvertStrideBaseline()
public override void ConvertStrideBaseline(IccProfile iccProfile)
{
}

1
tests/ImageSharp.Tests/ColorProfiles/Icc/ColorProfileConverterTests.Icc.cs

@ -62,6 +62,7 @@ public class ColorProfileConverterTests(ITestOutputHelper testOutputHelper)
[InlineData(TestIccProfiles.RommRgb, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> CMYK (different bit depth v2 LUTs, 16-bit vs 8-bit)
[InlineData(TestIccProfiles.Fogra39, TestIccProfiles.StandardRgbV2, 0.0005)] // CMYK -> LAB -> XYZ -> RGB (different LUT tags, A2B vs TRC) --- tolerance slightly higher due to difference in inverse curve implementation
[InlineData(TestIccProfiles.StandardRgbV2, TestIccProfiles.Fogra39)] // RGB -> XYZ -> LAB -> CMYK (different LUT tags, TRC vs A2B)
[InlineData(TestIccProfiles.Issue129, TestIccProfiles.StandardRgbV4)] // CMYK -> LAB -> -> XYZ -> RGB
public void CanBulkConvertIccProfiles(string sourceProfile, string targetProfile, double tolerance = 0.00005)
{
List<Vector4> actual = GetBulkActualTargetValues(Inputs, sourceProfile, targetProfile);

6
tests/ImageSharp.Tests/ColorProfiles/Icc/TestIccProfiles.cs

@ -16,6 +16,7 @@ internal static class TestIccProfiles
/// v2 CMYK -> LAB, output, lut16
/// </summary>
public const string Fogra39 = "Coated_Fogra39L_VIGC_300.icc";
/// <summary>
/// v2 CMYK -> LAB, output, lut16
/// </summary>
@ -46,6 +47,11 @@ internal static class TestIccProfiles
/// </summary>
public const string StandardRgbV4 = "sRGB_v4_ICC_preference.icc";
/// <summary>
/// v2 CMYK -> LAB, output
/// </summary>
public const string Issue129 = "issue-129.icc";
/// <summary>
/// v2 RGB -> XYZ, display, TRCs
/// </summary>

6
tests/ImageSharp.Tests/ColorProfiles/RbgAndYConversionTests.cs

@ -23,7 +23,7 @@ public class RbgAndYConversionTests
{
ColorConversionOptions options = new()
{
YCbCrMatrix = KnownYCbCrMatrices.BT601
YCbCrTransform = KnownYCbCrMatrices.BT601
};
Convert_Rgb_To_Y_Core(r, g, b, y, options);
@ -37,7 +37,7 @@ public class RbgAndYConversionTests
{
ColorConversionOptions options = new()
{
YCbCrMatrix = KnownYCbCrMatrices.BT709
YCbCrTransform = KnownYCbCrMatrices.BT709
};
Convert_Rgb_To_Y_Core(r, g, b, y, options);
@ -51,7 +51,7 @@ public class RbgAndYConversionTests
{
ColorConversionOptions options = new()
{
YCbCrMatrix = KnownYCbCrMatrices.BT2020
YCbCrTransform = KnownYCbCrMatrices.BT2020
};
Convert_Rgb_To_Y_Core(r, g, b, y, options);

51
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -3,7 +3,6 @@
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -365,4 +364,54 @@ public partial class JpegDecoderTests
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
[Theory]
[WithFile(TestImages.Jpeg.ICC.CMYK, PixelTypes.Rgba32)]
public void Decode_CMYK_ICC_Jpeg<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
JpegDecoderOptions options = new()
{
GeneralOptions = new() { ColorProfileHandling = ColorProfileHandling.Convert }
};
using Image<TPixel> image = provider.GetImage(JpegDecoder.Instance, options);
image.DebugSave(provider);
image.CompareToReferenceOutput(provider);
}
[Theory]
[WithFile(TestImages.Jpeg.ICC.YCCK, PixelTypes.Rgba32)]
public void Decode_YCCK_ICC_Jpeg<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
JpegDecoderOptions options = new()
{
GeneralOptions = new() { ColorProfileHandling = ColorProfileHandling.Convert }
};
using Image<TPixel> image = provider.GetImage(JpegDecoder.Instance, options);
image.DebugSave(provider);
image.CompareToReferenceOutput(provider);
}
[Theory]
[WithFile(TestImages.Jpeg.ICC.SRgb, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.ICC.AdobeRgb, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.ICC.ColorMatch, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgba32)]
public void Decode_RGB_ICC_Jpeg<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
JpegDecoderOptions options = new()
{
GeneralOptions = new() { ColorProfileHandling = ColorProfileHandling.Convert }
};
using Image<TPixel> image = provider.GetImage(JpegDecoder.Instance, options);
image.DebugSave(provider);
image.CompareToReferenceOutput(provider);
}
}

3
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using Xunit.Abstractions;
@ -164,7 +165,7 @@ public class SpectralJpegTests
}
}
public override void ConvertStrideBaseline()
public override void ConvertStrideBaseline(IccProfile iccProfile)
{
// This would be called only for baseline non-interleaved images
// We must copy spectral strides here

2
tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs

@ -48,7 +48,7 @@ public class SpectralToPixelConversionTests
provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName;
// Comparison
using var image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata());
using var image = new Image<TPixel>(Configuration.Default, converter.GetPixelBuffer(null, CancellationToken.None), new ImageMetadata());
using Image<TPixel> referenceImage = provider.GetReferenceOutputImage<TPixel>(appendPixelTypeToFileName: false);
ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image);

3
tests/ImageSharp.Tests/TestDataIcc/Profiles/issue-129.icc

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:35f401731df11a4eba3502af632e51d68bc394bcb7d34632a331c1ba3f4a0bf6
size 557168

1
tests/ImageSharp.Tests/TestImages.cs

@ -215,6 +215,7 @@ public static class TestImages
public const string WideRGB = "Jpg/icc-profiles/Momiji-WideRGB-yes.jpg";
public const string AppleRGB = "Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg";
public const string CMYK = "Jpg/icc-profiles/issue-129.jpg";
public const string YCCK = "Jpg/icc-profiles/issue_2723.jpg";
}
public static class Progressive

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_CMYK_ICC_Jpeg_Rgba32_issue-129.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:215cba73dfb0e19f75f6dc0c3fefca474bd65f57684a207a11d896e1637bb643
size 1240827

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AdobeRGB-yes.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c942b534baa51b8e46e88bd38d1ced319bccf1b55a5711ae5761697b7437fe4e
size 458321

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-AppleRGB-yes.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:163711226bdfa2f102b314435baea9f69ad1be1b11ef5ad8348358cd09a029ae
size 482963

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ColorMatch-yes.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f7981f40bab5bffff3e7c9ea1676d224c173fabbaa6e7a920d7a9dabd58655f
size 464917

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-ProPhoto-yes.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:11b02b982c024e295915e88f56a428ad73068217a7ae625f705127ab8c35a4bf
size 477061

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-WideRGB-yes.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a49967c20cccf824df4de3f105f5ddb44d7a602c072ce22caa38939f21f62505
size 469827

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_RGB_ICC_Jpeg_Rgba32_Momiji-sRGB-yes.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4b33fc8fd03142aaaf8aabc39e084acd9e82e9222292e281953d92d65edcc1a7
size 436111

3
tests/Images/External/ReferenceOutput/JpegDecoderTests/Decode_YCCK_ICC_Jpeg_Rgba32_issue_2723.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:cef85199e8560d6669766c094d078831024e44ac7fc537f8696f802c8e06138b
size 420141

3
tests/Images/Input/Jpg/icc-profiles/issue_2723.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:56dc6400d8f4c42c505ec562e4ade0b23e613b2c16e1666eccbbaa8e997efcc7
size 766523
Loading…
Cancel
Save