diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs index 48281c34a..dad093124 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykOperator.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 System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -240,6 +245,29 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // JPEG CMYK stores inverted components, while the ICC converter consumes normalized conventional CMYK. + PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + Span source = MemoryMarshal.Cast(packed); + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs deleted file mode 100644 index ebaa7c4b0..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykScalar.cs +++ /dev/null @@ -1,116 +0,0 @@ -// 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 -{ - internal sealed class CmykScalar : JpegColorConverterScalar - { - public CmykScalar(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) => - ConvertToRgbInPlace(values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i]; - float m = c1[i]; - float y = c2[i]; - float k = c3[i]; - - k *= scale; - c0[i] = c * k; - c1[i] = m * k; - c2[i] = y * k; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < c.Length; i++) - { - float ctmp = 255f - rLane[i]; - float mtmp = 255f - gLane[i]; - float ytmp = 255f - bLane[i]; - float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - - if (ktmp >= 255f) - { - ctmp = 0f; - mtmp = 0f; - ytmp = 0f; - } - else - { - ctmp = (ctmp - ktmp) / (255f - ktmp); - mtmp = (mtmp - ktmp) / (255f - ktmp); - ytmp = (ytmp - ktmp) / (255f - ktmp); - } - - c[i] = maxValue - (ctmp * maxValue); - m[i] = maxValue - (mtmp * maxValue); - y[i] = maxValue - (ytmp * maxValue); - k[i] = maxValue - ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs deleted file mode 100644 index 14addafc1..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector128.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector128 : JpegColorConverterVector128 - { - public CmykVector128(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c = ref Unsafe.Add(ref c0Base, i); - ref Vector128 m = ref Unsafe.Add(ref c1Base, i); - ref Vector128 y = ref Unsafe.Add(ref c2Base, i); - Vector128 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 scale = Vector128.Create(maxValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector128 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector128 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector128 ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); - - Vector128 kMask = ~Vector128.Equals(ktmp, scale); - Vector128 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs deleted file mode 100644 index 98bda53d2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector256.cs +++ /dev/null @@ -1,100 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector256 : JpegColorConverterVector256 - { - public CmykVector256(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 scale = Vector256.Create(maxValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector256 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector256 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector256 ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); - - Vector256 kMask = ~Vector256.Equals(ktmp, scale); - Vector256 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs deleted file mode 100644 index c72af2faf..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.CmykVector512.cs +++ /dev/null @@ -1,108 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class CmykVector512 : JpegColorConverterVector512 - { - public CmykVector512(int precision) - : base(JpegColorSpace.Cmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c = ref Unsafe.Add(ref c0Base, i); - ref Vector512 m = ref Unsafe.Add(ref c1Base, i); - ref Vector512 y = ref Unsafe.Add(ref c2Base, i); - Vector512 k = Unsafe.Add(ref c3Base, i); - - k *= scale; - c *= k; - m *= k; - y *= k; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => CmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => CmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 scale = Vector512.Create(maxValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector512 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector512 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector512 ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); - - Vector512 kMask = ~Vector512.Equals(ktmp, scale); - Vector512 divisor = scale - ktmp; - - ctmp = ((ctmp - ktmp) / divisor) & kMask; - mtmp = ((mtmp - ktmp) / divisor) & kMask; - ytmp = ((ytmp - ktmp) / divisor) & kMask; - - Unsafe.Add(ref destC, i) = scale - (ctmp * scale); - Unsafe.Add(ref destM, i) = scale - (mtmp * scale); - Unsafe.Add(ref destY, i) = scale - (ytmp * scale); - Unsafe.Add(ref destK, i) = scale - ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs index 2ac827da7..20b68e8b1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleOperator.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 System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -198,6 +203,28 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + float scale = 1F / maximumValue; + + // ICC luminance values are normalized, so the source plane is scaled in place before conversion. + TensorPrimitives_.Multiply(c0, scale, c0); + + Span source = MemoryMarshal.Cast(c0); + Span destination = MemoryMarshal.Cast(packed); + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs deleted file mode 100644 index 74869c93c..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleScalar.cs +++ /dev/null @@ -1,97 +0,0 @@ -// 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 -{ - internal sealed class GrayScaleScalar : JpegColorConverterScalar - { - public GrayScaleScalar(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbScalar(values, rLane, gLane, bLane); - - 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) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - ref float c0Base = ref MemoryMarshal.GetReference(c0); - ref float c1Base = ref MemoryMarshal.GetReference(c1); - ref float c2Base = ref MemoryMarshal.GetReference(c2); - - float scale = 1F / maxValue; - for (nuint i = 0; i < (nuint)values.Component0.Length; i++) - { - ref float c = ref Unsafe.Add(ref c0Base, i); - c *= scale; - } - - Span source = MemoryMarshal.Cast(values.Component0); - Span destination = MemoryMarshal.Cast(packed); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - internal static void ConvertFromRgbScalar(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - Span c0 = values.Component0; - - for (int i = 0; i < c0.Length; i++) - { - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - c0[i] = (float)((0.299f * rLane[i]) + (0.587f * gLane[i]) + (0.114f * bLane[i])); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs deleted file mode 100644 index 3c4a64f80..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector128.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector128 : JpegColorConverterVector128 - { - public GrayScaleVector128(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 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 override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector128 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 r = ref Unsafe.Add(ref srcRed, i); - ref Vector128 g = ref Unsafe.Add(ref srcGreen, i); - ref Vector128 b = ref Unsafe.Add(ref srcBlue, i); - - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs deleted file mode 100644 index 3d98d1eff..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector256.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector256 : JpegColorConverterVector256 - { - public GrayScaleVector256(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 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 override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector256 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref srcRed, i); - ref Vector256 g = ref Unsafe.Add(ref srcGreen, i); - ref Vector256 b = ref Unsafe.Add(ref srcBlue, i); - - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs deleted file mode 100644 index 96126ac9d..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.GrayScaleVector512.cs +++ /dev/null @@ -1,89 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class GrayScaleVector512 : JpegColorConverterVector512 - { - public GrayScaleVector512(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => GrayScaleScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 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; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destLuminance = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - - ref Vector512 srcRed = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcGreen = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcBlue = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - // Used for the color conversion - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 r = ref Unsafe.Add(ref srcRed, i); - ref Vector512 g = ref Unsafe.Add(ref srcGreen, i); - ref Vector512 b = ref Unsafe.Add(ref srcBlue, i); - - // luminosity = (0.299 * r) + (0.587 * g) + (0.114 * b) - Unsafe.Add(ref destLuminance, i) = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); - } - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => GrayScaleScalar.ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => GrayScaleScalar.ConvertFromRgbScalar(values, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs index 6d7281dc6..8559b818d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbOperator.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 System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -179,6 +184,26 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + // JPEG planes use the integer sample domain, while ICC RGB values are normalized and interleaved. + PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue); + + Span rgb = MemoryMarshal.Cast(packed); + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(rgb, rgb); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..rgb.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs deleted file mode 100644 index 92be9e896..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbScalar.cs +++ /dev/null @@ -1,84 +0,0 @@ -// 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 -{ - internal sealed class RgbScalar : JpegColorConverterScalar - { - public RgbScalar(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, rLane, gLane, bLane); - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - internal static void ConvertToRgbInPlace(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++) - { - 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 rLane, Span gLane, Span bLane) - { - // TODO: This doesn't seem correct. We should be scaling to the maximum value here. - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs deleted file mode 100644 index 6cbbc7c7c..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector128.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector128 : JpegColorConverterVector128 - { - public RgbVector128(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 r = ref Unsafe.Add(ref rBase, i); - ref Vector128 g = ref Unsafe.Add(ref gBase, i); - ref Vector128 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs deleted file mode 100644 index 10bc2be5f..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector256.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector256 : JpegColorConverterVector256 - { - public RgbVector256(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 r = ref Unsafe.Add(ref rBase, i); - ref Vector256 g = ref Unsafe.Add(ref gBase, i); - ref Vector256 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs deleted file mode 100644 index 6e01ad7cb..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.RgbVector512.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class RgbVector512 : JpegColorConverterVector512 - { - public RgbVector512(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 r = ref Unsafe.Add(ref rBase, i); - ref Vector512 g = ref Unsafe.Add(ref gBase, i); - ref Vector512 b = ref Unsafe.Add(ref bBase, i); - r *= scale; - g *= scale; - b *= scale; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => RgbScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - rLane.CopyTo(values.Component0); - gLane.CopyTo(values.Component1); - bLane.CopyTo(values.Component2); - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => RgbScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => RgbScalar.ConvertFromRgb(values, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs index 88b6c2411..f2c20959d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykOperator.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 System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc; namespace SixLabors.ImageSharp.Formats.Jpeg.Components; @@ -154,6 +159,28 @@ internal abstract partial class JpegColorConverterBase /// public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // TIFF CMYK is already non-inverted, so only normalization and interleaving precede ICC conversion. + PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + Span source = MemoryMarshal.Cast(packed); + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + ColorProfileConverter converter = new(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs deleted file mode 100644 index 27449a368..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykScalar.cs +++ /dev/null @@ -1,118 +0,0 @@ -// 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 -{ - /// - /// Color converter for tiff images, which use the jpeg compression and CMYK colorspace. - /// - internal sealed class TiffCmykScalar : JpegColorConverterScalar - { - public TiffCmykScalar(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / maxValue; - for (int i = 0; i < c0.Length; i++) - { - float c = c0[i] * scale; - float m = c1[i] * scale; - float y = c2[i] * scale; - float k = 1 - (c3[i] * scale); - - c0[i] = (1 - c) * k; - c1[i] = (1 - m) * k; - c2[i] = (1 - y) * k; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < c.Length; i++) - { - float ctmp = 255F - rLane[i]; - float mtmp = 255F - gLane[i]; - float ytmp = 255F - bLane[i]; - float ktmp = MathF.Min(MathF.Min(ctmp, mtmp), ytmp); - - if (ktmp >= 255F) - { - ctmp = 0F; - mtmp = 0F; - ytmp = 0F; - } - else - { - float divisor = 1 / (255F - ktmp); - ctmp = (ctmp - ktmp) * divisor; - mtmp = (mtmp - ktmp) * divisor; - ytmp = (ytmp - ktmp) * divisor; - } - - c[i] = ctmp * maxValue; - m[i] = mtmp * maxValue; - y[i] = ytmp * maxValue; - k[i] = ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - Span source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - ColorProfileConverter converter = new(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs deleted file mode 100644 index 6d52d5c72..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector128.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector128 : JpegColorConverterVector128 - { - public TiffCmykVector128(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c = ref Unsafe.Add(ref c0Base, i); - ref Vector128 m = ref Unsafe.Add(ref c1Base, i); - ref Vector128 y = ref Unsafe.Add(ref c2Base, i); - Vector128 k = Unsafe.Add(ref c3Base, i); - - k = Vector128.One - (k * scale); - c = (Vector128.One - (c * scale)) * k; - m = (Vector128.One - (m * scale)) * k; - y = (Vector128.One - (y * scale)) * k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 scale = Vector128.Create(maxValue); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector128 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector128 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector128 ktmp = Vector128.Min(ctmp, Vector128.Min(mtmp, ytmp)); - - Vector128 kMask = ~Vector128.Equals(ktmp, scale); - Vector128 divisor = Vector128.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs deleted file mode 100644 index 61b312a06..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector256.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector256 : JpegColorConverterVector256 - { - public TiffCmykVector256(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c = ref Unsafe.Add(ref c0Base, i); - ref Vector256 m = ref Unsafe.Add(ref c1Base, i); - ref Vector256 y = ref Unsafe.Add(ref c2Base, i); - Vector256 k = Unsafe.Add(ref c3Base, i); - - k = Vector256.One - (k * scale); - c = (Vector256.One - (c * scale)) * k; - m = (Vector256.One - (m * scale)) * k; - y = (Vector256.One - (y * scale)) * k; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => CmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertFromRgb(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 scale = Vector256.Create(maxValue); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector256 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector256 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector256 ktmp = Vector256.Min(ctmp, Vector256.Min(mtmp, ytmp)); - - Vector256 kMask = ~Vector256.Equals(ktmp, scale); - Vector256 divisor = Vector256.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs deleted file mode 100644 index 51d5cc76d..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffCmykVector512.cs +++ /dev/null @@ -1,108 +0,0 @@ -// 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; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffCmykVector512 : JpegColorConverterVector512 - { - public TiffCmykVector512(int precision) - : base(JpegColorSpace.TiffCmyk, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffCmykScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c = ref Unsafe.Add(ref c0Base, i); - ref Vector512 m = ref Unsafe.Add(ref c1Base, i); - ref Vector512 y = ref Unsafe.Add(ref c2Base, i); - Vector512 k = Unsafe.Add(ref c3Base, i); - - k = Vector512.One - (k * scale); - c = (Vector512.One - (c * scale)) * k; - m = (Vector512.One - (m * scale)) * k; - y = (Vector512.One - (y * scale)) * k; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => TiffCmykScalar.ConvertToRgbInPlace(values, this.MaximumValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => TiffCmykScalar.ConvertFromRgb(values, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destC = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destM = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 scale = Vector512.Create(maxValue); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 ctmp = scale - Unsafe.Add(ref srcR, i); - Vector512 mtmp = scale - Unsafe.Add(ref srcG, i); - Vector512 ytmp = scale - Unsafe.Add(ref srcB, i); - Vector512 ktmp = Vector512.Min(ctmp, Vector512.Min(mtmp, ytmp)); - - Vector512 kMask = ~Vector512.Equals(ktmp, scale); - Vector512 divisor = Vector512.One / (scale - ktmp); - - ctmp = ((ctmp - ktmp) * divisor) & kMask; - mtmp = ((mtmp - ktmp) * divisor) & kMask; - ytmp = ((ytmp - ktmp) * divisor) & kMask; - - Unsafe.Add(ref destC, i) = ctmp * scale; - Unsafe.Add(ref destM, i) = mtmp * scale; - Unsafe.Add(ref destY, i) = ytmp * scale; - Unsafe.Add(ref destK, i) = ktmp; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs index c66d266a1..63e14fb88 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKOperator.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 System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -34,9 +39,9 @@ internal abstract partial class JpegColorConverterBase // TIFF YccK is non-inverted: decode normalized YCbCr without integer rounding, then let the // remaining light after K modulate all three channels. - c0 = (y + (YCbCrScalar.RCrMult * cr)) * k; - c1 = (y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr)) * k; - c2 = (y + (YCbCrScalar.BCbMult * cb)) * k; + c0 = (y + (YCbCrOperator.RCrMult * cr)) * k; + c1 = (y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr)) * k; + c2 = (y + (YCbCrOperator.BCbMult * cb)) * k; } /// @@ -49,9 +54,9 @@ internal abstract partial class JpegColorConverterBase Vector128 k = Vector128.One - (c3 * scale); // Four lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. - c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y) * k; - c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)) * k; - c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y) * k; + c0 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y) * k; + c1 = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)) * k; + c2 = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y) * k; } /// @@ -64,9 +69,9 @@ internal abstract partial class JpegColorConverterBase Vector256 k = Vector256.One - (c3 * scale); // Eight lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. - c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y) * k; - c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)) * k; - c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y) * k; + c0 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y) * k; + c1 = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)) * k; + c2 = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y) * k; } /// @@ -79,9 +84,9 @@ internal abstract partial class JpegColorConverterBase Vector512 k = Vector512.One - (c3 * scale); // Sixteen lanes apply the non-rounded YCbCr matrix before their lane-aligned K modulation. - c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y) * k; - c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)) * k; - c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y) * k; + c0 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y) * k; + c1 = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)) * k; + c2 = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y) * k; } /// @@ -182,6 +187,31 @@ internal abstract partial class JpegColorConverterBase /// public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // TIFF YccK is non-inverted, so normalize directly before converting its JPEG-specific model to CMYK. + PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + ColorProfileConverter converter = new(); + Span source = MemoryMarshal.Cast(packed); + converter.Convert(MemoryMarshal.Cast(source), source); + + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + converter = new ColorProfileConverter(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs deleted file mode 100644 index 01bfc0875..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKScalar.cs +++ /dev/null @@ -1,153 +0,0 @@ -// 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 -{ - /// - /// Color converter for tiff images, which use the jpeg compression and CMYK colorspace. - /// - internal sealed class TiffYccKScalar : JpegColorConverterScalar - { - // Derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public TiffYccKScalar(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(in values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1F / maxValue; - halfValue *= scale; - - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i] * scale; - float cb = (c1[i] * scale) - halfValue; - float cr = (c2[i] * scale) - halfValue; - float scaledK = 1 - (c3[i] * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = (y + (RCrMult * cr)) * scaledK; - c1[i] = (y - (GCbMult * cb) - (GCrMult * cr)) * scaledK; - c2[i] = (y + (BCbMult * cb)) * scaledK; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) - { - Span y = values.Component0; - Span cb = values.Component1; - Span cr = values.Component2; - Span k = values.Component3; - - for (int i = 0; i < cr.Length; i++) - { - // Scale down to [0-1] - const float divisor = 1F / 255F; - float r = rLane[i] * divisor; - float g = gLane[i] * divisor; - float b = bLane[i] * divisor; - - float ytmp; - float cbtmp; - float crtmp; - float ktmp = 1F - MathF.Max(r, MathF.Max(g, b)); - - if (ktmp >= 1F) - { - ytmp = 0F; - cbtmp = 0.5F; - crtmp = 0.5F; - ktmp = maxValue; - } - else - { - float kmask = 1F / (1F - ktmp); - r *= kmask; - g *= kmask; - b *= kmask; - - // Scale to [0-maxValue] - ytmp = ((0.299f * r) + (0.587f * g) + (0.114f * b)) * maxValue; - cbtmp = halfValue - (((0.168736f * r) - (0.331264f * g) + (0.5f * b)) * maxValue); - crtmp = halfValue + (((0.5f * r) - (0.418688f * g) - (0.081312f * b)) * maxValue); - ktmp *= maxValue; - } - - y[i] = ytmp; - cb[i] = cbtmp; - cr[i] = crtmp; - k[i] = ktmp; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(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(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs deleted file mode 100644 index ae3391d42..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector128.cs +++ /dev/null @@ -1,131 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector128 : JpegColorConverterVector128 - { - public TiffYccKVector128(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 scale = Vector128.Create(1F / this.MaximumValue); - Vector128 chromaOffset = Vector128.Create(this.HalfValue) * scale; - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector128 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector128 y = c0 * scale; - Vector128 cb = (c1 * scale) - chromaOffset; - Vector128 cr = (c2 * scale) - chromaOffset; - Vector128 scaledK = Vector128.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; - Vector128 g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; - Vector128 b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector128 maxSourceValue = Vector128.Create(1 / 255F); - Vector128 maxSampleValue = Vector128.Create(this.MaximumValue); - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = Unsafe.Add(ref srcR, i) * maxSourceValue; - Vector128 g = Unsafe.Add(ref srcG, i) * maxSourceValue; - Vector128 b = Unsafe.Add(ref srcB, i) * maxSourceValue; - Vector128 ktmp = Vector128.One - Vector128.Max(r, Vector128.Min(g, b)); - - Vector128 kMask = ~Vector128.Equals(ktmp, Vector128.One); - Vector128 divisor = Vector128.One / (Vector128.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector128 cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector128 cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y * maxSampleValue; - Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs deleted file mode 100644 index ec6b228ca..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector256.cs +++ /dev/null @@ -1,131 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector256 : JpegColorConverterVector256 - { - public TiffYccKVector256(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 scale = Vector256.Create(1F / this.MaximumValue); - Vector256 chromaOffset = Vector256.Create(this.HalfValue) * scale; - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector256 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector256 y = c0 * scale; - Vector256 cb = (c1 * scale) - chromaOffset; - Vector256 cr = (c2 * scale) - chromaOffset; - Vector256 scaledK = Vector256.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; - Vector256 g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; - Vector256 b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector256 maxSourceValue = Vector256.Create(255F); - Vector256 maxSampleValue = Vector256.Create(this.MaximumValue); - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = Unsafe.Add(ref srcR, i) / maxSourceValue; - Vector256 g = Unsafe.Add(ref srcG, i) / maxSourceValue; - Vector256 b = Unsafe.Add(ref srcB, i) / maxSourceValue; - Vector256 ktmp = Vector256.One - Vector256.Max(r, Vector256.Min(g, b)); - - Vector256 kMask = ~Vector256.Equals(ktmp, Vector256.One); - Vector256 divisor = Vector256.One / (Vector256.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector256 cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector256 cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y * maxSampleValue; - Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs deleted file mode 100644 index 6a0fb93e2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.TiffYccKVector512.cs +++ /dev/null @@ -1,142 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class TiffYccKVector512 : JpegColorConverterVector512 - { - public TiffYccKVector512(int precision) - : base(JpegColorSpace.TiffYccK, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => TiffYccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector512 scale = Vector512.Create(1F / this.MaximumValue); - Vector512 chromaOffset = Vector512.Create(this.HalfValue) * scale; - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - ref Vector512 c3 = ref Unsafe.Add(ref c3Base, i); - - Vector512 y = c0 * scale; - Vector512 cb = (c1 * scale) - chromaOffset; - Vector512 cr = (c2 * scale) - chromaOffset; - Vector512 scaledK = Vector512.One - (c3 * scale); - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y) * scaledK; - Vector512 g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)) * scaledK; - Vector512 b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y) * scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgbVectorized(in values, this.MaximumValue, this.HalfValue, rLane, gLane, bLane); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => TiffYccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => TiffYccKScalar.ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - internal static void ConvertFromRgbVectorized(in ComponentValues values, float maxValue, float halfValue, Span rLane, Span gLane, Span bLane) - { - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 destK = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - Vector512 maxSourceValue = Vector512.Create(255F); - Vector512 maxSampleValue = Vector512.Create(maxValue); - Vector512 chromaOffset = Vector512.Create(halfValue); - - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = Unsafe.Add(ref srcR, i) / maxSourceValue; - Vector512 g = Unsafe.Add(ref srcG, i) / maxSourceValue; - Vector512 b = Unsafe.Add(ref srcB, i) / maxSourceValue; - Vector512 ktmp = Vector512.One - Vector512.Max(r, Vector512.Min(g, b)); - - Vector512 kMask = ~Vector512.Equals(ktmp, Vector512.One); - Vector512 divisor = Vector512.One / (Vector512.One - ktmp); - - r = (r * divisor) & kMask; - g = (g * divisor) & kMask; - b = (b * divisor) & kMask; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector512 cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector512 cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y * maxSampleValue; - Unsafe.Add(ref destCb, i) = chromaOffset + (cb * maxSampleValue); - Unsafe.Add(ref destCr, i) = chromaOffset + (cr * maxSampleValue); - Unsafe.Add(ref destK, i) = ktmp * maxSampleValue; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs index ca8671e9f..931b96dff 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrOperator.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 System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -15,6 +20,26 @@ internal abstract partial class JpegColorConverterBase /// internal readonly struct YCbCrOperator : IJpegColorConverterOperator { + /// + /// The BT.601 red contribution from centered Cr. + /// + public const float RCrMult = 1.402F; + + /// + /// The BT.601 green contribution from centered Cb. + /// + public const float GCbMult = (float)(0.114 * 1.772 / 0.587); + + /// + /// The BT.601 green contribution from centered Cr. + /// + public const float GCrMult = (float)(0.299 * 1.402 / 0.587); + + /// + /// The BT.601 blue contribution from centered Cb. + /// + public const float BCbMult = 1.772F; + /// public static JpegColorSpace ColorSpace => JpegColorSpace.YCbCr; @@ -40,9 +65,9 @@ internal abstract partial class JpegColorConverterBase // the BT.601 matrix, then integer-domain RGB is rounded away from zero and normalized // to [nominally] 0..1. Values intentionally remain unclamped because quantizing RGB into the // destination pixel format owns saturation; retaining overshoot avoids discarding color information. - c0 = MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c1 = MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c2 = MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero) * scale; + c0 = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c1 = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c2 = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; } /// @@ -63,12 +88,12 @@ internal abstract partial class JpegColorConverterBase // Lanes are four independent Y/Cb/Cr samples. MultiplyAddEstimate maps to FMA where available: // R uses Cr, B uses Cb, and G subtracts both chroma contributions. Rounding occurs in the sample // domain before the common normalization scale so all precisions use integer JPEG sample semantics. - Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); + Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(RCrMult), y); Vector128 g = Vector128_.MultiplyAddEstimate( cr, - Vector128.Create(-YCbCrScalar.GCrMult), - Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); - Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); + Vector128.Create(-GCrMult), + Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-GCbMult), y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(BCbMult), y); c0 = Vector128_.RoundToNearestInteger(r) * scale; c1 = Vector128_.RoundToNearestInteger(g) * scale; @@ -93,12 +118,12 @@ internal abstract partial class JpegColorConverterBase // These eight lanes have the same layout and BT.601 arithmetic as the Vector128 overload. // Keeping an explicit overload allows the JIT to emit native YMM operations without a width // switch or decomposing the vector into smaller values. - Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); + Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(RCrMult), y); Vector256 g = Vector256_.MultiplyAddEstimate( cr, - Vector256.Create(-YCbCrScalar.GCrMult), - Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); - Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); + Vector256.Create(-GCrMult), + Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-GCbMult), y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(BCbMult), y); c0 = Vector256_.RoundToNearestInteger(r) * scale; c1 = Vector256_.RoundToNearestInteger(g) * scale; @@ -123,12 +148,12 @@ internal abstract partial class JpegColorConverterBase // Sixteen independent samples occupy the ZMM lanes. The explicit constants are broadcasts; // assembly inspection verifies the JIT hoists them from the loop and retains fused operations. // The formula and rounding order remain identical to the narrower overloads. - Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); + Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(RCrMult), y); Vector512 g = Vector512_.MultiplyAddEstimate( cr, - Vector512.Create(-YCbCrScalar.GCrMult), - Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); - Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); + Vector512.Create(-GCrMult), + Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-GCbMult), y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(BCbMult), y); c0 = Vector512_.RoundToNearestInteger(r) * scale; c1 = Vector512_.RoundToNearestInteger(g) * scale; @@ -258,6 +283,30 @@ internal abstract partial class JpegColorConverterBase IccProfile profile, in ComponentValues values, float maximumValue) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + // ICC profiles rarely expose YCbCr transforms, so BT.601 first produces RGB in the profile's source space. + PackedNormalizeInterleave3(c0, c1, c2, packed, 1F / maximumValue); + + ColorProfileConverter converter = new(); + Span source = MemoryMarshal.Cast(packed); + Span destination = MemoryMarshal.Cast(packed); + converter.Convert(source, destination); + + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + converter = new ColorProfileConverter(options); + converter.Convert(destination, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs deleted file mode 100644 index 2b1c1dca3..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrScalar.cs +++ /dev/null @@ -1,121 +0,0 @@ -// 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 -{ - internal sealed class YCbCrScalar : JpegColorConverterScalar - { - // derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public YCbCrScalar(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - float scale = 1 / maxValue; - - for (int i = 0; i < c0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; - c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; - } - } - - public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maxValue) - { - using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 3); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span 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 source = MemoryMarshal.Cast(packed); - Span destination = MemoryMarshal.Cast(packed); - - converter.Convert(source, destination); - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(destination, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, Span rLane, Span gLane, Span bLane) - { - Span y = values.Component0; - Span cb = values.Component1; - Span cr = values.Component2; - - for (int i = 0; i < y.Length; i++) - { - float r = rLane[i]; - float g = gLane[i]; - float b = bLane[i]; - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - y[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - cb[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - cr[i] = halfValue + (0.5f * r) - (0.418688f * g) - (0.081312f * b); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs deleted file mode 100644 index 37847a6e6..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector128.cs +++ /dev/null @@ -1,121 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector128 : JpegColorConverterVector128 - { - public YCbCrVector128(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector128 chromaOffset = Vector128.Create(-this.HalfValue); - Vector128 scale = Vector128.Create(1 / this.MaximumValue); - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector128 y = c0; - Vector128 cb = c1 + chromaOffset; - Vector128 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y); - Vector128 g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)); - Vector128 b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y); - - r = Vector128_.RoundToNearestInteger(r) * scale; - g = Vector128_.RoundToNearestInteger(g) * scale; - b = Vector128_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector128 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector128 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector128 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = Unsafe.Add(ref srcR, i); - Vector128 g = Unsafe.Add(ref srcG, i); - Vector128 b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector128 cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector128 cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs deleted file mode 100644 index fbccf88e2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector256.cs +++ /dev/null @@ -1,121 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector256 : JpegColorConverterVector256 - { - public YCbCrVector256(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector256 chromaOffset = Vector256.Create(-this.HalfValue); - Vector256 scale = Vector256.Create(1 / this.MaximumValue); - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector256 y = c0; - Vector256 cb = c1 + chromaOffset; - Vector256 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y); - Vector256 g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)); - Vector256 b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y); - - r = Vector256_.RoundToNearestInteger(r) * scale; - g = Vector256_.RoundToNearestInteger(g) * scale; - b = Vector256_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector256 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector256 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = Unsafe.Add(ref srcR, i); - Vector256 g = Unsafe.Add(ref srcG, i); - Vector256 b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector256 cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector256 cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs deleted file mode 100644 index 387917175..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YCbCrVector512.cs +++ /dev/null @@ -1,128 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YCbCrVector512 : JpegColorConverterVector512 - { - public YCbCrVector512(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YCbCrScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - Vector512 chromaOffset = Vector512.Create(-this.HalfValue); - Vector512 scale = Vector512.Create(1 / this.MaximumValue); - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - - Vector512 y = c0; - Vector512 cb = c1 + chromaOffset; - Vector512 cr = c2 + chromaOffset; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y); - Vector512 g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)); - Vector512 b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y); - - r = Vector512_.RoundToNearestInteger(r) * scale; - g = Vector512_.RoundToNearestInteger(g) * scale; - b = Vector512_.RoundToNearestInteger(b) * scale; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector512 srcR = - ref Unsafe.As>(ref MemoryMarshal.GetReference(rLane)); - ref Vector512 srcG = - ref Unsafe.As>(ref MemoryMarshal.GetReference(gLane)); - ref Vector512 srcB = - ref Unsafe.As>(ref MemoryMarshal.GetReference(bLane)); - - Vector512 chromaOffset = Vector512.Create(this.HalfValue); - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = Unsafe.Add(ref srcR, i); - Vector512 g = Unsafe.Add(ref srcG, i); - Vector512 b = Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector512 cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector512 cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => YCbCrScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => YCbCrScalar.ConvertFromRgb(values, this.HalfValue, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs index ef7185dd3..fb85194e6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKOperator.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 System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -32,9 +37,9 @@ internal abstract partial class JpegColorConverterBase // YccK first reconstructs inverted RGB in the integer sample domain. Rounding must occur before // subtracting from max and applying K because changing that order changes encoded JPEG semantics. - c0 = (maximumValue - MathF.Round(y + (YCbCrScalar.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1 = (maximumValue - MathF.Round(y - (YCbCrScalar.GCbMult * cb) - (YCbCrScalar.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2 = (maximumValue - MathF.Round(y + (YCbCrScalar.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; + c0 = (maximumValue - MathF.Round(y + (YCbCrOperator.RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c1 = (maximumValue - MathF.Round(y - (YCbCrOperator.GCbMult * cb) - (YCbCrOperator.GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; + c2 = (maximumValue - MathF.Round(y + (YCbCrOperator.BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; } /// @@ -47,9 +52,9 @@ internal abstract partial class JpegColorConverterBase Vector128 scaledK = c3 * scale * scale; // Four lanes reconstruct YCbCr concurrently; each rounded result is inverted and modulated by its K lane. - Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrScalar.RCrMult), y); - Vector128 g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrScalar.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrScalar.GCbMult), y)); - Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrScalar.BCbMult), y); + Vector128 r = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(YCbCrOperator.RCrMult), y); + Vector128 g = Vector128_.MultiplyAddEstimate(cr, Vector128.Create(-YCbCrOperator.GCrMult), Vector128_.MultiplyAddEstimate(cb, Vector128.Create(-YCbCrOperator.GCbMult), y)); + Vector128 b = Vector128_.MultiplyAddEstimate(cb, Vector128.Create(YCbCrOperator.BCbMult), y); c0 = (maximumValue - Vector128_.RoundToNearestInteger(r)) * scaledK; c1 = (maximumValue - Vector128_.RoundToNearestInteger(g)) * scaledK; c2 = (maximumValue - Vector128_.RoundToNearestInteger(b)) * scaledK; @@ -65,9 +70,9 @@ internal abstract partial class JpegColorConverterBase Vector256 scaledK = c3 * scale * scale; // Eight lanes retain planar alignment from Y/Cb/Cr/K through normalized RGB. - Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrScalar.RCrMult), y); - Vector256 g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrScalar.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrScalar.GCbMult), y)); - Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrScalar.BCbMult), y); + Vector256 r = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(YCbCrOperator.RCrMult), y); + Vector256 g = Vector256_.MultiplyAddEstimate(cr, Vector256.Create(-YCbCrOperator.GCrMult), Vector256_.MultiplyAddEstimate(cb, Vector256.Create(-YCbCrOperator.GCbMult), y)); + Vector256 b = Vector256_.MultiplyAddEstimate(cb, Vector256.Create(YCbCrOperator.BCbMult), y); c0 = (maximumValue - Vector256_.RoundToNearestInteger(r)) * scaledK; c1 = (maximumValue - Vector256_.RoundToNearestInteger(g)) * scaledK; c2 = (maximumValue - Vector256_.RoundToNearestInteger(b)) * scaledK; @@ -83,9 +88,9 @@ internal abstract partial class JpegColorConverterBase Vector512 scaledK = c3 * scale * scale; // Sixteen lanes use the same matrix, rounding, inversion, and K modulation order as scalar code. - Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrScalar.RCrMult), y); - Vector512 g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrScalar.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrScalar.GCbMult), y)); - Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrScalar.BCbMult), y); + Vector512 r = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(YCbCrOperator.RCrMult), y); + Vector512 g = Vector512_.MultiplyAddEstimate(cr, Vector512.Create(-YCbCrOperator.GCrMult), Vector512_.MultiplyAddEstimate(cb, Vector512.Create(-YCbCrOperator.GCbMult), y)); + Vector512 b = Vector512_.MultiplyAddEstimate(cb, Vector512.Create(YCbCrOperator.BCbMult), y); c0 = (maximumValue - Vector512_.RoundToNearestInteger(r)) * scaledK; c1 = (maximumValue - Vector512_.RoundToNearestInteger(g)) * scaledK; c2 = (maximumValue - Vector512_.RoundToNearestInteger(b)) * scaledK; @@ -130,6 +135,31 @@ internal abstract partial class JpegColorConverterBase /// public static void ConvertToRgbInPlaceWithIcc(Configuration configuration, IccProfile profile, in ComponentValues values, float maximumValue) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, maximumValue); + { + using IMemoryOwner memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); + Span packed = memoryOwner.Memory.Span; + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + Span c3 = values.Component3; + + // Adobe-style JPEG YccK is inverted; normalize it before applying the format-defined YccK-to-CMYK transform. + PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maximumValue); + + ColorProfileConverter converter = new(); + Span source = MemoryMarshal.Cast(packed); + converter.Convert(MemoryMarshal.Cast(source), source); + + Span destination = MemoryMarshal.Cast(packed)[..source.Length]; + ColorConversionOptions options = new() + { + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }; + + converter = new ColorProfileConverter(options); + converter.Convert(source, destination); + UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs deleted file mode 100644 index 3368e52bf..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKScalar.cs +++ /dev/null @@ -1,125 +0,0 @@ -// 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 -{ - internal sealed class YccKScalar : JpegColorConverterScalar - { - // Derived from ITU-T Rec. T.871 - internal const float RCrMult = 1.402f; - internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); - internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); - internal const float BCbMult = 1.772f; - - public YccKScalar(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - => ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => ConvertFromRgb(values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - - public static void ConvertToRgbInPlace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - float scale = 1 / (maxValue * maxValue); - - for (int i = 0; i < values.Component0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - float scaledK = c3[i] * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - c0[i] = (maxValue - MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c1[i] = (maxValue - MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero)) * scaledK; - c2[i] = (maxValue - MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero)) * scaledK; - } - } - - public static void ConvertFromRgb(in ComponentValues values, float halfValue, float maxValue, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykScalar.ConvertFromRgb(in values, maxValue, rLane, gLane, bLane); - - // cmyk -> ycck - Span c = values.Component0; - Span m = values.Component1; - Span y = values.Component2; - - for (int i = 0; i < y.Length; i++) - { - float r = maxValue - c[i]; - float g = maxValue - m[i]; - float b = maxValue - y[i]; - - // k value is passed untouched from rgb -> cmyk conversion - c[i] = (0.299f * r) + (0.587f * g) + (0.114f * b); - m[i] = halfValue - (0.168736f * r) - (0.331264f * g) + (0.5f * b); - 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 memoryOwner = configuration.MemoryAllocator.Allocate(values.Component0.Length * 4); - Span packed = memoryOwner.Memory.Span; - - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - Span c3 = values.Component3; - - PackedInvertNormalizeInterleave4(c0, c1, c2, c3, packed, maxValue); - - ColorProfileConverter converter = new(); - Span source = MemoryMarshal.Cast(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(MemoryMarshal.Cast(source), source); - - Span destination = MemoryMarshal.Cast(packed)[..source.Length]; - - ColorConversionOptions options = new() - { - SourceIccProfile = profile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - }; - converter = new ColorProfileConverter(options); - converter.Convert(source, destination); - - UnpackDeinterleave3(MemoryMarshal.Cast(packed)[..source.Length], c0, c1, c2); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs deleted file mode 100644 index 279c100b2..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector128.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector128 : JpegColorConverterVector128 - { - public YccKVector128(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector128 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector128 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector128 chromaOffset = Vector128.Create(-this.HalfValue); - Vector128 scale = Vector128.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector128 max = Vector128.Create(this.MaximumValue); - Vector128 rCrMult = Vector128.Create(YCbCrScalar.RCrMult); - Vector128 gCbMult = Vector128.Create(-YCbCrScalar.GCbMult); - Vector128 gCrMult = Vector128.Create(-YCbCrScalar.GCrMult); - Vector128 bCbMult = Vector128.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector128 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector128 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector128 c2 = ref Unsafe.Add(ref c2Base, i); - Vector128 y = c0; - Vector128 cb = c1 + chromaOffset; - Vector128 cr = c2 + chromaOffset; - Vector128 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector128 r = Vector128_.MultiplyAddEstimate(cr, rCrMult, y); - Vector128 g = Vector128_.MultiplyAddEstimate(cr, gCrMult, Vector128_.MultiplyAddEstimate(cb, gCbMult, y)); - Vector128 b = Vector128_.MultiplyAddEstimate(cb, bCbMult, y); - - r = max - Vector128_.RoundToNearestInteger(r); - g = max - Vector128_.RoundToNearestInteger(g); - b = max - Vector128_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector128.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector128 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector128 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector128 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector128 srcR = ref destY; - ref Vector128 srcG = ref destCb; - ref Vector128 srcB = ref destCr; - - // Used for the color conversion - Vector128 maxSampleValue = Vector128.Create(this.MaximumValue); - - Vector128 chromaOffset = Vector128.Create(this.HalfValue); - - Vector128 f0299 = Vector128.Create(0.299f); - Vector128 f0587 = Vector128.Create(0.587f); - Vector128 f0114 = Vector128.Create(0.114f); - Vector128 fn0168736 = Vector128.Create(-0.168736f); - Vector128 fn0331264 = Vector128.Create(-0.331264f); - Vector128 fn0418688 = Vector128.Create(-0.418688f); - Vector128 fn0081312F = Vector128.Create(-0.081312F); - Vector128 f05 = Vector128.Create(0.5f); - - nuint n = values.Component0.Vector128Count(); - for (nuint i = 0; i < n; i++) - { - Vector128 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector128 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector128 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector128 y = Vector128_.MultiplyAddEstimate(f0299, r, Vector128_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector128 cb = chromaOffset + Vector128_.MultiplyAddEstimate(fn0168736, r, Vector128_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector128 cr = chromaOffset + Vector128_.MultiplyAddEstimate(f05, r, Vector128_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs deleted file mode 100644 index 1bebdfcf4..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector256.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector256 : JpegColorConverterVector256 - { - public YccKVector256(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - public override void ConvertToRgbInPlace(in ComponentValues values) - { - ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector256 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector256 chromaOffset = Vector256.Create(-this.HalfValue); - Vector256 scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector256 max = Vector256.Create(this.MaximumValue); - Vector256 rCrMult = Vector256.Create(YCbCrScalar.RCrMult); - Vector256 gCbMult = Vector256.Create(-YCbCrScalar.GCbMult); - Vector256 gCrMult = Vector256.Create(-YCbCrScalar.GCrMult); - Vector256 bCbMult = Vector256.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector256 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector256 c2 = ref Unsafe.Add(ref c2Base, i); - Vector256 y = c0; - Vector256 cb = c1 + chromaOffset; - Vector256 cr = c2 + chromaOffset; - Vector256 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector256 r = Vector256_.MultiplyAddEstimate(cr, rCrMult, y); - Vector256 g = Vector256_.MultiplyAddEstimate(cr, gCrMult, Vector256_.MultiplyAddEstimate(cb, gCbMult, y)); - Vector256 b = Vector256_.MultiplyAddEstimate(cb, bCbMult, y); - - r = max - Vector256_.RoundToNearestInteger(r); - g = max - Vector256_.RoundToNearestInteger(g); - b = max - Vector256_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - public override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector256.ConvertFromRgb(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector256 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector256 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector256 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector256 srcR = ref destY; - ref Vector256 srcG = ref destCb; - ref Vector256 srcB = ref destCr; - - // Used for the color conversion - Vector256 maxSampleValue = Vector256.Create(this.MaximumValue); - - Vector256 chromaOffset = Vector256.Create(this.HalfValue); - - Vector256 f0299 = Vector256.Create(0.299f); - Vector256 f0587 = Vector256.Create(0.587f); - Vector256 f0114 = Vector256.Create(0.114f); - Vector256 fn0168736 = Vector256.Create(-0.168736f); - Vector256 fn0331264 = Vector256.Create(-0.331264f); - Vector256 fn0418688 = Vector256.Create(-0.418688f); - Vector256 fn0081312F = Vector256.Create(-0.081312F); - Vector256 f05 = Vector256.Create(0.5f); - - nuint n = values.Component0.Vector256Count(); - for (nuint i = 0; i < n; i++) - { - Vector256 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector256 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector256 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector256 y = Vector256_.MultiplyAddEstimate(f0299, r, Vector256_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector256 cb = chromaOffset + Vector256_.MultiplyAddEstimate(fn0168736, r, Vector256_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector256 cr = chromaOffset + Vector256_.MultiplyAddEstimate(f05, r, Vector256_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs deleted file mode 100644 index 9c0e1ab74..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverter.YccKVector512.cs +++ /dev/null @@ -1,143 +0,0 @@ -// 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.Common.Helpers; -using SixLabors.ImageSharp.Metadata.Profiles.Icc; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - internal sealed class YccKVector512 : JpegColorConverterVector512 - { - public YccKVector512(int precision) - : base(JpegColorSpace.Ycck, precision) - { - } - - /// - protected override void ConvertToRgbInPlaceVectorized(in ComponentValues values) - { - ref Vector512 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - ref Vector512 kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - - // Used for the color conversion - Vector512 chromaOffset = Vector512.Create(-this.HalfValue); - Vector512 scale = Vector512.Create(1 / (this.MaximumValue * this.MaximumValue)); - Vector512 max = Vector512.Create(this.MaximumValue); - Vector512 rCrMult = Vector512.Create(YCbCrScalar.RCrMult); - Vector512 gCbMult = Vector512.Create(-YCbCrScalar.GCbMult); - Vector512 gCrMult = Vector512.Create(-YCbCrScalar.GCrMult); - Vector512 bCbMult = Vector512.Create(YCbCrScalar.BCbMult); - - // Walking 8 elements at one step: - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - // y = yVals[i]; - // cb = cbVals[i] - 128F; - // cr = crVals[i] - 128F; - // k = kVals[i] / 256F; - ref Vector512 c0 = ref Unsafe.Add(ref c0Base, i); - ref Vector512 c1 = ref Unsafe.Add(ref c1Base, i); - ref Vector512 c2 = ref Unsafe.Add(ref c2Base, i); - Vector512 y = c0; - Vector512 cb = c1 + chromaOffset; - Vector512 cr = c2 + chromaOffset; - Vector512 scaledK = Unsafe.Add(ref kBase, i) * scale; - - // r = y + (1.402F * cr); - // g = y - (0.344136F * cb) - (0.714136F * cr); - // b = y + (1.772F * cb); - Vector512 r = Vector512_.MultiplyAddEstimate(cr, rCrMult, y); - Vector512 g = Vector512_.MultiplyAddEstimate(cr, gCrMult, Vector512_.MultiplyAddEstimate(cb, gCbMult, y)); - Vector512 b = Vector512_.MultiplyAddEstimate(cb, bCbMult, y); - - r = max - Vector512_.RoundToNearestInteger(r); - g = max - Vector512_.RoundToNearestInteger(g); - b = max - Vector512_.RoundToNearestInteger(b); - - r *= scaledK; - g *= scaledK; - b *= scaledK; - - c0 = r; - c1 = g; - c2 = b; - } - } - - /// - public override void ConvertToRgbInPlaceWithIcc(Configuration configuration, in ComponentValues values, IccProfile profile) - => YccKScalar.ConvertToRgbInPlaceWithIcc(configuration, profile, values, this.MaximumValue); - - /// - protected override void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values) - => YccKScalar.ConvertToRgbInPlace(values, this.MaximumValue, this.HalfValue); - - /// - protected override void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - // rgb -> cmyk - CmykVector512.ConvertFromRgbVectorized(in values, this.MaximumValue, rLane, gLane, bLane); - - // cmyk -> ycck - ref Vector512 destY = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector512 destCb = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector512 destCr = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); - - ref Vector512 srcR = ref destY; - ref Vector512 srcG = ref destCb; - ref Vector512 srcB = ref destCr; - - // Used for the color conversion - Vector512 maxSampleValue = Vector512.Create(this.MaximumValue); - - Vector512 chromaOffset = Vector512.Create(this.HalfValue); - - Vector512 f0299 = Vector512.Create(0.299f); - Vector512 f0587 = Vector512.Create(0.587f); - Vector512 f0114 = Vector512.Create(0.114f); - Vector512 fn0168736 = Vector512.Create(-0.168736f); - Vector512 fn0331264 = Vector512.Create(-0.331264f); - Vector512 fn0418688 = Vector512.Create(-0.418688f); - Vector512 fn0081312F = Vector512.Create(-0.081312F); - Vector512 f05 = Vector512.Create(0.5f); - - nuint n = values.Component0.Vector512Count(); - for (nuint i = 0; i < n; i++) - { - Vector512 r = maxSampleValue - Unsafe.Add(ref srcR, i); - Vector512 g = maxSampleValue - Unsafe.Add(ref srcG, i); - Vector512 b = maxSampleValue - Unsafe.Add(ref srcB, i); - - // y = 0 + (0.299 * r) + (0.587 * g) + (0.114 * b) - // cb = 128 - (0.168736 * r) - (0.331264 * g) + (0.5 * b) - // cr = 128 + (0.5 * r) - (0.418688 * g) - (0.081312 * b) - Vector512 y = Vector512_.MultiplyAddEstimate(f0299, r, Vector512_.MultiplyAddEstimate(f0587, g, f0114 * b)); - Vector512 cb = chromaOffset + Vector512_.MultiplyAddEstimate(fn0168736, r, Vector512_.MultiplyAddEstimate(fn0331264, g, f05 * b)); - Vector512 cr = chromaOffset + Vector512_.MultiplyAddEstimate(f05, r, Vector512_.MultiplyAddEstimate(fn0418688, g, fn0081312F * b)); - - Unsafe.Add(ref destY, i) = y; - Unsafe.Add(ref destCb, i) = cb; - Unsafe.Add(ref destCr, i) = cr; - } - } - - /// - protected override void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane) - => YccKScalar.ConvertFromRgb(in values, this.HalfValue, this.MaximumValue, rLane, gLane, bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs deleted file mode 100644 index 13e5c6d5b..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterScalar.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on scalar instructions. - /// - internal abstract class JpegColorConverterScalar : JpegColorConverterBase - { - protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public sealed override bool IsAvailable => true; - - public sealed override int ElementsPerBatch => 1; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs deleted file mode 100644 index f3c3eb8db..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on API. - /// - /// - /// Converters of this family can work with data of any size. - /// Even though real life data is guaranteed to be of size - /// divisible by 8 newer SIMD instructions like AVX512 won't work with - /// such data out of the box. These converters have fallback code - /// for 'remainder' data. - /// - internal abstract class JpegColorConverterVector : JpegColorConverterBase - { - protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - /// - /// Gets a value indicating whether this converter is supported on current hardware. - /// - public static bool IsSupported => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; - - /// - public sealed override bool IsAvailable => IsSupported; - - public override int ElementsPerBatch => Vector.Count; - - /// - public sealed override void ConvertToRgbInPlace(in ComponentValues values) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); - } - } - - /// - public sealed override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertFromRgbVectorized( - values.Slice(0, simdCount), - rLane[..simdCount], - gLane[..simdCount], - bLane[..simdCount]); - } - - // Jpeg images width is always divisible by 8 without a remainder - // so it's safe to say SSE/AVX1/AVX2 implementations would never have - // 'remainder' pixels - // But some exotic simd implementations e.g. AVX-512 can have - // remainder pixels - if (remainder > 0) - { - this.ConvertFromRgbScalarRemainder( - values.Slice(simdCount, remainder), - rLane.Slice(simdCount, remainder), - gLane.Slice(simdCount, remainder), - bLane.Slice(simdCount, remainder)); - } - } - - /// - /// Converts planar jpeg component values in - /// to RGB color space in place using API. - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); - - /// - /// Converts remainder of the planar jpeg component values after - /// conversion in . - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); - - /// - /// Converts RGB lanes to jpeg component values using API. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - /// - /// Converts remainder of RGB lanes to jpeg component values after - /// conversion in . - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs deleted file mode 100644 index 5cbb376c7..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector128.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - /// - /// Converters of this family would expect input buffers lengths to be - /// divisible by 8 without a remainder. - /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. - /// DO NOT pass test data of invalid size to these converters as they - /// potentially won't do a bound check and return a false positive result. - /// - internal abstract class JpegColorConverterVector128 : JpegColorConverterBase - { - protected JpegColorConverterVector128(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector128.IsHardwareAccelerated; - - public sealed override bool IsAvailable => IsSupported; - - public sealed override int ElementsPerBatch => Vector128.Count; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs deleted file mode 100644 index 61c37d846..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector256.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - /// - /// Converters of this family would expect input buffers lengths to be - /// divisible by 8 without a remainder. - /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. - /// DO NOT pass test data of invalid size to these converters as they - /// potentially won't do a bound check and return a false positive result. - /// - internal abstract class JpegColorConverterVector256 : JpegColorConverterBase - { - protected JpegColorConverterVector256(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector256.IsHardwareAccelerated; - - public sealed override bool IsAvailable => IsSupported; - - public sealed override int ElementsPerBatch => Vector256.Count; - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs b/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs deleted file mode 100644 index 0c7d032d4..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/ColorConverters/JpegColorConverterVector512.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.Intrinsics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components; - -internal abstract partial class JpegColorConverterBase -{ - /// - /// abstract base for implementations - /// based on instructions. - /// - internal abstract class JpegColorConverterVector512 : JpegColorConverterBase - { - protected JpegColorConverterVector512(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - public static bool IsSupported => Vector512.IsHardwareAccelerated; - - /// - public override bool IsAvailable => IsSupported; - - /// - public override int ElementsPerBatch => Vector512.Count; - - /// - public sealed override void ConvertFromRgb(in ComponentValues values, Span rLane, Span gLane, Span bLane) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector512.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertFromRgbVectorized( - values.Slice(0, simdCount), - rLane[..simdCount], - gLane[..simdCount], - bLane[..simdCount]); - } - - if (remainder > 0) - { - this.ConvertFromRgbScalarRemainder( - values.Slice(simdCount, remainder), - rLane.Slice(simdCount, remainder), - gLane.Slice(simdCount, remainder), - bLane.Slice(simdCount, remainder)); - } - } - - /// - public sealed override void ConvertToRgbInPlace(in ComponentValues values) - { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); - - int length = values.Component0.Length; - int remainder = (int)((uint)length % (uint)Vector512.Count); - - int simdCount = length - remainder; - if (simdCount > 0) - { - this.ConvertToRgbInPlaceVectorized(values.Slice(0, simdCount)); - } - - if (remainder > 0) - { - this.ConvertToRgbInPlaceScalarRemainder(values.Slice(simdCount, remainder)); - } - } - - /// - /// Converts planar jpeg component values in - /// to RGB color space in place using API. - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceVectorized(in ComponentValues values); - - /// - /// Converts remainder of the planar jpeg component values after - /// conversion in . - /// - /// The input/output as a stack-only struct - protected abstract void ConvertToRgbInPlaceScalarRemainder(in ComponentValues values); - - /// - /// Converts RGB lanes to jpeg component values using API. - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbVectorized(in ComponentValues values, Span rLane, Span gLane, Span bLane); - - /// - /// Converts remainder of RGB lanes to jpeg component values after - /// conversion in . - /// - /// Jpeg component values. - /// Red colors lane. - /// Green colors lane. - /// Blue colors lane. - protected abstract void ConvertFromRgbScalarRemainder(in ComponentValues values, Span rLane, Span gLane, Span bLane); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index a1ba3fb8d..9f9dfe433 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class CmykColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); + + /// + /// Initializes a new instance of the class. + /// public CmykColorConversion() : base(4) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.CmykVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one CMYK component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.CmykVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 3ade4279f..5776f4720 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class GrayScaleColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); + + /// + /// Initializes a new instance of the class. + /// public GrayScaleColorConversion() : base(1) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.GrayScaleVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one grayscale component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.GrayScaleVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs deleted file mode 100644 index b614583ca..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterOperatorComparison.cs +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Compares each shared operator converter with the Vector512 converter it replaces. -/// -[Config(typeof(Config.Standard))] -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -[CategoriesColumn] -public class JpegColorConverterOperatorComparison -{ - private JpegColorConverterBase legacy; - private JpegColorConverterBase operatorConverter; - private float[] legacyC0; - private float[] legacyC1; - private float[] legacyC2; - private float[] legacyC3; - private float[] operatorC0; - private float[] operatorC1; - private float[] operatorC2; - private float[] operatorC3; - private float[] r; - private float[] g; - private float[] b; - private int componentCount; - - /// - /// Gets or sets the color model measured by the current benchmark case. - /// - [Params( - JpegColorModel.Grayscale, - JpegColorModel.Rgb, - JpegColorModel.Cmyk, - JpegColorModel.YCbCr, - JpegColorModel.YccK, - JpegColorModel.TiffCmyk, - JpegColorModel.TiffYccK)] - public JpegColorModel ColorModel { get; set; } - - /// - /// Gets or sets the number of pixels converted by each invocation. - /// - [Params(128, 1024)] - public int Count { get; set; } - - /// - /// Creates equivalent legacy and operator converters and their independent component buffers. - /// - [GlobalSetup] - public void Setup() - { - (JpegColorConverterBase Legacy, JpegColorConverterBase Operator, int ComponentCount) converters = - this.ColorModel switch - { - JpegColorModel.Grayscale => ( - new JpegColorConverterBase.GrayScaleVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 1), - JpegColorModel.Rgb => ( - new JpegColorConverterBase.RgbVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 3), - JpegColorModel.Cmyk => ( - new JpegColorConverterBase.CmykVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - JpegColorModel.YCbCr => ( - new JpegColorConverterBase.YCbCrVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 3), - JpegColorModel.YccK => ( - new JpegColorConverterBase.YccKVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - JpegColorModel.TiffCmyk => ( - new JpegColorConverterBase.TiffCmykVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - JpegColorModel.TiffYccK => ( - new JpegColorConverterBase.TiffYccKVector512(8), - new JpegColorConverterBase.JpegColorConverter(8), - 4), - _ => throw new InvalidOperationException(), - }; - - (this.legacy, this.operatorConverter, this.componentCount) = converters; - - Random random = new(42); - this.legacyC0 = CreateRandomValues(this.Count, random); - this.legacyC1 = CreateRandomValues(this.Count, random); - this.legacyC2 = CreateRandomValues(this.Count, random); - this.legacyC3 = CreateRandomValues(this.Count, random); - this.operatorC0 = this.legacyC0.ToArray(); - this.operatorC1 = this.legacyC1.ToArray(); - this.operatorC2 = this.legacyC2.ToArray(); - this.operatorC3 = this.legacyC3.ToArray(); - this.r = CreateRandomValues(this.Count, random); - this.g = CreateRandomValues(this.Count, random); - this.b = CreateRandomValues(this.Count, random); - } - - /// - /// Converts JPEG components to RGB using the replaced Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("ToRgb")] - public void LegacyToRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); - - this.legacy.ConvertToRgbInPlace(values); - } - - /// - /// Converts JPEG components to RGB using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("ToRgb")] - public void OperatorToRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); - - this.operatorConverter.ConvertToRgbInPlace(values); - } - - /// - /// Converts RGB to JPEG components using the replaced Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("FromRgb")] - public void LegacyFromRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateLegacyValues(); - - this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Converts RGB to JPEG components using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("FromRgb")] - public void OperatorFromRgb() - { - JpegColorConverterBase.ComponentValues values = this.CreateOperatorValues(); - - this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Creates a component view over the buffers owned by the legacy converter. - /// - /// The component view for the configured color model. - private JpegColorConverterBase.ComponentValues CreateLegacyValues() - => new( - this.componentCount, - this.legacyC0, - this.componentCount > 1 ? this.legacyC1 : this.legacyC0, - this.componentCount > 2 ? this.legacyC2 : this.legacyC0, - this.componentCount > 3 ? this.legacyC3 : []); - - /// - /// Creates a component view over the buffers owned by the operator converter. - /// - /// The component view for the configured color model. - private JpegColorConverterBase.ComponentValues CreateOperatorValues() - => new( - this.componentCount, - this.operatorC0, - this.componentCount > 1 ? this.operatorC1 : this.operatorC0, - this.componentCount > 2 ? this.operatorC2 : this.operatorC0, - this.componentCount > 3 ? this.operatorC3 : []); - - /// - /// Creates deterministic sample-domain values for one component plane. - /// - /// The number of samples to create. - /// The deterministic random source shared by setup. - /// The populated component plane. - private static float[] CreateRandomValues(int length, Random random) - { - float[] values = new float[length]; - - for (int i = 0; i < values.Length; i++) - { - values[i] = (float)random.NextDouble() * 255F; - } - - return values; - } - - /// - /// Identifies the JPEG color model used by a benchmark case. - /// - public enum JpegColorModel - { - /// - /// One luminance component. - /// - Grayscale, - - /// - /// Three direct RGB components. - /// - Rgb, - - /// - /// Four inverted Adobe CMYK components. - /// - Cmyk, - - /// - /// Three JPEG YCbCr components. - /// - YCbCr, - - /// - /// Four inverted Adobe YCCK components. - /// - YccK, - - /// - /// Four non-inverted TIFF CMYK components. - /// - TiffCmyk, - - /// - /// Four non-inverted TIFF YCCK components. - /// - TiffYccK, - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs deleted file mode 100644 index 4ec7db8a1..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/JpegColorConverterTraversalAssembly.cs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Runtime.CompilerServices; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Exposes every closed JPEG operator traversal beside the Vector512 implementation it replaces. -/// -/// -/// A 63-pixel buffer leaves 256-bit, 128-bit, and scalar remainders after the 512-bit loop, making -/// every operator overload visible in the generated traversal assembly on AVX-512 hardware. -/// -[Config(typeof(Config.Analysis))] -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -[CategoriesColumn] -public class JpegColorConverterTraversalAssembly -{ - private const int Count = 63; - - private readonly JpegColorConverterBase.GrayScaleVector512 grayscaleLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter grayscaleOperator = new(8); - private readonly JpegColorConverterBase.RgbVector512 rgbLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter rgbOperator = new(8); - private readonly JpegColorConverterBase.CmykVector512 cmykLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter cmykOperator = new(8); - private readonly JpegColorConverterBase.YCbCrVector512 yCbCrLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter yCbCrOperator = new(8); - private readonly JpegColorConverterBase.YccKVector512 yccKLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter yccKOperator = new(8); - private readonly JpegColorConverterBase.TiffCmykVector512 tiffCmykLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter tiffCmykOperator = new(8); - private readonly JpegColorConverterBase.TiffYccKVector512 tiffYccKLegacy = new(8); - private readonly JpegColorConverterBase.JpegColorConverter tiffYccKOperator = new(8); - - private readonly float[] legacyC0 = new float[Count]; - private readonly float[] legacyC1 = new float[Count]; - private readonly float[] legacyC2 = new float[Count]; - private readonly float[] legacyC3 = new float[Count]; - private readonly float[] operatorC0 = new float[Count]; - private readonly float[] operatorC1 = new float[Count]; - private readonly float[] operatorC2 = new float[Count]; - private readonly float[] operatorC3 = new float[Count]; - private readonly float[] r = new float[Count]; - private readonly float[] g = new float[Count]; - private readonly float[] b = new float[Count]; - - /// - /// Populates the component and RGB planes with deterministic sample-domain values. - /// - [GlobalSetup] - public void Setup() - { - Random random = new(42); - - for (int i = 0; i < Count; i++) - { - // Independent non-constant lanes prevent the JIT from folding arithmetic or mask decisions. - this.legacyC0[i] = this.operatorC0[i] = (float)random.NextDouble() * 255F; - this.legacyC1[i] = this.operatorC1[i] = (float)random.NextDouble() * 255F; - this.legacyC2[i] = this.operatorC2[i] = (float)random.NextDouble() * 255F; - this.legacyC3[i] = this.operatorC3[i] = (float)random.NextDouble() * 255F; - this.r[i] = (float)random.NextDouble() * 255F; - this.g[i] = (float)random.NextDouble() * 255F; - this.b[i] = (float)random.NextDouble() * 255F; - } - } - - /// - /// Runs the replaced grayscale component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Grayscale.ToRgb")] - public void GrayscaleLegacyToRgb() - => this.grayscaleLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(1)); - - /// - /// Runs the shared grayscale component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("Grayscale.ToRgb")] - public void GrayscaleOperatorToRgb() - => this.grayscaleOperator.ConvertToRgbInPlace(this.CreateOperatorValues(1)); - - /// - /// Runs the replaced grayscale RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Grayscale.FromRgb")] - public void GrayscaleLegacyFromRgb() - => this.grayscaleLegacy.ConvertFromRgb(this.CreateLegacyValues(1), this.r, this.g, this.b); - - /// - /// Runs the shared grayscale RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("Grayscale.FromRgb")] - public void GrayscaleOperatorFromRgb() - => this.grayscaleOperator.ConvertFromRgb(this.CreateOperatorValues(1), this.r, this.g, this.b); - - /// - /// Runs the replaced RGB component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Rgb.ToRgb")] - public void RgbLegacyToRgb() - => this.rgbLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); - - /// - /// Runs the shared RGB component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("Rgb.ToRgb")] - public void RgbOperatorToRgb() - => this.rgbOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); - - /// - /// Runs the replaced RGB RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Rgb.FromRgb")] - public void RgbLegacyFromRgb() - => this.rgbLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); - - /// - /// Runs the shared RGB RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("Rgb.FromRgb")] - public void RgbOperatorFromRgb() - => this.rgbOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); - - /// - /// Runs the replaced CMYK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Cmyk.ToRgb")] - public void CmykLegacyToRgb() - => this.cmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared CMYK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("Cmyk.ToRgb")] - public void CmykOperatorToRgb() - => this.cmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced CMYK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("Cmyk.FromRgb")] - public void CmykLegacyFromRgb() - => this.cmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared CMYK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("Cmyk.FromRgb")] - public void CmykOperatorFromRgb() - => this.cmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Runs the replaced YCbCr component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YCbCr.ToRgb")] - public void YCbCrLegacyToRgb() - => this.yCbCrLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(3)); - - /// - /// Runs the shared YCbCr component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("YCbCr.ToRgb")] - public void YCbCrOperatorToRgb() - => this.yCbCrOperator.ConvertToRgbInPlace(this.CreateOperatorValues(3)); - - /// - /// Runs the replaced YCbCr RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YCbCr.FromRgb")] - public void YCbCrLegacyFromRgb() - => this.yCbCrLegacy.ConvertFromRgb(this.CreateLegacyValues(3), this.r, this.g, this.b); - - /// - /// Runs the shared YCbCr RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("YCbCr.FromRgb")] - public void YCbCrOperatorFromRgb() - => this.yCbCrOperator.ConvertFromRgb(this.CreateOperatorValues(3), this.r, this.g, this.b); - - /// - /// Runs the replaced YCCK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YccK.ToRgb")] - public void YccKLegacyToRgb() - => this.yccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared YCCK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("YccK.ToRgb")] - public void YccKOperatorToRgb() - => this.yccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced YCCK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("YccK.FromRgb")] - public void YccKLegacyFromRgb() - => this.yccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared YCCK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("YccK.FromRgb")] - public void YccKOperatorFromRgb() - => this.yccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Runs the replaced TIFF CMYK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffCmyk.ToRgb")] - public void TiffCmykLegacyToRgb() - => this.tiffCmykLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared TIFF CMYK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffCmyk.ToRgb")] - public void TiffCmykOperatorToRgb() - => this.tiffCmykOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced TIFF CMYK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffCmyk.FromRgb")] - public void TiffCmykLegacyFromRgb() - => this.tiffCmykLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared TIFF CMYK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffCmyk.FromRgb")] - public void TiffCmykOperatorFromRgb() - => this.tiffCmykOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Runs the replaced TIFF YCCK component-to-RGB traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffYccK.ToRgb")] - public void TiffYccKLegacyToRgb() - => this.tiffYccKLegacy.ConvertToRgbInPlace(this.CreateLegacyValues(4)); - - /// - /// Runs the shared TIFF YCCK component-to-RGB traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffYccK.ToRgb")] - public void TiffYccKOperatorToRgb() - => this.tiffYccKOperator.ConvertToRgbInPlace(this.CreateOperatorValues(4)); - - /// - /// Runs the replaced TIFF YCCK RGB-to-component traversal. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("TiffYccK.FromRgb")] - public void TiffYccKLegacyFromRgb() - => this.tiffYccKLegacy.ConvertFromRgb(this.CreateLegacyValues(4), this.r, this.g, this.b); - - /// - /// Runs the shared TIFF YCCK RGB-to-component traversal. - /// - [Benchmark] - [BenchmarkCategory("TiffYccK.FromRgb")] - public void TiffYccKOperatorFromRgb() - => this.tiffYccKOperator.ConvertFromRgb(this.CreateOperatorValues(4), this.r, this.g, this.b); - - /// - /// Creates a correctly aliased component view over the legacy planes. - /// - /// The number of component planes owned by the color model. - /// The legacy component view. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private JpegColorConverterBase.ComponentValues CreateLegacyValues(int componentCount) - => new( - componentCount, - this.legacyC0, - componentCount > 1 ? this.legacyC1 : this.legacyC0, - componentCount > 2 ? this.legacyC2 : this.legacyC0, - componentCount > 3 ? this.legacyC3 : []); - - /// - /// Creates a correctly aliased component view over the operator planes. - /// - /// The number of component planes owned by the color model. - /// The operator component view. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private JpegColorConverterBase.ComponentValues CreateOperatorValues(int componentCount) - => new( - componentCount, - this.operatorC0, - componentCount > 1 ? this.operatorC1 : this.operatorC0, - componentCount > 2 ? this.operatorC2 : this.operatorC0, - componentCount > 3 ? this.operatorC3 : []); -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 2916dcdce..d72bbb40b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class RgbColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); + + /// + /// Initializes a new instance of the class. + /// public RgbColorConversion() : base(3) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.RgbVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one RGB component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.RgbVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index fbd762af4..70936153a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YCbCrColorConversion : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); + + /// + /// Initializes a new instance of the class. + /// public YCbCrColorConversion() : base(3) { } + /// + /// Converts one YCbCr component row through the adaptive operator traversal. + /// [Benchmark] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YCbCrVector256(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.YCbCrVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs deleted file mode 100644 index bba07b1e1..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrOperatorComparison.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using SixLabors.ImageSharp.Formats.Jpeg.Components; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; - -/// -/// Compares the shared YCbCr operator traversal with the Vector512 implementation it replaces. -/// -[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] -[CategoriesColumn] -public class YCbCrOperatorComparison -{ - private JpegColorConverterBase.YCbCrVector512 legacy; - private JpegColorConverterBase.JpegColorConverter operatorConverter; - private float[] legacyC0; - private float[] legacyC1; - private float[] legacyC2; - private float[] operatorC0; - private float[] operatorC1; - private float[] operatorC2; - private float[] r; - private float[] g; - private float[] b; - - /// - /// Gets or sets the number of pixels converted by each invocation. - /// - [Params(8, 128, 1024)] - public int Count { get; set; } - - /// - /// Creates equivalent converter inputs in independent component buffers. - /// - [GlobalSetup] - public void Setup() - { - this.legacy = new JpegColorConverterBase.YCbCrVector512(8); - this.operatorConverter = - new JpegColorConverterBase.JpegColorConverter(8); - - Random random = new(42); - this.legacyC0 = CreateRandomValues(this.Count, random); - this.legacyC1 = CreateRandomValues(this.Count, random); - this.legacyC2 = CreateRandomValues(this.Count, random); - this.operatorC0 = this.legacyC0.ToArray(); - this.operatorC1 = this.legacyC1.ToArray(); - this.operatorC2 = this.legacyC2.ToArray(); - this.r = CreateRandomValues(this.Count, random); - this.g = CreateRandomValues(this.Count, random); - this.b = CreateRandomValues(this.Count, random); - } - - /// - /// Converts YCbCr components to RGB using the Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("ToRgb")] - public void LegacyToRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); - - this.legacy.ConvertToRgbInPlace(values); - } - - /// - /// Converts YCbCr components to RGB using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("ToRgb")] - public void OperatorToRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); - - this.operatorConverter.ConvertToRgbInPlace(values); - } - - /// - /// Converts RGB to YCbCr components using the Vector512 implementation. - /// - [Benchmark(Baseline = true)] - [BenchmarkCategory("FromRgb")] - public void LegacyFromRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.legacyC0, this.legacyC1, this.legacyC2, []); - - this.legacy.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Converts RGB to YCbCr components using the shared operator traversal. - /// - [Benchmark] - [BenchmarkCategory("FromRgb")] - public void OperatorFromRgb() - { - JpegColorConverterBase.ComponentValues values = - new(3, this.operatorC0, this.operatorC1, this.operatorC2, []); - - this.operatorConverter.ConvertFromRgb(values, this.r, this.g, this.b); - } - - /// - /// Creates deterministic sample-domain values for one component plane. - /// - /// The number of samples to create. - /// The deterministic random source shared by setup. - /// The populated component plane. - private static float[] CreateRandomValues(int length, Random random) - { - float[] values = new float[length]; - - for (int i = 0; i < values.Length; i++) - { - values[i] = (float)random.NextDouble() * 255F; - } - - return values; - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index e6b04c152..80adff5cc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -9,40 +9,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg; [Config(typeof(Config.Short))] public class YccKColorConverter : ColorConversionBenchmark { + private readonly JpegColorConverterBase converter = + JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); + + /// + /// Initializes a new instance of the class. + /// public YccKColorConverter() : base(4) { } - [Benchmark(Baseline = true)] - public void Scalar() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKScalar(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector128() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector128(8).ConvertToRgbInPlace(values); - } - - [Benchmark] - public void SimdVector256() - { - JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - - new JpegColorConverterBase.YccKVector256(8).ConvertToRgbInPlace(values); - } - + /// + /// Converts one YccK component row through the adaptive operator traversal. + /// [Benchmark] - public void SimdVector512() + public void ConvertToRgb() { JpegColorConverterBase.ComponentValues values = new(this.Input, 0); - new JpegColorConverterBase.YccKVector512(8).ConvertToRgbInPlace(values); + this.converter.ConvertToRgbInPlace(values); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs index 0547376b7..e5a434067 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncode.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; /// -/// Compares the shared PNG map/reduce traversal with the filter-specific traversals it replaces. +/// Measures the shared PNG filter map/reduce traversal. /// [Config(typeof(Config.Short))] public class PngFilterEncode @@ -17,8 +17,7 @@ public class PngFilterEncode private byte[] scanline; private byte[] previousScanline; - private byte[] currentResult; - private byte[] baselineResult; + private byte[] result; /// /// Gets or sets the filter evaluated by each invocation. @@ -33,15 +32,14 @@ public class PngFilterEncode public int Count { get; set; } /// - /// Creates deterministic non-uniform inputs and independent result buffers. + /// Creates deterministic non-uniform inputs and a result buffer. /// [GlobalSetup] public void Setup() { this.scanline = new byte[this.Count]; this.previousScanline = new byte[this.Count]; - this.currentResult = new byte[this.Count + 1]; - this.baselineResult = new byte[this.Count + 1]; + this.result = new byte[this.Count + 1]; Random random = new(12345678); random.NextBytes(this.scanline); @@ -49,109 +47,53 @@ public class PngFilterEncode } /// - /// Executes the operator-driven map/reduce traversal. + /// Executes the shared operator-driven map/reduce traversal. /// /// The filter variance sum. [Benchmark] - public int Current() + public int Encode() => this.Filter switch { - PngFilterMethod.Sub => this.EncodeSubCurrent(), - PngFilterMethod.Up => this.EncodeUpCurrent(), - PngFilterMethod.Average => this.EncodeAverageCurrent(), - PngFilterMethod.Paeth => this.EncodePaethCurrent(), + PngFilterMethod.Sub => this.EncodeSub(), + PngFilterMethod.Up => this.EncodeUp(), + PngFilterMethod.Average => this.EncodeAverage(), + PngFilterMethod.Paeth => this.EncodePaeth(), _ => throw new InvalidOperationException() }; - /// - /// Executes the filter-specific traversal being replaced. - /// - /// The filter variance sum. - [Benchmark(Baseline = true)] - public int Baseline() - { - int sum; - - switch (this.Filter) - { - case PngFilterMethod.Sub: - PngFilterEncodeBaseline.EncodeSub( - this.scanline, - this.baselineResult, - BytesPerPixel, - out sum); - - break; - - case PngFilterMethod.Up: - PngFilterEncodeBaseline.EncodeUp( - this.scanline, - this.previousScanline, - this.baselineResult, - out sum); - - break; - - case PngFilterMethod.Average: - PngFilterEncodeBaseline.EncodeAverage( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out sum); - - break; - - case PngFilterMethod.Paeth: - PngFilterEncodeBaseline.EncodePaeth( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out sum); - - break; - - default: - throw new InvalidOperationException(); - } - - return sum; - } - /// /// Executes the current Sub encoder. /// - private int EncodeSubCurrent() + private int EncodeSub() { - SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out int sum); + SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out int sum); return sum; } /// /// Executes the current Up encoder. /// - private int EncodeUpCurrent() + private int EncodeUp() { - UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out int sum); + UpFilter.Encode(this.scanline, this.previousScanline, this.result, out int sum); return sum; } /// /// Executes the current Average encoder. /// - private int EncodeAverageCurrent() + private int EncodeAverage() { - AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); + AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum); return sum; } /// /// Executes the current Paeth encoder. /// - private int EncodePaethCurrent() + private int EncodePaeth() { - PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out int sum); + PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out int sum); return sum; } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs index bf8a18c12..5461cb149 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeAssembly.cs @@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Formats.Png.Filters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; /// -/// Exposes every normalized PNG filter and retained baseline for assembly comparison. +/// Exposes every normalized PNG filter for assembly inspection. /// [Config(typeof(Config.Analysis))] public class PngFilterEncodeAssembly @@ -17,8 +17,7 @@ public class PngFilterEncodeAssembly private byte[] scanline; private byte[] previousScanline; - private byte[] currentResult; - private byte[] baselineResult; + private byte[] result; /// /// Creates inputs whose suffix exercises 512-, 256-, and 128-bit register widths. @@ -28,8 +27,7 @@ public class PngFilterEncodeAssembly { this.scanline = new byte[Count]; this.previousScanline = new byte[Count]; - this.currentResult = new byte[Count + 1]; - this.baselineResult = new byte[Count + 1]; + this.result = new byte[Count + 1]; Random random = new(12345678); random.NextBytes(this.scanline); @@ -41,64 +39,26 @@ public class PngFilterEncodeAssembly /// [Benchmark] public void Sub() - => SubFilter.Encode(this.scanline, this.currentResult, BytesPerPixel, out _); + => SubFilter.Encode(this.scanline, this.result, BytesPerPixel, out _); /// /// Executes the normalized Up encoder. /// [Benchmark] public void Up() - => UpFilter.Encode(this.scanline, this.previousScanline, this.currentResult, out _); + => UpFilter.Encode(this.scanline, this.previousScanline, this.result, out _); /// /// Executes the normalized Average encoder. /// [Benchmark] public void Average() - => AverageFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); + => AverageFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); /// /// Executes the normalized Paeth encoder. /// [Benchmark] public void Paeth() - => PaethFilter.Encode(this.scanline, this.previousScanline, this.currentResult, BytesPerPixel, out _); - - /// - /// Executes the retained Sub encoder. - /// - [Benchmark] - public void BaselineSub() - => PngFilterEncodeBaseline.EncodeSub(this.scanline, this.baselineResult, BytesPerPixel, out _); - - /// - /// Executes the retained Up encoder. - /// - [Benchmark] - public void BaselineUp() - => PngFilterEncodeBaseline.EncodeUp(this.scanline, this.previousScanline, this.baselineResult, out _); - - /// - /// Executes the retained Average encoder. - /// - [Benchmark] - public void BaselineAverage() - => PngFilterEncodeBaseline.EncodeAverage( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out _); - - /// - /// Executes the retained Paeth encoder. - /// - [Benchmark] - public void BaselinePaeth() - => PngFilterEncodeBaseline.EncodePaeth( - this.scanline, - this.previousScanline, - this.baselineResult, - BytesPerPixel, - out _); + => PaethFilter.Encode(this.scanline, this.previousScanline, this.result, BytesPerPixel, out _); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs deleted file mode 100644 index e28cc8105..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Png/PngFilterEncodeBaseline.cs +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Png; - -/// -/// Retains the filter-specific PNG encode traversals for direct performance comparison. -/// -internal static class PngFilterEncodeBaseline -{ - /// - /// Executes the filter-specific Sub traversal. - /// - public static void EncodeSub( - ReadOnlySpan scanline, - Span result, - int bytesPerPixel, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 1; - - nuint x = 0; - - for (; x < (uint)bytesPerPixel;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = scan; - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 residual = Avx2.Subtract(scan, left); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector accumulator = Vector.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector residual = scan - left; - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector.Count; - Numerics.Accumulate( - ref accumulator, - Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)accumulator[i]; - } - } - - for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - left); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Executes the filter-specific Up traversal. - /// - public static void EncodeUp( - ReadOnlySpan scanline, - ReadOnlySpan previousScanline, - Span result, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 2; - - nuint x = 0; - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - - for (; (int)x <= scanline.Length - Vector256.Count;) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector256 residual = Avx2.Subtract(scan, above); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector accumulator = Vector.Zero; - - for (; (int)x <= scanline.Length - Vector.Count;) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector residual = scan - above; - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector.Count; - Numerics.Accumulate( - ref accumulator, - Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)accumulator[i]; - } - } - - for (; x < (uint)scanline.Length;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Executes the filter-specific Average traversal. - /// - public static void EncodeAverage( - ReadOnlySpan scanline, - ReadOnlySpan previousScanline, - Span result, - uint bytesPerPixel, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 3; - - nuint x = 0; - - for (; x < bytesPerPixel;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - (above >> 1)); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - Vector256 allBitsSet = Avx2.CompareEqual(accumulator, accumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector256 average = Avx2.Xor( - Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), - allBitsSet); - - Vector256 residual = Avx2.Subtract(scan, average); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Sse2.IsSupported) - { - Vector128 zero = Vector128.Zero; - Vector128 accumulator = Vector128.Zero; - Vector128 allBitsSet = Sse2.CompareEqual(accumulator, accumulator).AsByte(); - - for (nuint xLeft = x - bytesPerPixel; (int)x <= scanline.Length - Vector128.Count; xLeft += (uint)Vector128.Count) - { - Vector128 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector128 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector128 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector128 average = Sse2.Xor( - Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), - allBitsSet); - - Vector128 residual = Sse2.Subtract(scan, average); - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector128.Count; - - Vector128 absolute; - - if (Ssse3.IsSupported) - { - absolute = Ssse3.Abs(residual.AsSByte()); - } - else - { - Vector128 mask = Sse2.CompareGreaterThan(zero.AsSByte(), residual.AsSByte()); - absolute = Sse2.Xor(Sse2.Add(residual.AsSByte(), mask), mask).AsByte(); - } - - accumulator = Sse2.Add( - accumulator, - Sse2.SumAbsoluteDifferences(absolute, zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - - for (nuint xLeft = x - bytesPerPixel; x < (uint)scanline.Length; xLeft++) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - ((left + above) >> 1)); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Executes the filter-specific Paeth traversal. - /// - public static void EncodePaeth( - ReadOnlySpan scanline, - ReadOnlySpan previousScanline, - Span result, - int bytesPerPixel, - out int sum) - { - ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline); - ref byte previousBaseRef = ref MemoryMarshal.GetReference(previousScanline); - ref byte resultBaseRef = ref MemoryMarshal.GetReference(result); - sum = 0; - resultBaseRef = 4; - - nuint x = 0; - - for (; x < (uint)bytesPerPixel;) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte above = Unsafe.Add(ref previousBaseRef, x); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - above); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - - if (Avx2.IsSupported) - { - Vector256 zero = Vector256.Zero; - Vector256 accumulator = Vector256.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector256.Count; xLeft += (uint)Vector256.Count) - { - Vector256 scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector256 left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector256 above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector256 upperLeft = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)); - Vector256 residual = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft)); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector256.Count; - accumulator = Avx2.Add( - accumulator, - Avx2.SumAbsoluteDifferences(Avx2.Abs(residual.AsSByte()), zero).AsInt32()); - } - - sum += Numerics.EvenReduceSum(accumulator); - } - else if (Vector.IsHardwareAccelerated) - { - Vector accumulator = Vector.Zero; - - for (nuint xLeft = x - (uint)bytesPerPixel; (int)x <= scanline.Length - Vector.Count; xLeft += (uint)Vector.Count) - { - Vector scan = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, x)); - Vector left = Unsafe.As>(ref Unsafe.Add(ref scanBaseRef, xLeft)); - Vector above = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, x)); - Vector upperLeft = Unsafe.As>(ref Unsafe.Add(ref previousBaseRef, xLeft)); - Vector residual = scan - PaethPredictor(left, above, upperLeft); - - Unsafe.As>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = residual; - x += (uint)Vector.Count; - Numerics.Accumulate( - ref accumulator, - Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(residual)))); - } - - for (int i = 0; i < Vector.Count; i++) - { - sum += (int)accumulator[i]; - } - } - - for (nuint xLeft = x - (uint)bytesPerPixel; x < (uint)scanline.Length; xLeft++) - { - byte scan = Unsafe.Add(ref scanBaseRef, x); - byte left = Unsafe.Add(ref scanBaseRef, xLeft); - byte above = Unsafe.Add(ref previousBaseRef, x); - byte upperLeft = Unsafe.Add(ref previousBaseRef, xLeft); - x++; - ref byte residual = ref Unsafe.Add(ref resultBaseRef, x); - residual = (byte)(scan - PaethPredictor(left, above, upperLeft)); - sum += Numerics.Abs(unchecked((sbyte)residual)); - } - } - - /// - /// Selects the scalar Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredictor(byte left, byte above, byte upperLeft) - { - int p = left + above - upperLeft; - int distanceLeft = Numerics.Abs(p - left); - int distanceAbove = Numerics.Abs(p - above); - int distanceUpperLeft = Numerics.Abs(p - upperLeft); - - if (distanceLeft <= distanceAbove && distanceLeft <= distanceUpperLeft) - { - return left; - } - - return distanceAbove <= distanceUpperLeft ? above : upperLeft; - } - - /// - /// Selects the AVX2 Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector256 PaethPredictor( - Vector256 left, - Vector256 above, - Vector256 upperLeft) - { - Vector256 zero = Vector256.Zero; - Vector256 aboveMinusUpper = Avx2.SubtractSaturate(above, upperLeft); - Vector256 leftMinusUpper = Avx2.SubtractSaturate(left, upperLeft); - Vector256 distanceLeft = - Avx2.Or(Avx2.SubtractSaturate(upperLeft, above), aboveMinusUpper); - - Vector256 distanceAbove = - Avx2.Or(Avx2.SubtractSaturate(upperLeft, left), leftMinusUpper); - - Vector256 sameDirection = Avx2.CompareEqual( - Avx2.CompareEqual(aboveMinusUpper, zero), - Avx2.CompareEqual(leftMinusUpper, zero)); - - Vector256 distanceUpper = Avx2.Or( - sameDirection, - Avx2.Or( - Avx2.SubtractSaturate(distanceAbove, distanceLeft), - Avx2.SubtractSaturate(distanceLeft, distanceAbove))); - - Vector256 minimumAboveUpper = Avx2.Min(distanceUpper, distanceAbove); - Vector256 aboveOrUpper = Avx2.BlendVariable( - upperLeft, - above, - Avx2.CompareEqual(minimumAboveUpper, distanceAbove)); - - return Avx2.BlendVariable( - aboveOrUpper, - left, - Avx2.CompareEqual(Avx2.Min(minimumAboveUpper, distanceLeft), distanceLeft)); - } - - /// - /// Selects the portable vector Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor( - Vector left, - Vector above, - Vector upperLeft) - { - Vector.Widen(left, out Vector leftLow, out Vector leftHigh); - Vector.Widen(above, out Vector aboveLow, out Vector aboveHigh); - Vector.Widen(upperLeft, out Vector upperLow, out Vector upperHigh); - - Vector lower = PaethPredictor( - Vector.AsVectorInt16(leftLow), - Vector.AsVectorInt16(aboveLow), - Vector.AsVectorInt16(upperLow)); - - Vector upper = PaethPredictor( - Vector.AsVectorInt16(leftHigh), - Vector.AsVectorInt16(aboveHigh), - Vector.AsVectorInt16(upperHigh)); - - return Vector.AsVectorByte(Vector.Narrow(lower, upper)); - } - - /// - /// Selects the portable widened Paeth predictor. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Vector PaethPredictor( - Vector left, - Vector above, - Vector upperLeft) - { - Vector p = left + above - upperLeft; - Vector distanceLeft = Vector.Abs(p - left); - Vector distanceAbove = Vector.Abs(p - above); - Vector distanceUpper = Vector.Abs(p - upperLeft); - - Vector chooseLeft = Vector.BitwiseAnd( - Vector.LessThanOrEqual(distanceLeft, distanceAbove), - Vector.LessThanOrEqual(distanceLeft, distanceUpper)); - - return Vector.ConditionalSelect( - chooseLeft, - left, - Vector.ConditionalSelect( - Vector.LessThanOrEqual(distanceAbove, distanceUpper), - above, - upperLeft)); - } -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs deleted file mode 100644 index 14802f590..000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/AddSpan.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class AddSpan -{ - private byte[] scalarValues = null!; - private byte[] tensorValues = null!; - private byte[] addends = null!; - - /// - /// Gets or sets the number of values to add. - /// - [Params(32, 257, 2048)] - public int Length { get; set; } - - /// - /// Creates equivalent deterministic inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.scalarValues = new byte[this.Length]; - this.tensorValues = new byte[this.Length]; - this.addends = new byte[this.Length]; - - for (int i = 0; i < this.Length; i++) - { - byte value = (byte)((i * 17) + 31); - this.scalarValues[i] = value; - this.tensorValues[i] = value; - this.addends[i] = (byte)((i * 29) + 7); - } - } - - /// - /// Adds the values with a scalar loop. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark(Baseline = true)] - public byte Scalar() - { - for (int i = 0; i < this.scalarValues.Length; i++) - { - this.scalarValues[i] += this.addends[i]; - } - - return this.scalarValues[0]; - } - - /// - /// Adds the values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark] - public byte TensorPipeline() - { - TensorPrimitives_.Add(this.tensorValues, this.addends, this.tensorValues); - return this.tensorValues[0]; - } -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs deleted file mode 100644 index bc11fbaff..000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/NormalizeSpan.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using BenchmarkDotNet.Attributes; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -public class NormalizeSpan -{ - private float[] scalarValues = null!; - private float[] tensorValues = null!; - - /// - /// Gets or sets the number of values to normalize. - /// - [Params(7, 32, 257, 2048)] - public int Length { get; set; } - - /// - /// Creates equivalent deterministic inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.scalarValues = new float[this.Length]; - this.tensorValues = new float[this.Length]; - - for (int i = 0; i < this.scalarValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.scalarValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Normalizes the values with a scalar loop. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Scalar() - { - for (int i = 0; i < this.scalarValues.Length; i++) - { - this.scalarValues[i] /= 4096F; - } - - return this.scalarValues[0]; - } - - /// - /// Normalizes the values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the mutated data observable to the benchmark harness. - [Benchmark] - public float TensorPipeline() - { - Numerics.Normalize(this.tensorValues, 4096F); - return this.tensorValues[0]; - } -} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs new file mode 100644 index 000000000..855d5644c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssembly.cs @@ -0,0 +1,175 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; + +/// +/// Exposes every floating-point tensor compatibility operation for assembly inspection. +/// +[Config(typeof(Config.Analysis))] +public class TensorPrimitivesAssembly +{ + private const int Count = 2048; + + private readonly float[] x = new float[Count]; + private readonly float[] y = new float[Count]; + private readonly float[] destination = new float[Count]; + + /// + /// Populates the input spans with deterministic non-uniform values. + /// + [GlobalSetup] + public void Setup() + { + for (int i = 0; i < Count; i++) + { + this.x[i] = ((i * 17) % 251) + 1; + this.y[i] = ((i * 29) % 251) + 1; + } + } + + /// + /// Adds two floating-point spans. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Add() + { + TensorPrimitives_.Add(this.x, this.y, this.destination); + return this.destination[0]; + } + + /// + /// Clamps a floating-point span between scalar bounds. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Clamp() + { + TensorPrimitives_.Clamp(this.x, 64F, 128F, this.destination); + return this.destination[0]; + } + + /// + /// Divides a floating-point span by a scalar. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Divide() + { + TensorPrimitives_.Divide(this.x, 4096F, this.destination); + return this.destination[0]; + } + + /// + /// Computes the element-wise maximum of a floating-point span and a scalar. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Max() + { + TensorPrimitives_.Max(this.x, 64F, this.destination); + return this.destination[0]; + } + + /// + /// Multiplies a floating-point span by a scalar. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public float Multiply() + { + TensorPrimitives_.Multiply(this.x, 0.5F, this.destination); + return this.destination[0]; + } +} + +/// +/// Exposes integral addition specializations for assembly inspection. +/// +/// The integral element type. +[Config(typeof(Config.Analysis))] +[GenericTypeArguments(typeof(byte))] +[GenericTypeArguments(typeof(uint))] +public class TensorPrimitivesIntegralAddAssembly + where T : unmanaged, INumber +{ + private const int Count = 2048; + + private readonly T[] x = new T[Count]; + private readonly T[] y = new T[Count]; + private readonly T[] destination = new T[Count]; + + /// + /// Populates the input spans with deterministic non-uniform values. + /// + [GlobalSetup] + public void Setup() + { + for (int i = 0; i < Count; i++) + { + this.x[i] = T.CreateTruncating((i * 17) + 31); + this.y[i] = T.CreateTruncating((i * 29) + 7); + } + } + + /// + /// Adds two integral spans. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public T Add() + { + TensorPrimitives_.Add(this.x, this.y, this.destination); + return this.destination[0]; + } +} + +/// +/// Exposes integral clamp specializations for assembly inspection. +/// +/// The integral element type. +[Config(typeof(Config.Analysis))] +[GenericTypeArguments(typeof(byte))] +[GenericTypeArguments(typeof(uint))] +[GenericTypeArguments(typeof(int))] +public class TensorPrimitivesIntegralClampAssembly + where T : unmanaged, INumber +{ + private const int Count = 2048; + + private readonly T[] source = new T[Count]; + private readonly T[] destination = new T[Count]; + private T min; + private T max; + + /// + /// Populates the input span and scalar bounds with deterministic values. + /// + [GlobalSetup] + public void Setup() + { + this.min = T.CreateTruncating(64); + this.max = T.CreateTruncating(128); + + for (int i = 0; i < Count; i++) + { + this.source[i] = T.CreateTruncating((i * 31) % 257); + } + } + + /// + /// Clamps an integral span between scalar bounds. + /// + /// The first result, which keeps the destination observable. + [Benchmark] + public T Clamp() + { + TensorPrimitives_.Clamp(this.source, this.min, this.max, this.destination); + return this.destination[0]; + } +} diff --git a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs b/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs deleted file mode 100644 index 08ac3d09b..000000000 --- a/tests/ImageSharp.Benchmarks/General/BasicMath/TensorPrimitivesAssemblyComparison.cs +++ /dev/null @@ -1,813 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; -using System.Runtime.Intrinsics.X86; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Common.Helpers; - -namespace SixLabors.ImageSharp.Benchmarks.General.BasicMath; - -#pragma warning disable SA1649 // File name should match first type name -public class TensorPrimitivesJpegMultiplyAssemblyComparison -#pragma warning restore SA1649 // File name should match first type name -{ - private readonly float multiplier = -1F; - private float[] legacyValues = null!; - private float[] tensorValues = null!; - - /// - /// Creates equivalent stable inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new float[256]; - this.tensorValues = new float[256]; - - for (int i = 0; i < this.legacyValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.legacyValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Multiplies the row with the retired JPEG AVX pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - LegacyMultiply(this.legacyValues, this.multiplier); - return this.legacyValues[0]; - } - - /// - /// Multiplies the row with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - TensorPrimitives_.Multiply(this.tensorValues, this.multiplier, this.tensorValues); - return this.tensorValues[0]; - } - - /// - /// Reproduces the retired JPEG multiplication loop for assembly comparison. - /// - /// The row to multiply. - /// The scalar multiplier. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyMultiply(Span target, float multiplier) - { - ref Vector256 targetVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - nuint count = (uint)target.Length / (uint)Vector256.Count; - Vector256 multiplierVector = Vector256.Create(multiplier); - - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVector, i) = Avx.Multiply(Unsafe.Add(ref targetVector, i), multiplierVector); - } - } -} - -public class TensorPrimitivesNormalizeAssemblyComparison -{ - private readonly float divisor = -1F; - private float[] legacyValues = null!; - private float[] tensorValues = null!; - - /// - /// Creates equivalent stable inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new float[7]; - this.tensorValues = new float[7]; - - for (int i = 0; i < this.legacyValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.legacyValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Normalizes the values with the retired fixed-width pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - LegacyNormalize(this.legacyValues, this.divisor); - return this.legacyValues[0]; - } - - /// - /// Normalizes the values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - Numerics.Normalize(this.tensorValues, this.divisor); - return this.tensorValues[0]; - } - - /// - /// Reproduces the retired normalization loop for assembly comparison. - /// - /// The values to normalize. - /// The scalar divisor. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyNormalize(Span span, float sum) - { - ref float start = ref MemoryMarshal.GetReference(span); - ref float vectorEnd = ref Unsafe.Add(ref start, span.Length & ~7); - Vector256 sum256 = Vector256.Create(sum); - - while (Unsafe.IsAddressLessThan(ref start, ref vectorEnd)) - { - Unsafe.As>(ref start) /= sum256; - start = ref Unsafe.Add(ref start, (nuint)8); - } - - if ((span.Length & 7) >= 4) - { - Unsafe.As>(ref start) /= sum256.GetLower(); - start = ref Unsafe.Add(ref start, (nuint)4); - } - - ref float end = ref Unsafe.Add(ref start, span.Length & 3); - - while (Unsafe.IsAddressLessThan(ref start, ref end)) - { - start /= sum; - start = ref Unsafe.Add(ref start, (nuint)1); - } - } -} - -public class TensorPrimitivesUInt32AssemblyComparison -{ - private uint[] x = null!; - private uint[] y = null!; - private uint[] legacyDestination = null!; - private uint[] tensorDestination = null!; - - /// - /// Creates deterministic histogram inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.x = new uint[2048]; - this.y = new uint[2048]; - this.legacyDestination = new uint[2048]; - this.tensorDestination = new uint[2048]; - - for (int i = 0; i < this.x.Length; i++) - { - this.x[i] = (uint)((i * 17) + 31); - this.y[i] = (uint)((i * 29) + 7); - } - } - - /// - /// Adds histogram bins with the retired four-vector AVX2 pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public uint Legacy() - { - LegacyAdd(this.x, this.y, this.legacyDestination); - return this.legacyDestination[0]; - } - - /// - /// Adds histogram bins with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public uint Tensor() - { - TensorPrimitives_.Add(this.x, this.y, this.tensorDestination); - return this.tensorDestination[0]; - } - - /// - /// Reproduces the retired WebP histogram addition loop for assembly comparison. - /// - /// The first histogram. - /// The second histogram. - /// The destination receiving the sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyAdd(ReadOnlySpan x, ReadOnlySpan y, Span destination) - { - ref uint xRef = ref MemoryMarshal.GetReference(x); - ref uint yRef = ref MemoryMarshal.GetReference(y); - ref uint destinationRef = ref MemoryMarshal.GetReference(destination); - - nuint index = 0; - - do - { - Vector256 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, index)); - Vector256 x1 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 8)); - Vector256 x2 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 16)); - Vector256 x3 = Unsafe.As>(ref Unsafe.Add(ref xRef, index + 24)); - Vector256 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, index)); - Vector256 y1 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 8)); - Vector256 y2 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 16)); - Vector256 y3 = Unsafe.As>(ref Unsafe.Add(ref yRef, index + 24)); - - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index)) = Avx2.Add(x0, y0); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 8)) = Avx2.Add(x1, y1); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 16)) = Avx2.Add(x2, y2); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, index + 24)) = Avx2.Add(x3, y3); - index += 32; - } - while (index <= (uint)x.Length - 32); - - for (int i = (int)index; i < x.Length; i++) - { - destination[i] = x[i] + y[i]; - } - } -} - -public class TensorPrimitivesByteAssemblyComparison -{ - private byte[] x = null!; - private byte[] y = null!; - private byte[] legacyDestination = null!; - private byte[] tensorDestination = null!; - - /// - /// Creates deterministic byte inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.x = new byte[2048]; - this.y = new byte[2048]; - this.legacyDestination = new byte[2048]; - this.tensorDestination = new byte[2048]; - - for (int i = 0; i < this.x.Length; i++) - { - this.x[i] = (byte)((i * 17) + 31); - this.y[i] = (byte)((i * 29) + 7); - } - } - - /// - /// Adds bytes with the retired WebP AVX2 pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public byte Legacy() - { - LegacyAdd(this.x, this.y, this.legacyDestination); - return this.legacyDestination[0]; - } - - /// - /// Adds bytes with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public byte Tensor() - { - TensorPrimitives_.Add(this.x, this.y, this.tensorDestination); - return this.tensorDestination[0]; - } - - /// - /// Reproduces the retired WebP byte addition loop for assembly comparison. - /// - /// The first input. - /// The second input. - /// The destination receiving modulo-256 sums. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyAdd(ReadOnlySpan x, ReadOnlySpan y, Span destination) - { - ref byte xRef = ref MemoryMarshal.GetReference(x); - ref byte yRef = ref MemoryMarshal.GetReference(y); - ref byte destinationRef = ref MemoryMarshal.GetReference(destination); - - nuint i; - int maxPosition = x.Length & ~31; - - for (i = 0; i < (uint)maxPosition; i += 32) - { - Vector256 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); - Vector256 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); - Vector256 result = x0.AsByte() + y0.AsByte(); - Unsafe.As>(ref Unsafe.Add(ref destinationRef, i)) = result; - } - - for (; i < (uint)x.Length; i++) - { - Unsafe.Add(ref destinationRef, i) = (byte)(Unsafe.Add(ref xRef, i) + Unsafe.Add(ref yRef, i)); - } - } -} - -public class TensorPrimitivesSingleAddAssemblyComparison -{ - private float[] legacyTarget = null!; - private float[] tensorTarget = null!; - private float[] source = null!; - - /// - /// Creates deterministic JPEG row inputs. - /// - [GlobalSetup] - public void Setup() - { - this.legacyTarget = new float[2048]; - this.tensorTarget = new float[2048]; - this.source = new float[2048]; - - for (int i = 0; i < this.source.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.legacyTarget[i] = value; - this.tensorTarget[i] = value; - this.source[i] = ((i * 29) % 31) - 15; - } - } - - /// - /// Adds JPEG row values with the retired AVX pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - LegacyAdd(this.legacyTarget, this.source); - return this.legacyTarget[0]; - } - - /// - /// Adds JPEG row values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - TensorPrimitives_.Add(this.tensorTarget, this.source, this.tensorTarget); - return this.tensorTarget[0]; - } - - /// - /// Reproduces the retired JPEG row addition loop for assembly comparison. - /// - /// The destination row. - /// The row added to the destination. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyAdd(Span target, ReadOnlySpan source) - { - ref Vector256 targetVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(target)); - ref Vector256 sourceVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - nuint count = (uint)source.Length / (uint)Vector256.Count; - - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref targetVector, i) = Avx.Add(Unsafe.Add(ref targetVector, i), Unsafe.Add(ref sourceVector, i)); - } - } -} - -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -[GenericTypeArguments(typeof(int))] -[GenericTypeArguments(typeof(float))] -[GenericTypeArguments(typeof(double))] -public class TensorPrimitivesClampAssemblyComparison - where T : unmanaged, INumber -{ - private T[] legacyValues = null!; - private T[] tensorValues = null!; - private T min; - private T max; - - /// - /// Creates deterministic clamp inputs for the current element type. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new T[2048]; - this.tensorValues = new T[2048]; - this.min = T.CreateTruncating(64); - this.max = T.CreateTruncating(128); - - for (int i = 0; i < this.legacyValues.Length; i++) - { - T value = T.CreateTruncating((i * 31) % 257); - this.legacyValues[i] = value; - this.tensorValues[i] = value; - } - } - - /// - /// Clamps values with the retired pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public T Legacy() - { - LegacyClamp(this.legacyValues, this.min, this.max); - return this.legacyValues[0]; - } - - /// - /// Clamps values with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public T Tensor() - { - TensorPrimitives_.Clamp(this.tensorValues, this.min, this.max, this.tensorValues); - return this.tensorValues[0]; - } - - /// - /// Reproduces the retired clamp pipeline for assembly comparison. - /// - /// The values to clamp. - /// The inclusive lower bound. - /// The inclusive upper bound. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void LegacyClamp(Span span, T min, T max) - { - int remainder = Numerics.ModuloP2(span.Length, Vector.Count); - int adjustedCount = span.Length - remainder; - - if (adjustedCount > 0) - { - Vector vectorMin = new(min); - Vector vectorMax = new(max); - nint vectorCount = (nint)(uint)adjustedCount / Vector.Count; - nint remainingVectors = Numerics.Modulo4(vectorCount); - nint unrolledVectors = vectorCount - remainingVectors; - - ref Vector current0 = ref Unsafe.As>(ref MemoryMarshal.GetReference(span)); - ref Vector current1 = ref Unsafe.Add(ref current0, 1); - ref Vector current2 = ref Unsafe.Add(ref current0, 2); - ref Vector current3 = ref Unsafe.Add(ref current0, 3); - ref Vector end = ref Unsafe.Add(ref current0, unrolledVectors); - - while (Unsafe.IsAddressLessThan(ref current0, ref end)) - { - current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax); - current1 = Vector.Min(Vector.Max(vectorMin, current1), vectorMax); - current2 = Vector.Min(Vector.Max(vectorMin, current2), vectorMax); - current3 = Vector.Min(Vector.Max(vectorMin, current3), vectorMax); - - current0 = ref Unsafe.Add(ref current0, 4); - current1 = ref Unsafe.Add(ref current1, 4); - current2 = ref Unsafe.Add(ref current2, 4); - current3 = ref Unsafe.Add(ref current3, 4); - } - - if (remainingVectors > 0) - { - current0 = ref end; - end = ref Unsafe.Add(ref end, remainingVectors); - - while (Unsafe.IsAddressLessThan(ref current0, ref end)) - { - current0 = Vector.Min(Vector.Max(vectorMin, current0), vectorMax); - current0 = ref Unsafe.Add(ref current0, 1); - } - } - } - - for (int i = adjustedCount; i < span.Length; i++) - { - T value = span[i]; - span[i] = value > max ? max : value < min ? min : value; - } - } -} - -public class TensorPrimitivesIccMaxAssemblyComparison -{ - private Vector4[] legacyValues = null!; - private Vector4[] tensorValues = null!; - - /// - /// Creates deterministic ICC values containing positive and negative channels. - /// - [GlobalSetup] - public void Setup() - { - this.legacyValues = new Vector4[512]; - this.tensorValues = new Vector4[512]; - - for (int i = 0; i < this.legacyValues.Length; i++) - { - float value = ((i * 17) % 251) - 125; - Vector4 vector = new(value, value + 1, value - 1, value + 2); - this.legacyValues[i] = vector; - this.tensorValues[i] = vector; - } - } - - /// - /// Clips negative channels with the retired ICC pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - for (int i = 0; i < this.legacyValues.Length; i++) - { - this.legacyValues[i] = Vector4.Max(this.legacyValues[i], Vector4.Zero); - } - - return this.legacyValues[0].X; - } - - /// - /// Clips negative channels with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - Span values = MemoryMarshal.Cast(this.tensorValues.AsSpan()); - TensorPrimitives_.Max(values, 0F, values); - return values[0]; - } -} - -public class TensorPrimitivesIccMultiplyAssemblyComparison -{ - private readonly float multiplier = 65280F / 65535F; - private Vector4[] source = null!; - private Vector4[] legacyDestination = null!; - private Vector4[] tensorDestination = null!; - - /// - /// Creates deterministic ICC inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.source = new Vector4[512]; - this.legacyDestination = new Vector4[512]; - this.tensorDestination = new Vector4[512]; - - for (int i = 0; i < this.source.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.source[i] = new Vector4(value, value + 1, value + 2, value + 3); - } - } - - /// - /// Multiplies ICC channels with the retired pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public float Legacy() - { - Span source = MemoryMarshal.Cast(this.source.AsSpan()); - Span destination = MemoryMarshal.Cast(this.legacyDestination.AsSpan()); - ref Vector sourceVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(source)); - ref Vector destinationVector = ref Unsafe.As>(ref MemoryMarshal.GetReference(destination)); - Vector scale = new(this.multiplier); - nuint count = (uint)source.Length / (uint)Vector.Count; - - for (nuint i = 0; i < count; i++) - { - Unsafe.Add(ref destinationVector, i) = Unsafe.Add(ref sourceVector, i) * scale; - } - - return destination[0]; - } - - /// - /// Multiplies ICC channels with the tensor compatibility pipeline. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float Tensor() - { - Span source = MemoryMarshal.Cast(this.source.AsSpan()); - Span destination = MemoryMarshal.Cast(this.tensorDestination.AsSpan()); - TensorPrimitives_.Multiply(source, this.multiplier, destination); - return destination[0]; - } -} - -#if NET10_0_OR_GREATER -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -[GenericTypeArguments(typeof(float))] -public class TensorPrimitivesRuntimeAddAssemblyComparison - where T : unmanaged, INumber -{ - private T[] x = null!; - private T[] y = null!; - private T[] compatibilityDestination = null!; - private T[] runtimeDestination = null!; - - /// - /// Creates deterministic inputs and independent destinations. - /// - [GlobalSetup] - public void Setup() - { - this.x = new T[2048]; - this.y = new T[2048]; - this.compatibilityDestination = new T[2048]; - this.runtimeDestination = new T[2048]; - - for (int i = 0; i < this.x.Length; i++) - { - this.x[i] = T.CreateTruncating((i * 17) + 31); - this.y[i] = T.CreateTruncating((i * 29) + 7); - } - } - - /// - /// Adds values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public T Compatibility() - { - TensorPrimitives_.Add(this.x, this.y, this.compatibilityDestination); - return this.compatibilityDestination[0]; - } - - /// - /// Adds values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public T Runtime() - { - System.Numerics.Tensors.TensorPrimitives.Add(this.x, this.y, this.runtimeDestination); - return this.runtimeDestination[0]; - } -} - -[GenericTypeArguments(typeof(byte))] -[GenericTypeArguments(typeof(uint))] -[GenericTypeArguments(typeof(int))] -[GenericTypeArguments(typeof(float))] -[GenericTypeArguments(typeof(double))] -public class TensorPrimitivesRuntimeClampAssemblyComparison - where T : unmanaged, INumber -{ - private T[] compatibilityValues = null!; - private T[] runtimeValues = null!; - private T min; - private T max; - - /// - /// Creates deterministic inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.compatibilityValues = new T[2048]; - this.runtimeValues = new T[2048]; - this.min = T.CreateTruncating(64); - this.max = T.CreateTruncating(128); - - for (int i = 0; i < this.compatibilityValues.Length; i++) - { - T value = T.CreateTruncating((i * 31) % 257); - this.compatibilityValues[i] = value; - this.runtimeValues[i] = value; - } - } - - /// - /// Clamps values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark(Baseline = true)] - public T Compatibility() - { - TensorPrimitives_.Clamp(this.compatibilityValues, this.min, this.max, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Clamps values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public T Runtime() - { - System.Numerics.Tensors.TensorPrimitives.Clamp(this.runtimeValues, this.min, this.max, this.runtimeValues); - return this.runtimeValues[0]; - } -} - -public class TensorPrimitivesRuntimeSingleScalarAssemblyComparison -{ - private readonly float scalar = -1F; - private float[] compatibilityValues = null!; - private float[] runtimeValues = null!; - - /// - /// Creates equivalent stable inputs for both implementations. - /// - [GlobalSetup] - public void Setup() - { - this.compatibilityValues = new float[2048]; - this.runtimeValues = new float[2048]; - - for (int i = 0; i < this.compatibilityValues.Length; i++) - { - float value = ((i * 17) % 251) + 1; - this.compatibilityValues[i] = value; - this.runtimeValues[i] = value; - } - } - - /// - /// Divides values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float CompatibilityDivide() - { - TensorPrimitives_.Divide(this.compatibilityValues, this.scalar, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Divides values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float RuntimeDivide() - { - System.Numerics.Tensors.TensorPrimitives.Divide(this.runtimeValues, this.scalar, this.runtimeValues); - return this.runtimeValues[0]; - } - - /// - /// Computes maximum values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float CompatibilityMax() - { - TensorPrimitives_.Max(this.compatibilityValues, 0F, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Computes maximum values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float RuntimeMax() - { - System.Numerics.Tensors.TensorPrimitives.Max(this.runtimeValues, 0F, this.runtimeValues); - return this.runtimeValues[0]; - } - - /// - /// Multiplies values with the compatibility implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float CompatibilityMultiply() - { - TensorPrimitives_.Multiply(this.compatibilityValues, this.scalar, this.compatibilityValues); - return this.compatibilityValues[0]; - } - - /// - /// Multiplies values with the .NET runtime implementation. - /// - /// The first result, which keeps the writes observable to the benchmark harness. - [Benchmark] - public float RuntimeMultiply() - { - System.Numerics.Tensors.TensorPrimitives.Multiply(this.runtimeValues, this.scalar, this.runtimeValues); - return this.runtimeValues[0]; - } -} -#endif diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs index 0eb7b9ccd..d671117b6 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransform.cs @@ -2,16 +2,13 @@ // Licensed under the Six Labors Split License. using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Intrinsics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats.Utils; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; /// -/// Compares operator-driven affine vector transforms with the duplicated traversals they replace. +/// Measures the operator-driven affine vector transforms. /// [Config(typeof(Config.Short))] public class Vector4AffineTransform @@ -21,7 +18,6 @@ public class Vector4AffineTransform private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); private Vector4[] current; - private Vector4[] baseline; /// /// Gets or sets the number of vectors transformed by each invocation. @@ -30,7 +26,7 @@ public class Vector4AffineTransform public int Count { get; set; } /// - /// Creates identical non-uniform buffers for the current and baseline traversals. + /// Creates a non-uniform input buffer. /// [GlobalSetup] public void Setup() @@ -41,167 +37,19 @@ public class Vector4AffineTransform { this.current[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); } - - this.baseline = [.. this.current]; } /// /// Executes the operator-driven multiply-then-add traversal. /// [Benchmark] - public void CurrentMultiplyThenAdd() + public void MultiplyThenAdd() => Vector4Converters.MultiplyThenAdd(this.current, Multiplier, Offset); - /// - /// Executes the duplicated multiply-then-add traversal. - /// - [Benchmark(Baseline = true)] - public void BaselineMultiplyThenAdd() - => BaselineMultiplyThenAdd(this.baseline, Multiplier, Offset); - /// /// Executes the operator-driven add-then-divide traversal. /// [Benchmark] - public void CurrentAddThenDivide() + public void AddThenDivide() => Vector4Converters.AddThenDivide(this.current, Offset, Divisor); - - /// - /// Executes the duplicated add-then-divide traversal. - /// - [Benchmark] - public void BaselineAddThenDivide() - => BaselineAddThenDivide(this.baseline, Offset, Divisor); - - /// - /// Retains the multiply-then-add traversal being replaced for direct measurement. - /// - /// The vectors to transform. - /// The component-wise multiplier. - /// The component-wise offset. - internal static void BaselineMultiplyThenAdd(Span vectors, Vector4 multiplier, Vector4 offset) - { - ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); - int index = 0; - - if (Vector512.IsHardwareAccelerated) - { - int vectorsPerVector = Vector512.Count / Vector128.Count; - Vector256 multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128()); - Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); - Vector512 multiplier512 = Vector512.Create(multiplier256, multiplier256); - Vector512 offset512 = Vector512.Create(offset256, offset256); - - for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) - { - ref Vector512 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); - - vector = (vector * multiplier512) + offset512; - } - } - - if (Vector256.IsHardwareAccelerated) - { - int vectorsPerVector = Vector256.Count / Vector128.Count; - Vector256 multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128()); - Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); - - for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) - { - ref Vector256 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); - - vector = (vector * multiplier256) + offset256; - } - } - - if (Vector128.IsHardwareAccelerated) - { - Vector128 multiplier128 = multiplier.AsVector128(); - Vector128 offset128 = offset.AsVector128(); - - for (; index < vectors.Length; index++) - { - ref Vector128 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); - - vector = (vector * multiplier128) + offset128; - } - - return; - } - - for (; index < vectors.Length; index++) - { - ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); - vector = (vector * multiplier) + offset; - } - } - - /// - /// Retains the add-then-divide traversal being replaced for direct measurement. - /// - /// The vectors to transform. - /// The component-wise offset. - /// The component-wise divisor. - internal static void BaselineAddThenDivide(Span vectors, Vector4 offset, Vector4 divisor) - { - ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); - int index = 0; - - if (Vector512.IsHardwareAccelerated) - { - int vectorsPerVector = Vector512.Count / Vector128.Count; - Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); - Vector256 divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128()); - Vector512 offset512 = Vector512.Create(offset256, offset256); - Vector512 divisor512 = Vector512.Create(divisor256, divisor256); - - for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) - { - ref Vector512 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); - - vector = (vector + offset512) / divisor512; - } - } - - if (Vector256.IsHardwareAccelerated) - { - int vectorsPerVector = Vector256.Count / Vector128.Count; - Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); - Vector256 divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128()); - - for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) - { - ref Vector256 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); - - vector = (vector + offset256) / divisor256; - } - } - - if (Vector128.IsHardwareAccelerated) - { - Vector128 offset128 = offset.AsVector128(); - Vector128 divisor128 = divisor.AsVector128(); - - for (; index < vectors.Length; index++) - { - ref Vector128 vector = ref Unsafe.As>( - ref Unsafe.Add(ref vectorBase, (uint)index)); - - vector = (vector + offset128) / divisor128; - } - - return; - } - - for (; index < vectors.Length; index++) - { - ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); - vector = (vector + offset) / divisor; - } - } } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs index fcecf1306..1b5767ebd 100644 --- a/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/Vector4AffineTransformAssembly.cs @@ -56,18 +56,4 @@ public class Vector4AffineTransformAssembly [Benchmark] public void AddThenDivide() => Vector4Converters.AddThenDivide(this.vectors, Offset, Divisor); - - /// - /// Executes the multiply-then-add traversal being replaced for assembly comparison. - /// - [Benchmark] - public void BaselineMultiplyThenAdd() - => Vector4AffineTransform.BaselineMultiplyThenAdd(this.vectors, Multiplier, Offset); - - /// - /// Executes the add-then-divide traversal being replaced for assembly comparison. - /// - [Benchmark] - public void BaselineAddThenDivide() - => Vector4AffineTransform.BaselineAddThenDivide(this.vectors, Offset, Divisor); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index c11456014..dae2d3c3f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -2,12 +2,9 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.ColorProfiles; using SixLabors.ImageSharp.Tests.TestUtilities; -using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -15,37 +12,38 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg; public class JpegColorConverterTests { private const float MaxColorChannelValue = 255F; + private const float ColorProfileTolerance = 0.1F / MaxColorChannelValue; + private const float ToRgbTolerance = 0.0001F; + private const float FromRgbTolerance = 0.01F; - private const float Precision = 0.1F / 255; - - private const int TestBufferLength = 40; - - private static readonly ApproximateColorProfileComparer ColorSpaceComparer = new(epsilon: Precision); - - public static readonly TheoryData Seeds = new() { 1, 2, 3 }; - - public JpegColorConverterTests(ITestOutputHelper output) - => this.Output = output; - - private ITestOutputHelper Output { get; } + // Independent model checks compare normalized colors at one tenth of a byte-domain sample. + private static readonly ApproximateColorProfileComparer ColorSpaceComparer = + new(epsilon: ColorProfileTolerance); + /// + /// Verifies that unsupported color spaces are rejected by the converter factory. + /// [Fact] public void GetConverterThrowsExceptionOnInvalidColorSpace() { const JpegColorSpace invalidColorSpace = (JpegColorSpace)(-1); + Assert.Throws(() => JpegColorConverterBase.GetConverter(invalidColorSpace, 8)); } + /// + /// Verifies that unsupported JPEG sample precisions are rejected by the converter factory. + /// [Fact] public void GetConverterThrowsExceptionOnInvalidPrecision() { - // Valid precisions: 8 & 12 bit const int invalidPrecision = 9; + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, invalidPrecision)); } /// - /// Verifies that each supported color space and precision resolves to an available converter. + /// Verifies that every supported color-space and precision pair resolves to the shared operator converter. /// /// The JPEG color space. /// The JPEG sample precision. @@ -68,312 +66,137 @@ public class JpegColorConverterTests { JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, precision); - Assert.NotNull(converter); Assert.True(converter.IsAvailable); Assert.Equal(colorSpace, converter.ColorSpace); Assert.Equal(precision, converter.Precision); } - [Fact] - public void GetConverterReturnsCorrectConverterWithRgbColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithGrayScaleColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Grayscale, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithCmykColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Cmyk, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithYCbCrColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - - [Fact] - public void GetConverterReturnsCorrectConverterWithYcckColorSpace() - { - FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic); - - static void RunTest(string arg) - { - // arrange - Type expectedType = - typeof(JpegColorConverterBase.JpegColorConverter); - - // act - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.Ycck, 8); - Type actualType = converter.GetType(); - - // assert - Assert.Equal(expectedType, actualType); - } - } - /// - /// Verifies that TIFF color spaces resolve to their closed shared converter types. + /// Verifies that the converter factory closes the shared traversal over the matching color-model operator. /// - /// The TIFF JPEG color space. + /// The JPEG color space. /// The expected closed converter type. [Theory] + [InlineData( + JpegColorSpace.Grayscale, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.RGB, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.Cmyk, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.YCbCr, + typeof(JpegColorConverterBase.JpegColorConverter))] + [InlineData( + JpegColorSpace.Ycck, + typeof(JpegColorConverterBase.JpegColorConverter))] [InlineData( JpegColorSpace.TiffCmyk, typeof(JpegColorConverterBase.JpegColorConverter))] [InlineData( JpegColorSpace.TiffYccK, typeof(JpegColorConverterBase.JpegColorConverter))] - internal void GetConverterReturnsCorrectConverterWithTiffColorSpace(JpegColorSpace colorSpace, Type expectedType) + internal void GetConverterReturnsClosedOperatorConverter(JpegColorSpace colorSpace, Type expectedType) { JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); Assert.Equal(expectedType, converter.GetType()); } - [Theory] - [InlineData(JpegColorSpace.Grayscale, 1)] - [InlineData(JpegColorSpace.Ycck, 4)] - [InlineData(JpegColorSpace.Cmyk, 4)] - [InlineData(JpegColorSpace.RGB, 3)] - [InlineData(JpegColorSpace.YCbCr, 3)] - internal void ConvertToRgbWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) - { - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - ValidateConversionToRgb( - converter, - componentCount, - 1); - } - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YCbCrScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector512(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector256(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYCbCrVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YCbCrVector128(8), - 3, - seed, - new JpegColorConverterBase.YCbCrScalar(8), - precision: 2); - /// - /// Verifies YCbCr equivalence around every scalar and SIMD width boundary. + /// Verifies the replacement converter against independent definitions of the established JPEG color models. /// - /// The number of samples to convert. - /// The JPEG sample precision. - [Theory] - [InlineData(1, 8)] - [InlineData(3, 12)] - [InlineData(4, 8)] - [InlineData(7, 12)] - [InlineData(8, 8)] - [InlineData(15, 12)] - [InlineData(16, 8)] - [InlineData(31, 12)] - [InlineData(32, 8)] - [InlineData(40, 12)] - [InlineData(64, 8)] - [InlineData(128, 12)] - public void YCbCrOperatorMatchesScalarForAllVectorBoundaries(int length, int precision) - { - JpegColorConverterBase converter = - new JpegColorConverterBase.JpegColorConverter(precision); - JpegColorConverterBase baseline = new JpegColorConverterBase.YCbCrScalar(precision); - - ValidateConversionToRgb(converter, baseline, length, 3, precision); - ValidateConversionFromRgb(converter, baseline, length, 3, precision); - } - - /// - /// Verifies that the YCbCr operator retains scalar behavior when hardware intrinsics are disabled. - /// - [Fact] - public void YCbCrOperatorMatchesScalarWithoutHardwareIntrinsics() - => FeatureTestRunner.RunWithHwIntrinsicsFeature( - RunTest, - HwIntrinsics.DisableHWIntrinsic); - - /// - /// Verifies converter equivalence around every scalar and SIMD width boundary. - /// - /// The color space under test. - /// The number of component planes written by the converter. + /// The JPEG color space. + /// The number of component planes owned by the color model. [Theory] [InlineData(JpegColorSpace.Grayscale, 1)] [InlineData(JpegColorSpace.RGB, 3)] [InlineData(JpegColorSpace.Cmyk, 4)] + [InlineData(JpegColorSpace.YCbCr, 3)] [InlineData(JpegColorSpace.Ycck, 4)] - [InlineData(JpegColorSpace.TiffCmyk, 4)] - internal void OperatorsMatchScalarAcrossEveryWidthBoundary(JpegColorSpace colorSpace, int componentCount) + internal void ConvertToRgbMatchesColorModelDefinition(JpegColorSpace colorSpace, int componentCount) { + const int length = 128; + JpegColorConverterBase.ComponentValues source = CreateRandomValues(length, componentCount, 8); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, 8); JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(colorSpace, 8); - JpegColorConverterBase baseline = colorSpace switch - { - JpegColorSpace.Grayscale => new JpegColorConverterBase.GrayScaleScalar(8), - JpegColorSpace.RGB => new JpegColorConverterBase.RgbScalar(8), - JpegColorSpace.Cmyk => new JpegColorConverterBase.CmykScalar(8), - JpegColorSpace.Ycck => new JpegColorConverterBase.YccKScalar(8), - JpegColorSpace.TiffCmyk => new JpegColorConverterBase.TiffCmykScalar(8), - _ => throw new InvalidOperationException(), - }; - // These lengths exercise every point immediately below, at, and above the supported SIMD widths. - int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; + converter.ConvertToRgbInPlace(actual); - foreach (int length in lengths) + for (int i = 0; i < length; i++) { - ValidateConversionToRgb(converter, baseline, length, componentCount, 8); - ValidateConversionFromRgb(converter, baseline, length, componentCount, 8); + AssertColorModelDefinition(colorSpace, source, actual, i); } } /// - /// Verifies TIFF YCCK decoding around every scalar and SIMD width boundary. + /// Verifies the adaptive traversal against each operator's scalar definition around every SIMD boundary. /// + /// The JPEG color space. + /// The number of component planes owned by the color model. /// The JPEG sample precision. [Theory] - [InlineData(8)] - [InlineData(12)] - public void TiffYccKOperatorToRgbMatchesScalarAcrossEveryWidthBoundary(int precision) + [InlineData(JpegColorSpace.Grayscale, 1, 8)] + [InlineData(JpegColorSpace.Grayscale, 1, 12)] + [InlineData(JpegColorSpace.RGB, 3, 8)] + [InlineData(JpegColorSpace.RGB, 3, 12)] + [InlineData(JpegColorSpace.Cmyk, 4, 8)] + [InlineData(JpegColorSpace.Cmyk, 4, 12)] + [InlineData(JpegColorSpace.YCbCr, 3, 8)] + [InlineData(JpegColorSpace.YCbCr, 3, 12)] + [InlineData(JpegColorSpace.Ycck, 4, 8)] + [InlineData(JpegColorSpace.Ycck, 4, 12)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 8)] + [InlineData(JpegColorSpace.TiffCmyk, 4, 12)] + [InlineData(JpegColorSpace.TiffYccK, 4, 8)] + [InlineData(JpegColorSpace.TiffYccK, 4, 12)] + internal void OperatorTraversalMatchesScalarDefinition( + JpegColorSpace colorSpace, + int componentCount, + int precision) { - JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.TiffYccK, precision); - JpegColorConverterBase baseline = new JpegColorConverterBase.TiffYccKScalar(precision); - - // The combined lengths force scalar, 128-bit, 256-bit, and 512-bit work on capable hardware. - int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; - - foreach (int length in lengths) + switch (colorSpace) { - ValidateConversionToRgb(converter, baseline, length, 4, precision); + case JpegColorSpace.Grayscale: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.RGB: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.Cmyk: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.YCbCr: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.Ycck: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.TiffCmyk: + ValidateOperator(componentCount, precision); + break; + case JpegColorSpace.TiffYccK: + ValidateOperator(componentCount, precision); + break; + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + break; } } /// - /// Verifies TIFF YCCK encoding against the canonical normalized color-profile conversion. + /// Verifies that the shared converter retains its scalar behavior when hardware intrinsics are disabled. + /// + [Fact] + public void OperatorTraversalMatchesScalarWithoutHardwareIntrinsics() + => FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunWithoutHardwareIntrinsics, + HwIntrinsics.DisableHWIntrinsic); + + /// + /// Verifies TIFF YccK encoding against the canonical normalized color-profile conversion. /// [Fact] public void TiffYccKOperatorFromRgbMatchesColorProfileDefinition() @@ -381,7 +204,6 @@ public class JpegColorConverterTests const int maximumLength = 40; const float maximumValue = 255F; const float halfValue = 128F; - const float tolerance = 0.0001F; float[] rSeed = [0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 255, 0, 0, 127, 32, 240, 0, 255, 64, 192]; float[] gSeed = [0, 255, 0, 255, 0, 127, 160, 16, 0, 255, 0, 255, 0, 127, 160, 16, 255, 0, 128, 96]; float[] bSeed = [0, 255, 0, 0, 255, 127, 224, 80, 255, 0, 255, 0, 0, 127, 224, 80, 0, 255, 192, 32]; @@ -390,7 +212,7 @@ public class JpegColorConverterTests float[] b = new float[maximumLength]; JpegColorConverterBase converter = JpegColorConverterBase.GetConverter(JpegColorSpace.TiffYccK, 8); - // Repeating the color set provides enough lanes to exercise every SIMD width and each mixed-width tail. + // Repeating the color set provides enough lanes to exercise every SIMD width and mixed-width tail. rSeed.CopyTo(r, 0); rSeed.CopyTo(r, rSeed.Length); gSeed.CopyTo(g, 0); @@ -398,8 +220,6 @@ public class JpegColorConverterTests bSeed.CopyTo(b, 0); bSeed.CopyTo(b, bSeed.Length); - // The normalized color-profile implementation is the canonical definition; JPEG stores each result - // in the configured integer sample domain. ColorProfileConverter reference = new(); int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40]; @@ -418,659 +238,302 @@ public class JpegColorConverterTests Rgb rgb = new(r[i] / maximumValue, g[i] / maximumValue, b[i] / maximumValue); YccK expected = reference.Convert(rgb); - Assert.Equal(expected.Y * maximumValue, y[i], tolerance); + Assert.Equal(expected.Y * maximumValue, y[i], ToRgbTolerance); - // JPEG centers chroma on the integer sample midpoint (128 at 8-bit precision), whereas - // the color-profile definition centers normalized chroma exactly on 0.5. - Assert.Equal(halfValue + ((expected.Cb - 0.5F) * maximumValue), cb[i], tolerance); - Assert.Equal(halfValue + ((expected.Cr - 0.5F) * maximumValue), cr[i], tolerance); - Assert.Equal(expected.K * maximumValue, k[i], tolerance); + // JPEG centers chroma on the integer midpoint, while the color-profile definition uses exactly 0.5. + Assert.Equal(halfValue + ((expected.Cb - 0.5F) * maximumValue), cb[i], ToRgbTolerance); + Assert.Equal(halfValue + ((expected.Cr - 0.5F) * maximumValue), cr[i], ToRgbTolerance); + Assert.Equal(expected.K * maximumValue, k[i], ToRgbTolerance); } } } /// - /// Runs the YCbCr equivalence check in the feature-test process. + /// Runs the disabled-intrinsics scalar comparison inside the feature-test process. /// /// The unused feature-test argument. - private static void RunTest(string arg) + private static void RunWithoutHardwareIntrinsics(string arg) + => ValidateOperator(3, 8); + + /// + /// Checks one closed operator converter at every scalar and SIMD transition length. + /// + /// The color-model operator under test. + /// The number of component planes owned by the operator. + /// The JPEG sample precision. + private static void ValidateOperator(int componentCount, int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - const int length = 40; - const int precision = 8; JpegColorConverterBase converter = - new JpegColorConverterBase.JpegColorConverter(precision); - JpegColorConverterBase baseline = new JpegColorConverterBase.YCbCrScalar(precision); - - ValidateConversionToRgb(converter, baseline, length, 3, precision); - ValidateConversionFromRgb(converter, baseline, length, 3, precision); - } - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.CmykScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector512(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector256(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToCmykVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.CmykVector128(8), - 4, - seed, - new JpegColorConverterBase.CmykScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.GrayScaleScalar(8), 1, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayScaleVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector512(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector256(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToGrayScaleVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.GrayScaleVector128(8), - 1, - seed, - new JpegColorConverterBase.GrayScaleScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.RgbScalar(8), 3, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector512(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector256(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToRgbVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.RgbVector128(8), - 3, - seed, - new JpegColorConverterBase.RgbScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKBasic(int seed) => - this.TestConversionToRgb(new JpegColorConverterBase.YccKScalar(8), 4, seed); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector512(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector256(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromYccKVector128(int seed) => - this.TestConversionToRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8)); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector512(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector512(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector256(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector256(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); - - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbToYccKVector128(int seed) => - this.TestConversionFromRgb( - new JpegColorConverterBase.YccKVector128(8), - 4, - seed, - new JpegColorConverterBase.YccKScalar(8), - precision: 2); + new JpegColorConverterBase.JpegColorConverter(precision); + int[] lengths = [1, 3, 4, 7, 8, 15, 16, 31, 32, 40, 64, 128]; - private void TestConversionToRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) - { - if (!converter.IsAvailable) + // Adjacent values around 4/8/16 lanes verify every prefix, mixed-width tail, and scalar remainder. + foreach (int length in lengths) { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; + ValidateConversionToRgb(converter, length, componentCount, precision); + ValidateConversionFromRgb(converter, length, componentCount, precision); } - - ValidateConversionToRgb( - converter, - componentCount, - seed, - baseLineConverter); } - private void TestConversionFromRgb( + /// + /// Compares the adaptive component-to-RGB traversal with repeated scalar operator calls. + /// + /// The color-model operator under test. + /// The adaptive converter. + /// The number of samples to convert. + /// The number of source component planes. + /// The JPEG sample precision. + private static void ValidateConversionToRgb( JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision) - { - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } - - ValidateConversionFromRgb( - converter, - componentCount, - seed, - baseLineConverter, - precision); - } - - private static JpegColorConverterBase.ComponentValues CreateRandomValues( int length, int componentCount, - int seed) + int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - Random rnd = new(seed); + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + float maximumValue = MathF.Pow(2, precision) - 1; + float halfValue = MathF.Ceiling(maximumValue * 0.5F); + float scale = 1F / maximumValue; - Buffer2D[] buffers = new Buffer2D[componentCount]; - for (int i = 0; i < componentCount; i++) + for (int i = 0; i < length; i++) { - float[] values = new float[length]; + ref float c0 = ref expected.Component0[i]; + ref float c1 = ref expected.Component1[i]; + ref float c2 = ref expected.Component2[i]; + float c3 = componentCount == 4 ? expected.Component3[i] : 0; - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } - - // no need to dispose when buffer is not array owner - Memory memory = new(values); - MemoryGroup source = MemoryGroup.Wrap(memory); - buffers[i] = new Buffer2D(source, values.Length, 1); + TOperator.ConvertToRgb(ref c0, ref c1, ref c2, c3, maximumValue, halfValue, scale); } - return new JpegColorConverterBase.ComponentValues(buffers, 0); - } - - private static float[] CreateRandomValues(int length, Random rnd) - { - float[] values = new float[length]; - - for (int j = 0; j < values.Length; j++) - { - values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; - } + converter.ConvertToRgbInPlace(actual); - return values; + // YCbCr conversion rounds in the integer sample domain before normalization. Fused SIMD arithmetic can + // cross a half-way boundary differently from the scalar expression, so allow one source-sample quantum + // in addition to the ordinary floating-point tolerance at both supported sample precisions. + float tolerance = scale + ToRgbTolerance; + CompareSequence(expected.Component0, actual.Component0, tolerance); + CompareSequence(expected.Component1, actual.Component1, tolerance); + CompareSequence(expected.Component2, actual.Component2, tolerance); } - private static void ValidateConversionToRgb( + /// + /// Compares the adaptive RGB-to-component traversal with repeated scalar operator calls. + /// + /// The color-model operator under test. + /// The adaptive converter. + /// The number of samples to convert. + /// The number of destination component planes. + /// The JPEG sample precision. + private static void ValidateConversionFromRgb( JpegColorConverterBase converter, + int length, int componentCount, - int seed, - JpegColorConverterBase baseLineConverter = null) + int precision) + where TOperator : struct, JpegColorConverterBase.IJpegColorConverterOperator { - JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues actual = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); - - converter.ConvertToRgbInPlace(actual); - - for (int i = 0; i < TestBufferLength; i++) - { - Validate(converter.ColorSpace, original, actual, i); - } - - // Compare conversion result to a baseline, should be the scalar version. - if (baseLineConverter != null) + JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); + JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); + Random random = new(precision); + float[] r = CreateRandomValues(length, random); + float[] g = CreateRandomValues(length, random); + float[] b = CreateRandomValues(length, random); + float maximumValue = MathF.Pow(2, precision) - 1; + float halfValue = MathF.Ceiling(maximumValue * 0.5F); + float scale = 1F / maximumValue; + + for (int i = 0; i < length; i++) { - JpegColorConverterBase.ComponentValues expected = new( - original.ComponentCount, - original.Component0.ToArray(), - original.Component1.ToArray(), - original.Component2.ToArray(), - original.Component3.ToArray()); - baseLineConverter.ConvertToRgbInPlace(expected); - if (componentCount == 1) - { - Assert.True(expected.Component0.SequenceEqual(actual.Component0)); - } - - if (componentCount == 2) + TOperator.ConvertFromRgb( + r[i], + g[i], + b[i], + maximumValue, + halfValue, + scale, + out expected.Component0[i], + out float c1, + out float c2, + out float c3); + + if (componentCount >= 2) { - Assert.True(expected.Component1.SequenceEqual(actual.Component1)); + expected.Component1[i] = c1; } - if (componentCount == 3) + if (componentCount >= 3) { - Assert.True(expected.Component2.SequenceEqual(actual.Component2)); + expected.Component2[i] = c2; } if (componentCount == 4) { - Assert.True(expected.Component3.SequenceEqual(actual.Component3)); + expected.Component3[i] = c3; } } - } - private static void ValidateConversionFromRgb( - JpegColorConverterBase converter, - int componentCount, - int seed, - JpegColorConverterBase baseLineConverter, - int precision = 4) - { - // arrange - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(TestBufferLength, componentCount, seed); - Random rnd = new(seed); - float[] rLane = CreateRandomValues(TestBufferLength, rnd); - float[] gLane = CreateRandomValues(TestBufferLength, rnd); - float[] bLane = CreateRandomValues(TestBufferLength, rnd); - - // act - converter.ConvertFromRgb(actual, rLane, gLane, bLane); - baseLineConverter.ConvertFromRgb(expected, rLane, gLane, bLane); - - // assert - if (componentCount == 1) - { - CompareSequenceWithTolerance(expected.Component0, actual.Component0, precision); - } + converter.ConvertFromRgb(actual, r, g, b); + CompareSequence(expected.Component0, actual.Component0, FromRgbTolerance); - if (componentCount == 2) + if (componentCount >= 2) { - CompareSequenceWithTolerance(expected.Component1, actual.Component1, precision); + CompareSequence(expected.Component1, actual.Component1, FromRgbTolerance); } - if (componentCount == 3) + if (componentCount >= 3) { - CompareSequenceWithTolerance(expected.Component2, actual.Component2, precision); + CompareSequence(expected.Component2, actual.Component2, FromRgbTolerance); } if (componentCount == 4) { - CompareSequenceWithTolerance(expected.Component3, actual.Component3, precision); - } - } - - private static void CompareSequenceWithTolerance(Span expected, Span actual, int precision) - { - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i], precision: precision); - } - } - - /// - /// Compares two component planes using an absolute floating-point tolerance. - /// - /// The expected component values. - /// The actual component values. - /// The maximum permitted absolute difference. - private static void CompareSequenceWithTolerance(Span expected, Span actual, float tolerance) - { - for (int i = 0; i < expected.Length; i++) - { - Assert.Equal(expected[i], actual[i], tolerance); + CompareSequence(expected.Component3, actual.Component3, FromRgbTolerance); } } /// - /// Compares component-to-RGB conversion with a scalar reference implementation. + /// Creates deterministic component planes in the configured JPEG sample domain. /// - /// The shared converter under test. - /// The scalar reference converter. - /// The number of samples to convert. - /// The number of source component planes. - /// The JPEG sample precision. - private static void ValidateConversionToRgb( - JpegColorConverterBase converter, - JpegColorConverterBase baseline, + /// The number of samples in each plane. + /// The number of independent component planes. + /// The JPEG sample precision and deterministic random seed. + /// The generated component planes. + private static JpegColorConverterBase.ComponentValues CreateRandomValues( int length, int componentCount, int precision) { - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); - - baseline.ConvertToRgbInPlace(expected); - converter.ConvertToRgbInPlace(actual); + Random random = new(precision); + float maximumValue = MathF.Pow(2, precision) - 1; + float[] c0 = CreateRandomValues(length, random, maximumValue); + float[] c1 = componentCount >= 2 ? CreateRandomValues(length, random, maximumValue) : c0; + float[] c2 = componentCount >= 3 ? CreateRandomValues(length, random, maximumValue) : c0; + float[] c3 = componentCount == 4 ? CreateRandomValues(length, random, maximumValue) : []; - // SIMD multiply-add instructions can differ from the scalar expression by the final rounding bit. - CompareSequenceWithTolerance(expected.Component0, actual.Component0, 0.0001F); - CompareSequenceWithTolerance(expected.Component1, actual.Component1, 0.0001F); - CompareSequenceWithTolerance(expected.Component2, actual.Component2, 0.0001F); + return new JpegColorConverterBase.ComponentValues(componentCount, c0, c1, c2, c3); } /// - /// Compares RGB-to-component conversion with a scalar reference implementation. + /// Creates deterministic RGB samples in ImageSharp's byte-scaled encoder domain. /// - /// The shared converter under test. - /// The scalar reference converter. - /// The number of samples to convert. - /// The number of destination component planes. - /// The JPEG sample precision. - private static void ValidateConversionFromRgb( - JpegColorConverterBase converter, - JpegColorConverterBase baseline, - int length, - int componentCount, - int precision) - { - JpegColorConverterBase.ComponentValues expected = CreateRandomValues(length, componentCount, precision); - JpegColorConverterBase.ComponentValues actual = CreateRandomValues(length, componentCount, precision); - Random random = new(precision); - float[] rLane = CreateRandomValues(length, random); - float[] gLane = CreateRandomValues(length, random); - float[] bLane = CreateRandomValues(length, random); - - baseline.ConvertFromRgb(expected, rLane, gLane, bLane); - converter.ConvertFromRgb(actual, rLane, gLane, bLane); - - // The generic traversal must preserve every plane owned by the closed color model. - CompareSequenceWithTolerance(expected.Component0, actual.Component0, 2); + /// The number of samples. + /// The deterministic random source. + /// The generated samples. + private static float[] CreateRandomValues(int length, Random random) + => CreateRandomValues(length, random, 255F); - if (componentCount >= 2) - { - CompareSequenceWithTolerance(expected.Component1, actual.Component1, 2); - } + /// + /// Creates deterministic samples between zero and the supplied inclusive domain maximum. + /// + /// The number of samples. + /// The deterministic random source. + /// The upper bound of the sample domain. + /// The generated samples. + private static float[] CreateRandomValues(int length, Random random, float maximumValue) + { + float[] values = new float[length]; - if (componentCount >= 3) + for (int i = 0; i < values.Length; i++) { - CompareSequenceWithTolerance(expected.Component2, actual.Component2, 2); + values[i] = random.NextSingle() * maximumValue; } - if (componentCount >= 4) - { - CompareSequenceWithTolerance(expected.Component3, actual.Component3, 2); - } + return values; } - private static void Validate( + /// + /// Compares one converted sample with an independent definition of its JPEG color model. + /// + /// The JPEG color space. + /// The unmodified source component planes. + /// The converted RGB planes. + /// The sample index. + private static void AssertColorModelDefinition( JpegColorSpace colorSpace, - in JpegColorConverterBase.ComponentValues original, - in JpegColorConverterBase.ComponentValues result, - int i) + in JpegColorConverterBase.ComponentValues source, + in JpegColorConverterBase.ComponentValues actual, + int index) { + float c0 = source.Component0[index]; + float c1 = source.Component1[index]; + float c2 = source.Component2[index]; + float c3 = 0; + Rgb expected; + switch (colorSpace) { case JpegColorSpace.Grayscale: - ValidateGrayScale(original, result, i); + float luminance = c0 / MaxColorChannelValue; + expected = new Rgb(luminance, luminance, luminance); break; - case JpegColorSpace.Ycck: - ValidateYccK(original, result, i); + case JpegColorSpace.RGB: + expected = new Rgb( + c0 / MaxColorChannelValue, + c1 / MaxColorChannelValue, + c2 / MaxColorChannelValue); + break; case JpegColorSpace.Cmyk: - ValidateCmyk(original, result, i); - break; - case JpegColorSpace.RGB: - ValidateRgb(original, result, i); + c3 = source.Component3[index] / MaxColorChannelValue; + expected = new Rgb( + c0 * c3 / MaxColorChannelValue, + c1 * c3 / MaxColorChannelValue, + c2 * c3 / MaxColorChannelValue); + break; case JpegColorSpace.YCbCr: - ValidateYCbCr(original, result, i); - break; - default: - Assert.Fail($"Invalid Colorspace enum value: {colorSpace}."); - break; - } - } - - private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128; - float cr = values.Component2[i] - 128; - - float r = (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero); - float g = (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero); - float b = (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); - - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } - - private static void ValidateYccK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; - - float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - float g = (255F - (float)Math.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + c1 -= 128F; + c2 -= 128F; - r /= MaxColorChannelValue; - g /= MaxColorChannelValue; - b /= MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + // JPEG applies the BT.601 matrix in the integer sample domain and rounds before normalization. + expected = new Rgb( + MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, + MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero) / MaxColorChannelValue, + MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero) / MaxColorChannelValue); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } - - private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float r = values.Component0[i] / MaxColorChannelValue; - float g = values.Component1[i] / MaxColorChannelValue; - float b = values.Component2[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); - - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); + break; + case JpegColorSpace.Ycck: + c1 -= 128F; + c2 -= 128F; + c3 = source.Component3[index] / MaxColorChannelValue; - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); - } + // Adobe YccK reconstructs inverted RGB first, then applies the normalized black component. + expected = new Rgb( + (MaxColorChannelValue - MathF.Round(c0 + (1.402F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, + (MaxColorChannelValue - MathF.Round(c0 - (0.344136F * c1) - (0.714136F * c2), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue, + (MaxColorChannelValue - MathF.Round(c0 + (1.772F * c1), MidpointRounding.AwayFromZero)) * c3 / MaxColorChannelValue); - private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) - { - float y = values.Component0[i] / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(y, y, y)); + break; + default: + Assert.Fail($"Unexpected JPEG color space: {colorSpace}."); + return; + } - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component0[i], result.Component0[i])); + // Color-space comparison intentionally clamps both sides because JPEG reconstruction can overshoot + // the normalized RGB gamut and saturation belongs to the eventual pixel conversion. + Rgb clampedExpected = Rgb.Clamp(expected); + Rgb clampedActual = Rgb.Clamp( + new Rgb(actual.Component0[index], actual.Component1[index], actual.Component2[index])); - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + Assert.True( + ColorSpaceComparer.Equals(clampedExpected, clampedActual), + $"Colors {clampedExpected} and {clampedActual} are not equal at index {index}."); } - private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) + /// + /// Compares two component planes using an absolute floating-point tolerance. + /// + /// The scalar reference values. + /// The adaptive traversal values. + /// The maximum permitted absolute difference. + private static void CompareSequence(Span expected, Span actual, float tolerance) { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / MaxColorChannelValue; - - float r = c * k / MaxColorChannelValue; - float g = m * k / MaxColorChannelValue; - float b = y * k / MaxColorChannelValue; - Rgb expected = Rgb.Clamp(new Rgb(r, g, b)); + Assert.Equal(expected.Length, actual.Length); - Rgb actual = Rgb.Clamp(new Rgb(result.Component0[i], result.Component1[i], result.Component2[i])); - - bool equal = ColorSpaceComparer.Equals(expected, actual); - Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i], tolerance); + } } }