diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs index 8f04c9152..505e1ca22 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs @@ -65,8 +65,36 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters #endif } + protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + { +#if SUPPORTS_RUNTIME_INTRINSICS + 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 + var scale = Vector256.Create(1 / this.MaximumValue); + int n = values.Component0.Length / 8; + for (int 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 = Avx.Multiply(r, scale); + g = Avx.Multiply(g, scale); + b = Avx.Multiply(b, scale); + } +#endif + } + protected override void ConvertCore(in ComponentValues values, Span result) => FromRgbBasic.ConvertCore(values, result, this.MaximumValue); + + protected override void ConvertCoreInplace(in ComponentValues values) => + FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs index ddca3fe2f..497c943a3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { @@ -20,6 +21,48 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ConvertCore(values, result, this.MaximumValue); } + public override void ConvertToRgbInplace(in ComponentValues values) + { + ConvertCoreInplace(values, this.MaximumValue); + } + + internal static void ConvertCoreInplace(ComponentValues values, float maxValue) + { + // TODO: Optimize this + ConvertComponent(values.Component0, maxValue); + ConvertComponent(values.Component1, maxValue); + ConvertComponent(values.Component2, maxValue); + + static void ConvertComponent(Span values, float maxValue) + { + Span vecValues = MemoryMarshal.Cast(values); + + var scaleVector = new Vector4(1 / maxValue); + + for (int i = 0; i < vecValues.Length; i++) + { + vecValues[i] *= scaleVector; + } + + values = values.Slice(vecValues.Length * 4); + if (values.Length > 0) + { + float scaleValue = 1f / maxValue; + values[0] *= scaleValue; + + if (values.Length > 1) + { + values[1] *= scaleValue; + + if (values.Length > 2) + { + values[2] *= scaleValue; + } + } + } + } + } + internal static void ConvertCore(in ComponentValues values, Span result, float maxValue) { ReadOnlySpan rVals = values.Component0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs index 763064d1e..0db568217 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs @@ -60,8 +60,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } + protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + { + ref Vector rBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Vector gBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Vector bBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + + var scale = new Vector(1 / this.MaximumValue); + + // Walking 8 elements at one step: + int n = values.Component0.Length / 8; + for (int i = 0; i < n; i++) + { + ref Vector r = ref Unsafe.Add(ref rBase, i); + ref Vector g = ref Unsafe.Add(ref gBase, i); + ref Vector b = ref Unsafe.Add(ref bBase, i); + r *= scale; + g *= scale; + b *= scale; + } + } + protected override void ConvertCore(in ComponentValues values, Span result) => FromRgbBasic.ConvertCore(values, result, this.MaximumValue); + + protected override void ConvertCoreInplace(in ComponentValues values) => + FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs index 522be82c2..046416847 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs @@ -38,9 +38,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters this.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder)); } + public override void ConvertToRgbInplace(in ComponentValues values) + { + int length = values.Component0.Length; + int remainder = values.Component0.Length % this.vectorSize; + int simdCount = length - remainder; + if (simdCount > 0) + { + // This implementation is actually AVX specific. + // An AVX register is capable of storing 8 float-s. + if (!this.IsAvailable) + { + throw new InvalidOperationException( + "This converter can be used only on architecture having 256 byte floating point SIMD registers!"); + } + + this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); + } + + this.ConvertCoreInplace(values.Slice(simdCount, remainder)); + } + protected abstract void ConvertCoreVectorized(in ComponentValues values, Span result); + protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); + protected abstract void ConvertCore(in ComponentValues values, Span result); + + protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException(); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 2d24f01dd..4c07783d7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -82,6 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// The destination buffer of values public abstract void ConvertToRgba(in ComponentValues values, Span result); + public virtual void ConvertToRgbInplace(in ComponentValues values) => throw new NotImplementedException(); + /// /// Returns the s for all supported colorspaces and precisions. /// @@ -181,22 +183,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// The component 0 (eg. Y) /// - public readonly ReadOnlySpan Component0; + public readonly Span Component0; /// /// The component 1 (eg. Cb) /// - public readonly ReadOnlySpan Component1; + public readonly Span Component1; /// /// The component 2 (eg. Cr) /// - public readonly ReadOnlySpan Component2; + public readonly Span Component2; /// /// The component 4 /// - public readonly ReadOnlySpan Component3; + public readonly Span Component3; /// /// Initializes a new instance of the struct. @@ -226,12 +228,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } } - private ComponentValues( + internal ComponentValues( int componentCount, - ReadOnlySpan c0, - ReadOnlySpan c1, - ReadOnlySpan c2, - ReadOnlySpan c3) + Span c0, + Span c1, + Span c2, + Span c3) { this.ComponentCount = componentCount; this.Component0 = c0; @@ -242,10 +244,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters public ComponentValues Slice(int start, int length) { - ReadOnlySpan c0 = this.Component0.Slice(start, length); - ReadOnlySpan c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : ReadOnlySpan.Empty; - ReadOnlySpan c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : ReadOnlySpan.Empty; - ReadOnlySpan c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : ReadOnlySpan.Empty; + Span c0 = this.Component0.Slice(start, length); + Span c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : Span.Empty; + Span c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : Span.Empty; + Span c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : Span.Empty; return new ComponentValues(this.ComponentCount, c0, c1, c2, c3); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 5f0562146..e275bf50c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -416,39 +416,77 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int resultBufferLength, int seed) { - JpegColorConverter.ComponentValues values = CreateRandomValues(componentCount, inputBufferLength, seed); - var result = new Vector4[resultBufferLength]; + JpegColorConverter.ComponentValues original = CreateRandomValues(componentCount, inputBufferLength, seed); + JpegColorConverter.ComponentValues values = Copy(original); - converter.ConvertToRgba(values, result); + converter.ConvertToRgbInplace(values); for (int i = 0; i < resultBufferLength; i++) { - Validate(converter.ColorSpace, values, result, i); + Validate(converter.ColorSpace, original, values, i); + } + + static JpegColorConverter.ComponentValues Copy(JpegColorConverter.ComponentValues values) + { + Span c0 = values.Component0.ToArray(); + Span c1 = values.ComponentCount > 1 ? values.Component1.ToArray().AsSpan() : default; + Span c2 = values.ComponentCount > 2 ? values.Component2.ToArray().AsSpan() : default; + Span c3 = values.ComponentCount > 3 ? values.Component3.ToArray().AsSpan() : default; + return new JpegColorConverter.ComponentValues(values.ComponentCount, c0, c1, c2, c3); } } private static void Validate( JpegColorSpace colorSpace, - in JpegColorConverter.ComponentValues values, + in JpegColorConverter.ComponentValues original, Vector4[] result, int i) { switch (colorSpace) { case JpegColorSpace.Grayscale: - ValidateGrayScale(values, result, i); + ValidateGrayScale(original, result, i); break; case JpegColorSpace.Ycck: - ValidateCyyK(values, result, i); + ValidateCyyK(original, result, i); break; case JpegColorSpace.Cmyk: - ValidateCmyk(values, result, i); + ValidateCmyk(original, result, i); break; case JpegColorSpace.RGB: - ValidateRgb(values, result, i); + ValidateRgb(original, result, i); break; case JpegColorSpace.YCbCr: - ValidateYCbCr(values, result, i); + ValidateYCbCr(original, result, i); + break; + default: + Assert.True(false, $"Colorspace {colorSpace} not supported!"); + break; + } + } + + private static void Validate( + JpegColorSpace colorSpace, + in JpegColorConverter.ComponentValues original, + in JpegColorConverter.ComponentValues result, + int i) + { + switch (colorSpace) + { + case JpegColorSpace.Grayscale: + ValidateGrayScale(original, result, i); + break; + case JpegColorSpace.Ycck: + ValidateCyyK(original, result, i); + break; + case JpegColorSpace.Cmyk: + ValidateCmyk(original, result, i); + break; + case JpegColorSpace.RGB: + ValidateRgb(original, result, i); + break; + case JpegColorSpace.YCbCr: + ValidateYCbCr(original, result, i); break; default: Assert.True(false, $"Colorspace {colorSpace} not supported!"); @@ -471,6 +509,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(1, rgba.W); } + private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + { + float y = values.Component0[i]; + float cb = values.Component1[i]; + float cr = values.Component2[i]; + var ycbcr = new YCbCr(y, cb, cr); + + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + var expected = ColorSpaceConverter.ToRgb(ycbcr); + + Assert.Equal(expected, actual, ColorSpaceComparer); + } + private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) { var v = new Vector4(0, 0, 0, 1F); @@ -498,6 +549,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(1, rgba.W); } + private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues 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; + + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.Equal(expected, actual, ColorSpaceComparer); + } + private static void ValidateRgb(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) { float r = values.Component0[i]; @@ -511,6 +587,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(1, rgba.W); } + private static void ValidateRgb(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + { + float r = values.Component0[i]; + float g = values.Component1[i]; + float b = values.Component2[i]; + + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + var expected = new Rgb(r / 255F, g / 255F, b / 255F); + + Assert.Equal(expected, actual, ColorSpaceComparer); + } + private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) { float y = values.Component0[i]; @@ -522,6 +610,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(1, rgba.W); } + private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + { + float y = values.Component0[i]; + var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); + var expected = new Rgb(y / 255F, y / 255F, y / 255F); + + Assert.Equal(expected, actual, ColorSpaceComparer); + } + private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, Vector4[] result, int i) { var v = new Vector4(0, 0, 0, 1F); @@ -546,5 +643,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, ColorSpaceComparer); Assert.Equal(1, rgba.W); } + + private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues 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; + + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + var expected = new Rgb(v.X, v.Y, v.Z); + + Assert.Equal(expected, actual, ColorSpaceComparer); + } } }