diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs index f479d186a8..5997a9cdc8 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/MatrixCalculator.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Numerics; @@ -20,7 +20,7 @@ internal class MatrixCalculator : IVector4Calculator [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 Calculate(Vector4 value) { - var transformed = Vector4.Transform(value, this.matrix2D); + Vector4 transformed = Vector4.Transform(value, this.matrix2D); return Vector4.Add(this.matrix1D, transformed); } } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs index 5a3bab6e86..9312326830 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/Calculators/ParametricCurveCalculator.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. -using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -9,8 +8,8 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; internal class ParametricCurveCalculator : ISingleCalculator { - private IccParametricCurve curve; - private IccParametricCurveType type; + private readonly IccParametricCurve curve; + private readonly IccParametricCurveType type; private const IccParametricCurveType InvertedFlag = (IccParametricCurveType)(1 << 3); public ParametricCurveCalculator(IccParametricCurveTagDataEntry entry, bool inverted) @@ -26,41 +25,23 @@ internal class ParametricCurveCalculator : ISingleCalculator } public float Calculate(float value) - { - switch (this.type) - { - case IccParametricCurveType.Type1: - return this.CalculateGamma(value); - case IccParametricCurveType.Cie122_1996: - return this.CalculateCie122(value); - case IccParametricCurveType.Iec61966_3: - return this.CalculateIec61966(value); - case IccParametricCurveType.SRgb: - return this.CalculateSRgb(value); - case IccParametricCurveType.Type5: - return this.CalculateType5(value); - - case IccParametricCurveType.Type1 | InvertedFlag: - return this.CalculateInvertedGamma(value); - case IccParametricCurveType.Cie122_1996 | InvertedFlag: - return this.CalculateInvertedCie122(value); - case IccParametricCurveType.Iec61966_3 | InvertedFlag: - return this.CalculateInvertedIec61966(value); - case IccParametricCurveType.SRgb | InvertedFlag: - return this.CalculateInvertedSRgb(value); - case IccParametricCurveType.Type5 | InvertedFlag: - return this.CalculateInvertedType5(value); - - default: - throw new InvalidIccProfileException("ParametricCurve"); - } - } + => this.type switch + { + IccParametricCurveType.Type1 => this.CalculateGamma(value), + IccParametricCurveType.Cie122_1996 => this.CalculateCie122(value), + IccParametricCurveType.Iec61966_3 => this.CalculateIec61966(value), + IccParametricCurveType.SRgb => this.CalculateSRgb(value), + IccParametricCurveType.Type5 => this.CalculateType5(value), + IccParametricCurveType.Type1 | InvertedFlag => this.CalculateInvertedGamma(value), + IccParametricCurveType.Cie122_1996 | InvertedFlag => this.CalculateInvertedCie122(value), + IccParametricCurveType.Iec61966_3 | InvertedFlag => this.CalculateInvertedIec61966(value), + IccParametricCurveType.SRgb | InvertedFlag => this.CalculateInvertedSRgb(value), + IccParametricCurveType.Type5 | InvertedFlag => this.CalculateInvertedType5(value), + _ => throw new InvalidIccProfileException("ParametricCurve"), + }; [MethodImpl(MethodImplOptions.AggressiveInlining)] - private float CalculateGamma(float value) - { - return MathF.Pow(value, this.curve.G); - } + private float CalculateGamma(float value) => MathF.Pow(value, this.curve.G); [MethodImpl(MethodImplOptions.AggressiveInlining)] private float CalculateCie122(float value) @@ -116,15 +97,11 @@ internal class ParametricCurveCalculator : ISingleCalculator [MethodImpl(MethodImplOptions.AggressiveInlining)] private float CalculateInvertedGamma(float value) - { - return MathF.Pow(value, 1 / this.curve.G); - } + => MathF.Pow(value, 1 / this.curve.G); [MethodImpl(MethodImplOptions.AggressiveInlining)] private float CalculateInvertedCie122(float value) - { - return (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; - } + => (MathF.Pow(value, 1 / this.curve.G) - this.curve.B) / this.curve.A; [MethodImpl(MethodImplOptions.AggressiveInlining)] private float CalculateInvertedIec61966(float value) diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs index fac56c3b5d..344efddad2 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterBase.Checks.cs @@ -31,9 +31,7 @@ internal abstract partial class IccConverterBase private static ConversionMethod CheckMethod1(IccProfile profile, IccRenderingIntent renderingIntent) { - ConversionMethod method = ConversionMethod.Invalid; - - method = CheckMethodD(profile, renderingIntent); + ConversionMethod method = CheckMethodD(profile, renderingIntent); if (method != ConversionMethod.Invalid) { return method; diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs index 6e9c8aa829..79f04229e1 100644 --- a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccConverterbase.cs @@ -29,8 +29,5 @@ internal abstract partial class IccConverterBase /// The value to convert /// The converted value [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Vector4 Calculate(Vector4 value) - { - return this.calculator.Calculate(value); - } + public Vector4 Calculate(Vector4 value) => this.calculator.Calculate(value); } diff --git a/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccProfileConverter.cs b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccProfileConverter.cs new file mode 100644 index 0000000000..ef7be7d1ab --- /dev/null +++ b/src/ImageSharp/ColorSpaces/Conversion/Implementation/Icc/IccProfileConverter.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using System.Numerics; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.ColorSpaces.Conversion.Icc; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.Icc; +internal static class IccProfileConverter +{ + public static void Convert(Image image, IccProfile inputIccProfile, IccProfile outputIccProfile) + where TPixel : unmanaged, IPixel + { + IccDataToPcsConverter converterDataToPcs = new(inputIccProfile); + IccPcsToDataConverter converterPcsToData = new(outputIccProfile); + Configuration configuration = image.GetConfiguration(); + + image.ProcessPixelRows(accessor => + { + using IMemoryOwner vectors = configuration.MemoryAllocator.Allocate(accessor.Width); + Span vectorsSpan = vectors.GetSpan(); + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + PixelOperations.Instance.ToVector4(configuration, row, vectorsSpan, PixelConversionModifiers.Scale); + + for (int x = 0; x < vectorsSpan.Length; x++) + { + Vector4 pcs = converterDataToPcs.Calculate(vectorsSpan[x]); + vectorsSpan[x] = converterPcsToData.Calculate(pcs); + } + + PixelOperations.Instance.FromVector4Destructive(configuration, vectorsSpan, row); + } + }); + + image.Metadata.IccProfile = outputIccProfile; + } +} diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs index e4403a47e2..00c545c88a 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs @@ -174,7 +174,6 @@ public sealed class IccProfile : IDeepCloneable return copy; } - IccWriter writer = new(); return IccWriter.Write(this); } @@ -191,7 +190,6 @@ public sealed class IccProfile : IDeepCloneable return; } - IccReader reader = new(); this.header = IccReader.ReadHeader(this.data); } diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs index 56d620ec32..428dabf37b 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccTagDataEntry.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -41,9 +41,7 @@ public abstract class IccTagDataEntry : IEquatable /// public override bool Equals(object obj) - { - return obj is IccTagDataEntry entry && this.Equals(entry); - } + => obj is IccTagDataEntry entry && this.Equals(entry); /// public virtual bool Equals(IccTagDataEntry other) diff --git a/tests/ImageSharp.Tests/Colorspaces/Icc/IccProfileConverterTests.cs b/tests/ImageSharp.Tests/Colorspaces/Icc/IccProfileConverterTests.cs new file mode 100644 index 0000000000..272219a5cf --- /dev/null +++ b/tests/ImageSharp.Tests/Colorspaces/Icc/IccProfileConverterTests.cs @@ -0,0 +1,58 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.ColorSpaces.Conversion.Implementation.Icc; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Tests.Colorspaces.Icc; +public class IccProfileConverterTests +{ + private static PngEncoder Encoder = new PngEncoder(); + + [Theory] + [WithFile(TestImages.Jpeg.ICC.AdobeRgb, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.ICC.AppleRGB, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.ICC.ColorMatch, PixelTypes.Rgb24)] + [WithFile(TestImages.Jpeg.ICC.WideRGB, PixelTypes.Rgb24)] + + // [WithFile(TestImages.Jpeg.ICC.SRgb, PixelTypes.Rgb24)] ConverterBase says this is invalid. + // [WithFile(TestImages.Jpeg.ICC.ProPhoto, PixelTypes.Rgb24)] ConverterBase says this is invalid. + public void CanRoundTripProfile(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + IccProfile profile = image.Metadata.IccProfile; + + TPixel expected = image[0, 0]; + + IccProfileConverter.Convert(image, profile, profile); + + image.DebugSave(provider, Encoder); + + TPixel actual = image[0, 0]; + + Assert.Equal(expected, actual); + } + + // TODO: This fails as the base calculator says sRGB is invalid. + [Theory] + [WithFile(TestImages.Jpeg.ICC.AdobeRgb, PixelTypes.Rgb24)] + public void CanConvertTosRGB(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + IccProfile profile = image.Metadata.IccProfile; + + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.ICC.SRgb); + IImageInfo i = Image.Identify(file); + IccProfile sRGBProfile = i.Metadata.IccProfile; + + IccProfileConverter.Convert(image, profile, sRGBProfile); + + // TODO: Compare. + image.DebugSave(provider, Encoder); + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 638f7dfb71..39e4feab32 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -169,6 +169,16 @@ public static class TestImages public static class Jpeg { + public static class ICC + { + public const string SRgb = "Jpg/icc-profiles/Momiji-sRGB-yes.jpg"; + public const string AdobeRgb = "Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg"; + public const string ColorMatch = "Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg"; + public const string ProPhoto = "Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg"; + public const string WideRGB = "Jpg/icc-profiles/Momiji-WideRGB-yes.jpg"; + public const string AppleRGB = "Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg"; + } + public static class Progressive { public const string Fb = "Jpg/progressive/fb.jpg"; diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg new file mode 100644 index 0000000000..077ee22beb --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Momiji-AdobeRGB-yes.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb8bdcc137efa3e28db69e48612230b3a9fec17267de9ce29757d9bacc181d28 +size 42001 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg new file mode 100644 index 0000000000..188faa2bdd --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Momiji-AppleRGB-yes.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7129f5485e997b75cff143021522cc8ab94e2c3c1912689bc765ce2b3b937441 +size 72150 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg new file mode 100644 index 0000000000..befc3d1170 --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Momiji-ColorMatch-yes.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe7fa60a53893836200c62f34492c7a0c931692dd073dffa4afc49fe3826e433 +size 44446 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg new file mode 100644 index 0000000000..645ad2869a --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Momiji-ProPhoto-yes.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb686b44e3253143a32db890823f63c79026c9ac9badc4ad9de21f6cb2fa2f2a +size 40703 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-WideRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-WideRGB-yes.jpg new file mode 100644 index 0000000000..57727aaa29 --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Momiji-WideRGB-yes.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:928b854a9629d1532d37095c4744da6bc2fc986f878a76aea373f69490f4b586 +size 40505 diff --git a/tests/Images/Input/Jpg/icc-profiles/Momiji-sRGB-yes.jpg b/tests/Images/Input/Jpg/icc-profiles/Momiji-sRGB-yes.jpg new file mode 100644 index 0000000000..4b7b612be0 --- /dev/null +++ b/tests/Images/Input/Jpg/icc-profiles/Momiji-sRGB-yes.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:faf67048c2b7bd3fb5fa9b69bd53943d63a216ef371c5dc9d062ac443c9d2d34 +size 47434