diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 50513b289..8662a6192 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -42,9 +42,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [MemberData(nameof(CommonConversionData))] - public void ConvertFromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) + public void FromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) { - ValidateRgbToYCbCrConversion( + ValidateConversion( new JpegColorConverter.FromYCbCrBasic(8), 3, inputBufferLength, @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromYCbCrVector(int inputBufferLength, int resultBufferLength, int seed) { - ValidateRgbToYCbCrConversion( + ValidateConversion( new JpegColorConverter.FromYCbCrVector(8), 3, inputBufferLength, @@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - ValidateRgbToYCbCrConversion( + ValidateConversion( new JpegColorConverter.FromYCbCrVector8(8), 3, inputBufferLength, @@ -92,8 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - // JpegColorConverter.FromYCbCrSimdAvx2.LogPlz = s => this.Output.WriteLine(s); - ValidateRgbToYCbCrConversion( + ValidateConversion( new JpegColorConverter.FromYCbCrAvx2(8), 3, inputBufferLength, @@ -103,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [MemberData(nameof(CommonConversionData))] - public void ConvertFromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) + public void FromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) { ValidateConversion( JpegColorSpace.YCbCr, @@ -113,149 +112,269 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } - // Benchmark, for local execution only - // [Theory] - // [InlineData(false)] - // [InlineData(true)] - public void BenchmarkYCbCr(bool simd) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromCmykBasic(int inputBufferLength, int resultBufferLength, int seed) { - int count = 2053; - int times = 50000; - - JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); - var result = new Vector4[count]; + ValidateConversion( + new JpegColorConverter.FromCmykBasic(8), + 4, + inputBufferLength, + resultBufferLength, + seed); + } - JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrVector(8) : new JpegColorConverter.FromYCbCrBasic(8); + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromCmykVector8(int inputBufferLength, int resultBufferLength, int seed) + { + if (!SimdUtils.HasVector8) + { + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; + } - // Warm up: - converter.ConvertToRgba(values, result); + ValidateConversion( + new JpegColorConverter.FromCmykVector8(8), + 4, + inputBufferLength, + resultBufferLength, + seed); + } - using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}")) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromCmykAvx2(int inputBufferLength, int resultBufferLength, int seed) + { + if (!SimdUtils.HasAvx2) { - for (int i = 0; i < times; i++) - { - converter.ConvertToRgba(values, result); - } + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; } + + ValidateConversion( + new JpegColorConverter.FromCmykAvx2(8), + 4, + inputBufferLength, + resultBufferLength, + seed); } [Theory] [MemberData(nameof(CommonConversionData))] - public void ConvertFromCmyk(int inputBufferLength, int resultBufferLength, int seed) + public void FromCmyk_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) { - var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + ValidateConversion( + JpegColorSpace.Cmyk, + 4, + inputBufferLength, + resultBufferLength, + seed); + } - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Cmyk, 8); - JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); - var result = new Vector4[resultBufferLength]; + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromGrayscaleBasic(int inputBufferLength, int resultBufferLength, int seed) + { + ValidateConversion( + new JpegColorConverter.FromGrayscaleBasic(8), + 1, + inputBufferLength, + resultBufferLength, + seed); + } - converter.ConvertToRgba(values, result); + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromGrayscaleVector8(int inputBufferLength, int resultBufferLength, int seed) + { + if (!SimdUtils.HasVector8) + { + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; + } - for (int i = 0; i < resultBufferLength; i++) + ValidateConversion( + new JpegColorConverter.FromGrayscaleVector8(8), + 1, + inputBufferLength, + resultBufferLength, + seed); + } + + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromGrayscaleAvx2(int inputBufferLength, int resultBufferLength, int seed) + { + if (!SimdUtils.HasAvx2) { - float c = values.Component0[i]; - float m = values.Component1[i]; - float y = values.Component2[i]; - float k = values.Component3[i] / 255F; + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; + } - v.X = c * k; - v.Y = m * k; - v.Z = y * k; - v.W = 1F; + ValidateConversion( + new JpegColorConverter.FromGrayscaleAvx2(8), + 1, + inputBufferLength, + resultBufferLength, + seed); + } - v *= scale; + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromGraysacle_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) + { + ValidateConversion( + JpegColorSpace.Grayscale, + 1, + inputBufferLength, + resultBufferLength, + seed); + } - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(v.X, v.Y, v.Z); + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromRgbBasic(int inputBufferLength, int resultBufferLength, int seed) + { + ValidateConversion( + new JpegColorConverter.FromRgbBasic(8), + 3, + inputBufferLength, + resultBufferLength, + seed); + } - Assert.Equal(expected, actual, ColorSpaceComparer); - Assert.Equal(1, rgba.W); + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromRgbVector8(int inputBufferLength, int resultBufferLength, int seed) + { + if (!SimdUtils.HasVector8) + { + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; } + + ValidateConversion( + new JpegColorConverter.FromRgbVector8(8), + 3, + inputBufferLength, + resultBufferLength, + seed); } [Theory] [MemberData(nameof(CommonConversionData))] - public void ConvertFromGrayScale(int inputBufferLength, int resultBufferLength, int seed) + public void FromRgbAvx2(int inputBufferLength, int resultBufferLength, int seed) { - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Grayscale, 8); - JpegColorConverter.ComponentValues values = CreateRandomValues(1, inputBufferLength, seed); - var result = new Vector4[resultBufferLength]; + if (!SimdUtils.HasAvx2) + { + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; + } - converter.ConvertToRgba(values, result); + ValidateConversion( + new JpegColorConverter.FromRgbAvx2(8), + 3, + inputBufferLength, + resultBufferLength, + seed); + } - for (int i = 0; i < resultBufferLength; i++) - { - float y = values.Component0[i]; - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(y / 255F, y / 255F, y / 255F); + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromRgb_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) + { + ValidateConversion( + JpegColorSpace.RGB, + 3, + inputBufferLength, + resultBufferLength, + seed); + } - Assert.Equal(expected, actual, ColorSpaceComparer); - Assert.Equal(1, rgba.W); - } + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromYccKBasic(int inputBufferLength, int resultBufferLength, int seed) + { + ValidateConversion( + new JpegColorConverter.FromYccKBasic(8), + 4, + inputBufferLength, + resultBufferLength, + seed); } [Theory] [MemberData(nameof(CommonConversionData))] - public void ConvertFromRgb(int inputBufferLength, int resultBufferLength, int seed) + public void FromYccKVector8(int inputBufferLength, int resultBufferLength, int seed) { - var converter = JpegColorConverter.GetConverter(JpegColorSpace.RGB, 8); - JpegColorConverter.ComponentValues values = CreateRandomValues(3, inputBufferLength, seed); - var result = new Vector4[resultBufferLength]; + if (!SimdUtils.HasVector8) + { + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; + } - converter.ConvertToRgba(values, result); + ValidateConversion( + new JpegColorConverter.FromYccKVector8(8), + 4, + inputBufferLength, + resultBufferLength, + seed); + } - for (int i = 0; i < resultBufferLength; i++) + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromYccKAvx2(int inputBufferLength, int resultBufferLength, int seed) + { + if (!SimdUtils.HasAvx2) { - float r = values.Component0[i]; - float g = values.Component1[i]; - float b = values.Component2[i]; - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(r / 255F, g / 255F, b / 255F); - - Assert.Equal(expected, actual, ColorSpaceComparer); - Assert.Equal(1, rgba.W); + this.Output.WriteLine("No AVX2 present, skipping test!"); + return; } + + ValidateConversion( + new JpegColorConverter.FromYccKAvx2(8), + 4, + inputBufferLength, + resultBufferLength, + seed); } [Theory] [MemberData(nameof(CommonConversionData))] - public void ConvertFromYcck(int inputBufferLength, int resultBufferLength, int seed) + public void FromYcck_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) { - var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + ValidateConversion( + JpegColorSpace.Ycck, + 4, + inputBufferLength, + resultBufferLength, + seed); + } - var converter = JpegColorConverter.GetConverter(JpegColorSpace.Ycck, 8); - JpegColorConverter.ComponentValues values = CreateRandomValues(4, inputBufferLength, seed); - var result = new Vector4[resultBufferLength]; + // Benchmark, for local execution only + // [Theory] + // [InlineData(false)] + // [InlineData(true)] + public void BenchmarkYCbCr(bool simd) + { + int count = 2053; + int times = 50000; + JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); + var result = new Vector4[count]; + + JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrVector(8) : new JpegColorConverter.FromYCbCrBasic(8); + + // Warm up: converter.ConvertToRgba(values, result); - for (int i = 0; i < resultBufferLength; i++) + using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}")) { - float y = values.Component0[i]; - float cb = values.Component1[i] - 128F; - float cr = values.Component2[i] - 128F; - float k = values.Component3[i] / 255F; - - v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - v.Y = (255F - (float)Math.Round( - y - (0.344136F * cb) - (0.714136F * cr), - MidpointRounding.AwayFromZero)) * k; - v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - v.W = 1F; - - v *= scale; - - Vector4 rgba = result[i]; - var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); - var expected = new Rgb(v.X, v.Y, v.Z); - - Assert.Equal(expected, actual, ColorSpaceComparer); - Assert.Equal(1, rgba.W); + for (int i = 0; i < times; i++) + { + converter.ConvertToRgba(values, result); + } } } @@ -270,7 +389,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var buffers = new Buffer2D[componentCount]; for (int i = 0; i < componentCount; i++) { - float[] values = new float[inputBufferLength]; + var values = new float[inputBufferLength]; for (int j = 0; j < inputBufferLength; j++) { @@ -293,7 +412,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int resultBufferLength, int seed) { - ValidateRgbToYCbCrConversion( + ValidateConversion( JpegColorConverter.GetConverter(colorSpace, 8), componentCount, inputBufferLength, @@ -301,7 +420,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } - private static void ValidateRgbToYCbCrConversion( + private static void ValidateConversion( JpegColorConverter converter, int componentCount, int inputBufferLength, @@ -315,7 +434,36 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int i = 0; i < resultBufferLength; i++) { - ValidateYCbCr(values, result, i); + Validate(converter.ColorSpace, values, result, i); + } + } + + private static void Validate( + JpegColorSpace colorSpace, + in JpegColorConverter.ComponentValues values, + Vector4[] result, + int i) + { + switch (colorSpace) + { + case JpegColorSpace.Grayscale: + ValidateGrayScale(values, result, i); + break; + case JpegColorSpace.Ycck: + ValidateCyyK(values, result, i); + break; + case JpegColorSpace.Cmyk: + ValidateCmyk(values, result, i); + break; + case JpegColorSpace.RGB: + ValidateRgb(values, result, i); + break; + case JpegColorSpace.YCbCr: + ValidateYCbCr(values, result, i); + break; + default: + Assert.True(false, $"Colorspace {colorSpace} not supported!"); + break; } } @@ -333,5 +481,81 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, ColorSpaceComparer); Assert.Equal(1, rgba.W); } + + private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) + { + var v = new Vector4(0, 0, 0, 1F); + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + float y = values.Component0[i]; + float cb = values.Component1[i] - 128F; + float cr = values.Component2[i] - 128F; + float k = values.Component3[i] / 255F; + + v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + v.Y = (255F - (float)Math.Round( + y - (0.344136F * cb) - (0.714136F * cr), + MidpointRounding.AwayFromZero)) * k; + v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); + } + + private static void ValidateRgb(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) + { + float r = values.Component0[i]; + float g = values.Component1[i]; + float b = values.Component2[i]; + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(r / 255F, g / 255F, b / 255F); + + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); + } + + private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) + { + float y = values.Component0[i]; + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(y / 255F, y / 255F, y / 255F); + + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); + } + + private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) + { + var v = new Vector4(0, 0, 0, 1F); + var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + + float c = values.Component0[i]; + float m = values.Component1[i]; + float y = values.Component2[i]; + float k = values.Component3[i] / 255F; + + v.X = c * k; + v.Y = m * k; + v.Z = y * k; + v.W = 1F; + + v *= scale; + + Vector4 rgba = result[i]; + var actual = new Rgb(rgba.X, rgba.Y, rgba.Z); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.Equal(expected, actual, ColorSpaceComparer); + Assert.Equal(1, rgba.W); + } } }