diff --git a/shared-infrastructure b/shared-infrastructure index 3ad6e96a5..b7b9a2755 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 3ad6e96a5f900fecd134f0dbba937cb97c7fb94f +Subproject commit b7b9a2755e456a96acbf103494228226d92eddf3 diff --git a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs index 9a8b5f0a8..dc6c960aa 100644 --- a/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs +++ b/src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs @@ -5,6 +5,10 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.ColorSpaces.Companding { @@ -18,21 +22,83 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// public static class SRgbCompanding { + private const int Length = Scale + 2; // 256kb @ 16bit precision. + private const int Scale = (1 << 16) - 1; + + private static readonly Lazy LazyCompressTable = new Lazy( + () => + { + var result = new float[Length]; + + for (int i = 0; i < result.Length; i++) + { + double d = (double)i / Scale; + if (d <= (0.04045 / 12.92)) + { + d *= 12.92; + } + else + { + d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055; + } + + result[i] = (float)d; + } + + return result; + }, + true); + + private static readonly Lazy LazyExpandTable = new Lazy( + () => + { + var result = new float[Length]; + + for (int i = 0; i < result.Length; i++) + { + double d = (double)i / Scale; + if (d <= 0.04045) + { + d /= 12.92; + } + else + { + d = Math.Pow((d + 0.055) / 1.055, 2.4); + } + + result[i] = (float)d; + } + + return result; + }, + true); + + private static float[] ExpandTable => LazyExpandTable.Value; + + private static float[] CompressTable => LazyCompressTable.Value; + /// /// Expands the companded vectors to their linear equivalents with respect to the energy. /// /// The span of vectors. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Expand(Span vectors) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported && vectors.Length >= 2) { - Expand(ref vectorsStart); + CompandAvx2(vectors, ExpandTable); - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); + if (Numerics.Modulo2(vectors.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + Expand(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1))); + } + } + else +#endif + { + CompandScalar(vectors, ExpandTable); } } @@ -40,17 +106,24 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy. /// /// The span of vectors. - [MethodImpl(InliningOptions.ShortMethod)] - public static void Compress(Span vectors) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe void Compress(Span vectors) { - ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); - ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); - - while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd)) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported && vectors.Length >= 2) { - Compress(ref vectorsStart); + CompandAvx2(vectors, CompressTable); - vectorsStart = ref Unsafe.Add(ref vectorsStart, 1); + if (Numerics.Modulo2(vectors.Length) != 0) + { + // Vector4 fits neatly in pairs. Any overlap has to be equal to 1. + Compress(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1))); + } + } + else +#endif + { + CompandScalar(vectors, CompressTable); } } @@ -58,9 +131,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// Expands a companded vector to its linear equivalent with respect to the energy. /// /// The vector. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Expand(ref Vector4 vector) { + // Alpha is already a linear representation of opacity so we do not want to convert it. vector.X = Expand(vector.X); vector.Y = Expand(vector.Y); vector.Z = Expand(vector.Z); @@ -70,9 +144,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// Compresses an uncompanded vector (linear) to its nonlinear equivalent. /// /// The vector. - [MethodImpl(InliningOptions.ShortMethod)] + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Compress(ref Vector4 vector) { + // Alpha is already a linear representation of opacity so we do not want to convert it. vector.X = Compress(vector.X); vector.Y = Compress(vector.Y); vector.Z = Compress(vector.Z); @@ -83,15 +158,84 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding /// /// The channel value. /// The representing the linear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Expand(float channel) + => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); /// /// Compresses an uncompanded channel (linear) to its nonlinear equivalent. /// /// The channel value. /// The representing the nonlinear channel value. - [MethodImpl(InliningOptions.ShortMethod)] - public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Compress(float channel) + => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; + +#if SUPPORTS_RUNTIME_INTRINSICS + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void CompandAvx2(Span vectors, float[] table) + { + fixed (float* tablePointer = &table[0]) + { + var scale = Vector256.Create((float)Scale); + Vector256 zero = Vector256.Zero; + var offset = Vector256.Create(1); + + // Divide by 2 as 4 elements per Vector4 and 8 per Vector256 + ref Vector256 vectorsBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(vectors)); + ref Vector256 vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u)); + + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + { + Vector256 multiplied = Avx.Multiply(scale, vectorsBase); + multiplied = Avx.Min(Avx.Max(zero, multiplied), scale); + + Vector256 truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied); + Vector256 truncatedF = Avx.ConvertToVector256Single(truncated); + + Vector256 low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float)); + Vector256 high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float)); + + // Alpha is already a linear representation of opacity so we do not want to convert it. + Vector256 companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF)); + vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl); + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); + } + } + } +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static unsafe void CompandScalar(Span vectors, float[] table) + { + fixed (float* tablePointer = &table[0]) + { + Vector4 zero = Vector4.Zero; + var scale = new Vector4(Scale); + ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors); + ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, vectors.Length); + + while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast)) + { + Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale); + + float f0 = multiplied.X; + float f1 = multiplied.Y; + float f2 = multiplied.Z; + + uint i0 = (uint)f0; + uint i1 = (uint)f1; + uint i2 = (uint)f2; + + // Alpha is already a linear representation of opacity so we do not want to convert it. + vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0); + vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1); + vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2); + + vectorsBase = ref Unsafe.Add(ref vectorsBase, 1); + } + } + } } } diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index b9ccfafe0..610542237 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp internal static class Numerics { #if SUPPORTS_RUNTIME_INTRINSICS - private const int BlendAlphaControl = 0b_10_00_10_00; + public const int BlendAlphaControl = 0b_10_00_10_00; private const int ShuffleAlphaControl = 0b_11_11_11_11; #endif @@ -710,5 +710,43 @@ namespace SixLabors.ImageSharp } } } + +#if SUPPORTS_RUNTIME_INTRINSICS + + /// + /// Performs a linear interpolation between two values based on the given weighting. + /// + /// The first value. + /// The second value. + /// Values between 0 and 1 that indicates the weight of . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector256 Lerp( + in Vector256 value1, + in Vector256 value2, + in Vector256 amount) + { + Vector256 diff = Avx.Subtract(value2, value1); + if (Fma.IsSupported) + { + return Fma.MultiplyAdd(diff, amount, value1); + } + else + { + return Avx.Add(Avx.Multiply(diff, amount), value1); + } + } +#endif + + /// + /// Performs a linear interpolation between two values based on the given weighting. + /// + /// The first value. + /// The second value. + /// A value between 0 and 1 that indicates the weight of . + /// The . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Lerp(float value1, float value2, float amount) + => ((value2 - value1) * amount) + value1; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs similarity index 79% rename from src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs rename to src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs index 236eff27c..3c1a02c5a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs @@ -1,16 +1,17 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { /// /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. /// Methods to build the tables are based on libjpeg implementation. - /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// - internal unsafe struct RgbToYCbCrTables + internal unsafe struct RgbToYCbCrConverterLut { /// /// The red luminance table @@ -63,10 +64,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// Initializes the YCbCr tables /// - /// The initialized - public static RgbToYCbCrTables Create() + /// The initialized + public static RgbToYCbCrConverterLut Create() { - RgbToYCbCrTables tables = default; + RgbToYCbCrConverterLut tables = default; for (int i = 0; i <= 255; i++) { @@ -92,11 +93,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)! /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ConvertPixelInto( + private void ConvertPixelInto( int r, int g, int b, @@ -111,10 +111,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; - // float cr = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero); + // float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits; } + public void Convert(Span rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + { + ref Rgb24 rgbStart = ref rgbSpan[0]; + + for (int i = 0; i < 64; i++) + { + ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); + + this.ConvertPixelInto( + c.R, + c.G, + c.B, + ref yBlock, + ref cbBlock, + ref crBlock, + i); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Fix(float x) => (int)((x * (1L << ScaleBits)) + 0.5F); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs new file mode 100644 index 000000000..209cc3c6a --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs @@ -0,0 +1,120 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder +{ + internal static class RgbToYCbCrConverterVectorized + { + public static bool IsSupported + { + get + { +#if SUPPORTS_RUNTIME_INTRINSICS + return Avx2.IsSupported; +#else + return false; +#endif + } + } + +#if SUPPORTS_RUNTIME_INTRINSICS + private static ReadOnlySpan MoveFirst24BytesToSeparateLanes => new byte[] + { + 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, + 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 + }; + + private static ReadOnlySpan MoveLast24BytesToSeparateLanes => new byte[] + { + 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, + 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0 + }; + + private static ReadOnlySpan ExtractRgb => new byte[] + { + 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, + 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF + }; +#endif + + public static void Convert(ReadOnlySpan rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock) + { + Debug.Assert(IsSupported, "AVX2 is required to run this converter"); + +#if SUPPORTS_RUNTIME_INTRINSICS + var f0299 = Vector256.Create(0.299f); + var f0587 = Vector256.Create(0.587f); + var f0114 = Vector256.Create(0.114f); + var fn0168736 = Vector256.Create(-0.168736f); + var fn0331264 = Vector256.Create(-0.331264f); + var f128 = Vector256.Create(128f); + var fn0418688 = Vector256.Create(-0.418688f); + var fn0081312F = Vector256.Create(-0.081312F); + var f05 = Vector256.Create(0.5f); + var zero = Vector256.Create(0).AsByte(); + + ref Vector256 inRef = ref Unsafe.As>(ref MemoryMarshal.GetReference(rgbSpan)); + ref Vector256 destYRef = ref Unsafe.As>(ref yBlock); + ref Vector256 destCbRef = ref Unsafe.As>(ref cbBlock); + ref Vector256 destCrRef = ref Unsafe.As>(ref crBlock); + + var extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); + var extractRgbMask = Unsafe.As>(ref MemoryMarshal.GetReference(ExtractRgb)); + Vector256 rgb, rg, bx; + Vector256 r, g, b; + for (int i = 0; i < 7; i++) + { + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)(24 * i)).AsUInt32(), extractToLanesMask).AsByte(); + + rgb = Avx2.Shuffle(rgb, extractRgbMask); + + rg = Avx2.UnpackLow(rgb, zero); + bx = Avx2.UnpackHigh(rgb, zero); + + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); + + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Unsafe.Add(ref destYRef, i) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + + // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) + Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + + // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); + } + + extractToLanesMask = Unsafe.As>(ref MemoryMarshal.GetReference(MoveLast24BytesToSeparateLanes)); + rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)160).AsUInt32(), extractToLanesMask).AsByte(); + rgb = Avx2.Shuffle(rgb, extractRgbMask); + + rg = Avx2.UnpackLow(rgb, zero); + bx = Avx2.UnpackHigh(rgb, zero); + + r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32()); + g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); + b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); + + // (0.299F * r) + (0.587F * g) + (0.114F * b); + Unsafe.Add(ref destYRef, 7) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r); + + // 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) + Unsafe.Add(ref destCbRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); + + // 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)) + Unsafe.Add(ref destCrRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r)); +#endif + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs index 4d6186e22..8fcc63c6a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -33,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// /// The color conversion tables /// - private RgbToYCbCrTables colorTables; + private RgbToYCbCrConverterLut colorTables; /// /// Temporal 8x8 block to hold TPixel data @@ -48,7 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder public static YCbCrForwardConverter Create() { var result = default(YCbCrForwardConverter); - result.colorTables = RgbToYCbCrTables.Create(); + if (!RgbToYCbCrConverterVectorized.IsSupported) + { + // Avoid creating lookup tables, when vectorized converter is supported + result.colorTables = RgbToYCbCrConverterLut.Create(); + } + return result; } @@ -65,20 +69,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder ref Block8x8F yBlock = ref this.Y; ref Block8x8F cbBlock = ref this.Cb; ref Block8x8F crBlock = ref this.Cr; - ref Rgb24 rgbStart = ref rgbSpan[0]; - for (int i = 0; i < 64; i++) + if (RgbToYCbCrConverterVectorized.IsSupported) { - ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i); - - this.colorTables.ConvertPixelInto( - c.R, - c.G, - c.B, - ref yBlock, - ref cbBlock, - ref crBlock, - i); + RgbToYCbCrConverterVectorized.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); + } + else + { + this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock); } } } diff --git a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs index acc2725ce..7af266276 100644 --- a/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs +++ b/src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils } } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 6b64da1c9..700314f26 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -6,7 +6,6 @@ using System.Buffers; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -96,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); // Length of reduced palette + transparency. - ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors)); + ReadOnlyMemory result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, this.maxColors)); this.pixelMap = new EuclideanPixelMap(this.Configuration, result); this.palette = result; diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs index 35d1931d0..979206ad5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs @@ -4,6 +4,11 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Processing.Processors.Transforms { @@ -61,28 +66,99 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The weighted sum [MethodImpl(InliningOptions.ShortMethod)] public Vector4 Convolve(Span rowSpan) - { - return this.ConvolveCore(ref rowSpan[this.StartIndex]); - } + => this.ConvolveCore(ref rowSpan[this.StartIndex]); [MethodImpl(InliningOptions.ShortMethod)] public Vector4 ConvolveCore(ref Vector4 rowStartRef) { - ref float horizontalValues = ref Unsafe.AsRef(this.bufferPtr); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Fma.IsSupported) + { + float* bufferStart = this.bufferPtr; + float* bufferEnd = bufferStart + (this.Length & ~3); + Vector256 result256_0 = Vector256.Zero; + Vector256 result256_1 = Vector256.Zero; + ReadOnlySpan maskBytes = new byte[] + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + 1, 0, 0, 0, 1, 0, 0, 0, + }; + Vector256 mask = Unsafe.ReadUnaligned>(ref MemoryMarshal.GetReference(maskBytes)); - // Destination color components - Vector4 result = Vector4.Zero; + while (bufferStart < bufferEnd) + { + // It is important to use a single expression here so that the JIT will correctly use vfmadd231ps + // for the FMA operation, and execute it directly on the target register and reading directly from + // memory for the first parameter. This skips initializing a SIMD register, and an extra copy. + // The code below should compile in the following assembly on .NET 5 x64: + // + // vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _] + // vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b] + // vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0 + // + // For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212. + // Additionally, we're also unrolling two computations per each loop iterations to leverage the + // fact that most CPUs have two ports to schedule multiply operations for FMA instructions. + result256_0 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), + result256_0); - for (int i = 0; i < this.Length; i++) - { - float weight = Unsafe.Add(ref horizontalValues, i); + result256_1 = Fma.MultiplyAdd( + Unsafe.As>(ref Unsafe.Add(ref rowStartRef, 2)), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask), + result256_1); + + bufferStart += 4; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 4); + } + + result256_0 = Avx.Add(result256_0, result256_1); + + if ((this.Length & 3) >= 2) + { + result256_0 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask), + result256_0); + + bufferStart += 2; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 2); + } - // Vector4 v = offsetedRowSpan[i]; - Vector4 v = Unsafe.Add(ref rowStartRef, i); - result += v * weight; + Vector128 result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper()); + + if ((this.Length & 1) != 0) + { + result128 = Fma.MultiplyAdd( + Unsafe.As>(ref rowStartRef), + Vector128.Create(*bufferStart), + result128); + } + + return *(Vector4*)&result128; } + else +#endif + { + // Destination color components + Vector4 result = Vector4.Zero; + float* bufferStart = this.bufferPtr; + float* bufferEnd = this.bufferPtr + this.Length; + + while (bufferStart < bufferEnd) + { + // Vector4 v = offsetedRowSpan[i]; + result += rowStartRef * *bufferStart; - return result; + bufferStart++; + rowStartRef = ref Unsafe.Add(ref rowStartRef, 1); + } + + return result; + } } /// @@ -91,9 +167,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// [MethodImpl(InliningOptions.ShortMethod)] internal ResizeKernel AlterLeftValue(int left) - { - return new ResizeKernel(left, this.bufferPtr, this.Length); - } + => new ResizeKernel(left, this.bufferPtr, this.Length); internal void Fill(Span values) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs index 576f97a93..3d6900683 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs @@ -26,6 +26,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.DestinationHeight = size.Height; this.DestinationRectangle = rectangle; this.Compand = options.Compand; + this.PremultiplyAlpha = options.PremultiplyAlpha; } /// @@ -53,6 +54,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// public bool Compand { get; } + /// + /// Gets a value indicating whether to premultiply the alpha (if it exists) during the resize operation. + /// + public bool PremultiplyAlpha { get; } + /// public override ICloningImageProcessor CreatePixelSpecificCloningProcessor(Configuration configuration, Image source, Rectangle sourceRectangle) => new ResizeProcessor(configuration, this, source, sourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 3c033d89e..ec238608e 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -21,6 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly IResampler resampler; private readonly Rectangle destinationRectangle; private readonly bool compand; + private readonly bool premultiplyAlpha; private Image destination; public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image source, Rectangle sourceRectangle) @@ -30,6 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.destinationHeight = definition.DestinationHeight; this.destinationRectangle = definition.DestinationRectangle; this.resampler = definition.Sampler; + this.premultiplyAlpha = definition.PremultiplyAlpha; this.compand = definition.Compand; } @@ -60,6 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle sourceRectangle = this.SourceRectangle; Rectangle destinationRectangle = this.destinationRectangle; bool compand = this.compand; + bool premultiplyAlpha = this.premultiplyAlpha; // Handle resize dimensions identical to the original if (source.Width == destination.Width @@ -128,7 +131,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms sourceRectangle, destinationRectangle, interest, - compand); + compand, + premultiplyAlpha); } } @@ -159,6 +163,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms in operation); } + private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha) + { + if (premultiplyAlpha) + { + return PixelConversionModifiers.Premultiply.ApplyCompanding(compand); + } + else + { + return PixelConversionModifiers.None.ApplyCompanding(compand); + } + } + private static void ApplyResizeFrameTransform( Configuration configuration, ImageFrame source, @@ -168,10 +184,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle sourceRectangle, Rectangle destinationRectangle, Rectangle interest, - bool compand) + bool compand, + bool premultiplyAlpha) { - PixelConversionModifiers conversionModifiers = - PixelConversionModifiers.Premultiply.ApplyCompanding(compand); + PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha); Buffer2DRegion sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 764349884..e7207c7e6 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -105,14 +105,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public Span GetColumnSpan(int x, int startY) - { - return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); - } + => this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); public void Initialize() - { - this.CalculateFirstPassValues(this.currentWindow); - } + => this.CalculateFirstPassValues(this.currentWindow); public void FillDestinationPixels(RowInterval rowInterval, Buffer2D destination) { diff --git a/src/ImageSharp/Processing/ResizeOptions.cs b/src/ImageSharp/Processing/ResizeOptions.cs index bad8bd455..4b31998da 100644 --- a/src/ImageSharp/Processing/ResizeOptions.cs +++ b/src/ImageSharp/Processing/ResizeOptions.cs @@ -45,5 +45,11 @@ namespace SixLabors.ImageSharp.Processing /// Gets or sets the target rectangle to resize into. /// public Rectangle? TargetRectangle { get; set; } + + /// + /// Gets or sets a value indicating whether to premultiply + /// the alpha (if it exists) during the resize operation. + /// + public bool PremultiplyAlpha { get; set; } = true; } } diff --git a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs b/tests/ImageSharp.Benchmarks/BenchmarkBase.cs deleted file mode 100644 index 5573b1382..000000000 --- a/tests/ImageSharp.Benchmarks/BenchmarkBase.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Benchmarks -{ - /// - /// The image benchmark base class. - /// - public abstract class BenchmarkBase - { - /// - /// Initializes a new instance of the class. - /// - protected BenchmarkBase() - { - // Add Image Formats - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs index 58a97b65e..338f22156 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs @@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeBmp : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeBmp { private byte[] bmpBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [GlobalSetup] public void ReadImages() @@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] public SDSize BmpSystemDrawing() { - using (var memoryStream = new MemoryStream(this.bmpBytes)) - { - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.bmpBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; } [Benchmark(Description = "ImageSharp Bmp")] - public Size BmpCore() + public Size BmpImageSharp() { - using (var memoryStream = new MemoryStream(this.bmpBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return new Size(image.Width, image.Height); - } - } + using var memoryStream = new MemoryStream(this.bmpBytes); + using var image = Image.Load(memoryStream); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs index 3488d4405..6517bf3c4 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs @@ -6,12 +6,11 @@ using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreSize = SixLabors.ImageSharp.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeFilteredPng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeFilteredPng { private byte[] filter0; private byte[] filter1; @@ -30,44 +29,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } [Benchmark(Baseline = true, Description = "None-filtered PNG file")] - public CoreSize PngFilter0() - { - return LoadPng(this.filter0); - } + public Size PngFilter0() + => LoadPng(this.filter0); [Benchmark(Description = "Sub-filtered PNG file")] - public CoreSize PngFilter1() - { - return LoadPng(this.filter1); - } + public Size PngFilter1() + => LoadPng(this.filter1); [Benchmark(Description = "Up-filtered PNG file")] - public CoreSize PngFilter2() - { - return LoadPng(this.filter2); - } + public Size PngFilter2() + => LoadPng(this.filter2); [Benchmark(Description = "Average-filtered PNG file")] - public CoreSize PngFilter3() - { - return LoadPng(this.filter3); - } + public Size PngFilter3() + => LoadPng(this.filter3); [Benchmark(Description = "Paeth-filtered PNG file")] - public CoreSize PngFilter4() - { - return LoadPng(this.filter4); - } + public Size PngFilter4() + => LoadPng(this.filter4); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static CoreSize LoadPng(byte[] bytes) + private static Size LoadPng(byte[] bytes) { - using (var image = Image.Load(bytes)) - { - return image.Size(); - } + using var image = Image.Load(bytes); + return image.Size(); } - private static string TestImageFullPath(string path) => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + private static string TestImageFullPath(string path) + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs index f4cfddd88..3add96a68 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs @@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeGif : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeGif { private byte[] gifBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [GlobalSetup] public void ReadImages() @@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Gif")] public SDSize GifSystemDrawing() { - using (var memoryStream = new MemoryStream(this.gifBytes)) - { - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.gifBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; } [Benchmark(Description = "ImageSharp Gif")] - public Size GifCore() + public Size GifImageSharp() { - using (var memoryStream = new MemoryStream(this.gifBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return new Size(image.Width, image.Height); - } - } + using var memoryStream = new MemoryStream(this.gifBytes); + using var image = Image.Load(memoryStream); + return new Size(image.Width, image.Height); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs index 57ee308e7..66feb801f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs @@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodePng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodePng { private byte[] pngBytes; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [Params(TestImages.Png.Splash)] public string TestImage { get; set; } @@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Png")] public SDSize PngSystemDrawing() { - using (var memoryStream = new MemoryStream(this.pngBytes)) - { - using (var image = SDImage.FromStream(memoryStream)) - { - return image.Size; - } - } + using var memoryStream = new MemoryStream(this.pngBytes); + using var image = SDImage.FromStream(memoryStream); + return image.Size; } [Benchmark(Description = "ImageSharp Png")] - public Size PngCore() + public Size PngImageSharp() { - using (var memoryStream = new MemoryStream(this.pngBytes)) - { - using (var image = Image.Load(memoryStream)) - { - return image.Size(); - } - } + using var memoryStream = new MemoryStream(this.pngBytes); + using var image = Image.Load(memoryStream); + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs index 4695d7ca4..c73fcc17b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs @@ -5,7 +5,6 @@ using System.Buffers; using System.IO; using System.Threading; using BenchmarkDotNet.Attributes; - using ImageMagick; using Pfim; using SixLabors.ImageSharp.Formats.Tga; @@ -14,8 +13,8 @@ using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class DecodeTga : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class DecodeTga { private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); @@ -28,36 +27,28 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [GlobalSetup] public void SetupData() - { - this.data = File.ReadAllBytes(this.TestImageFullPath); - } + => this.data = File.ReadAllBytes(this.TestImageFullPath); [Benchmark(Baseline = true, Description = "ImageMagick Tga")] public int TgaImageMagick() { var settings = new MagickReadSettings { Format = MagickFormat.Tga }; - using (var image = new MagickImage(new MemoryStream(this.data), settings)) - { - return image.Width; - } + using var image = new MagickImage(new MemoryStream(this.data), settings); + return image.Width; } [Benchmark(Description = "ImageSharp Tga")] - public int TgaCore() + public int TgaImageSharp() { - using (var image = Image.Load(this.data, new TgaDecoder())) - { - return image.Width; - } + using var image = Image.Load(this.data, new TgaDecoder()); + return image.Width; } [Benchmark(Description = "Pfim Tga")] public int TgaPfim() { - using (var image = Targa.Create(this.data, this.pfimConfig)) - { - return image.Width; - } + using var image = Targa.Create(this.data, this.pfimConfig); + return image.Width; } private class PfimAllocator : IImageAllocator @@ -65,10 +56,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs private int rented; private readonly ArrayPool shared = ArrayPool.Shared; - public byte[] Rent(int size) - { - return this.shared.Rent(size); - } + public byte[] Rent(int size) => this.shared.Rent(size); public void Return(byte[] data) { diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs index c816aee2e..c9665457f 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.Drawing.Imaging; @@ -10,8 +10,8 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodeBmp : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeBmp { private Stream bmpStream; private SDImage bmpDrawing; @@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -40,19 +41,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Bmp")] public void BmpSystemDrawing() { - using (var memoryStream = new MemoryStream()) - { - this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); - } + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); } [Benchmark(Description = "ImageSharp Bmp")] - public void BmpCore() + public void BmpImageSharp() { - using (var memoryStream = new MemoryStream()) - { - this.bmpCore.SaveAsBmp(memoryStream); - } + using var memoryStream = new MemoryStream(); + this.bmpCore.SaveAsBmp(memoryStream); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs index a7ffbe46e..60dbdd666 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs @@ -8,29 +8,25 @@ using SixLabors.ImageSharp.Formats.Bmp; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] public void EncodeBmpImageSharp() - { - this.ForEachImageSharpImage((img, ms) => + => this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new BmpEncoder()); return null; }); - } [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] public void EncodeBmpSystemDrawing() - { - this.ForEachSystemDrawingImage((img, ms) => + => this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Bmp); return null; }); - } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs index b6ce67bfd..26d5c4bc1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs @@ -13,8 +13,8 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodeGif : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeGif { // System.Drawing needs this. private Stream bmpStream; @@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -53,19 +54,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Gif")] public void GifSystemDrawing() { - using (var memoryStream = new MemoryStream()) - { - this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); - } + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); } [Benchmark(Description = "ImageSharp Gif")] - public void GifCore() + public void GifImageSharp() { - using (var memoryStream = new MemoryStream()) - { - this.bmpCore.SaveAsGif(memoryStream, this.encoder); - } + using var memoryStream = new MemoryStream(); + this.bmpCore.SaveAsGif(memoryStream, this.encoder); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs index 179e6946a..6fea9315a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { [Params(InputImageCategory.AllImages)] @@ -20,8 +20,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Description = "EncodeGifMultiple - ImageSharp")] public void EncodeGifImageSharp() - { - this.ForEachImageSharpImage((img, ms) => + => this.ForEachImageSharpImage((img, ms) => { // Try to get as close to System.Drawing's output as possible var options = new GifEncoder @@ -32,16 +31,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs img.Save(ms, options); return null; }); - } [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] public void EncodeGifSystemDrawing() - { - this.ForEachSystemDrawingImage((img, ms) => + => this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Gif); return null; }); - } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs index b3113e6d7..de8d41202 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -8,15 +8,15 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests; -using CoreImage = SixLabors.ImageSharp.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { /// - /// Benchmarks saving png files using different quantizers. System.Drawing cannot save indexed png files so we cannot compare. + /// Benchmarks saving png files using different quantizers. + /// System.Drawing cannot save indexed png files so we cannot compare. /// - [Config(typeof(Config.ShortClr))] - public class EncodeIndexedPng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeIndexedPng { // System.Drawing needs this. private Stream bmpStream; @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs if (this.bmpStream == null) { this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); - this.bmpCore = CoreImage.Load(this.bmpStream); + this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; } } @@ -37,67 +37,56 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); } [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] public void PngCoreOctree() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Octree NoDither Png")] public void PngCoreOctreeNoDither() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Palette Png")] public void PngCorePalette() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Palette NoDither Png")] public void PngCorePaletteNoDither() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Wu Png")] public void PngCoreWu() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; + this.bmpCore.SaveAsPng(memoryStream, options); } [Benchmark(Description = "ImageSharp Wu NoDither Png")] public void PngCoreWuNoDither() { - using (var memoryStream = new MemoryStream()) - { - var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; - this.bmpCore.SaveAsPng(memoryStream, options); - } + using var memoryStream = new MemoryStream(); + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; + this.bmpCore.SaveAsPng(memoryStream, options); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs index 81b884b75..c628b7109 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs @@ -11,8 +11,8 @@ using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodePng : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodePng { // System.Drawing needs this. private Stream bmpStream; @@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -46,20 +47,16 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs [Benchmark(Baseline = true, Description = "System.Drawing Png")] public void PngSystemDrawing() { - using (var memoryStream = new MemoryStream()) - { - this.bmpDrawing.Save(memoryStream, ImageFormat.Png); - } + using var memoryStream = new MemoryStream(); + this.bmpDrawing.Save(memoryStream, ImageFormat.Png); } [Benchmark(Description = "ImageSharp Png")] public void PngCore() { - using (var memoryStream = new MemoryStream()) - { - var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; - this.bmpCore.SaveAsPng(memoryStream, encoder); - } + using var memoryStream = new MemoryStream(); + var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; + this.bmpCore.SaveAsPng(memoryStream, encoder); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs index 37cfa314c..bcb015e57 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs @@ -2,23 +2,21 @@ // Licensed under the Apache License, Version 2.0. using System.IO; - using BenchmarkDotNet.Attributes; - using ImageMagick; - using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs { - [Config(typeof(Config.ShortClr))] - public class EncodeTga : BenchmarkBase + [Config(typeof(Config.ShortMultiFramework))] + public class EncodeTga { private MagickImage tgaMagick; private Image tgaCore; - private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); + private string TestImageFullPath + => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); [Params(TestImages.Tga.Bit24BottomLeft)] public string TestImage { get; set; } @@ -33,22 +31,26 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } + [GlobalCleanup] + public void Cleanup() + { + this.tgaCore.Dispose(); + this.tgaCore = null; + this.tgaMagick.Dispose(); + } + [Benchmark(Baseline = true, Description = "Magick Tga")] - public void BmpSystemDrawing() + public void BmpImageMagick() { - using (var memoryStream = new MemoryStream()) - { - this.tgaMagick.Write(memoryStream, MagickFormat.Tga); - } + using var memoryStream = new MemoryStream(); + this.tgaMagick.Write(memoryStream, MagickFormat.Tga); } [Benchmark(Description = "ImageSharp Tga")] - public void BmpCore() + public void BmpImageSharp() { - using (var memoryStream = new MemoryStream()) - { - this.tgaCore.SaveAsBmp(memoryStream); - } + using var memoryStream = new MemoryStream(); + this.tgaCore.SaveAsBmp(memoryStream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs deleted file mode 100644 index 197f0804d..000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs -{ - public class GetSetPixel : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] - public System.Drawing.Color ResizeSystemDrawing() - { - using (var source = new Bitmap(400, 400)) - { - source.SetPixel(200, 200, System.Drawing.Color.White); - return source.GetPixel(200, 200); - } - } - - [Benchmark(Description = "ImageSharp GetSet pixel")] - public Rgba32 ResizeCore() - { - using (var image = new Image(400, 400)) - { - image[200, 200] = Color.White; - return image[200, 200]; - } - } - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs index 1e6b9fe92..d17882adf 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class CmykColorConversion : ColorConversionBenchmark { public CmykColorConversion() @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromCmykBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromCmykVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromCmykAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs index da8c51735..e93ad474b 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -11,27 +11,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public abstract class ColorConversionBenchmark { private readonly int componentCount; - protected Buffer2D[] input; - protected Vector4[] output; + + public const int Count = 128; protected ColorConversionBenchmark(int componentCount) - { - this.componentCount = componentCount; - } + => this.componentCount = componentCount; - public const int Count = 128; + protected Buffer2D[] Input { get; private set; } + + protected Vector4[] Output { get; private set; } [GlobalSetup] public void Setup() { - this.input = CreateRandomValues(this.componentCount, Count); - this.output = new Vector4[Count]; + this.Input = CreateRandomValues(this.componentCount, Count); + this.Output = new Vector4[Count]; } [GlobalCleanup] public void Cleanup() { - foreach (Buffer2D buffer in this.input) + foreach (Buffer2D buffer in this.Input) { buffer.Dispose(); } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs index 0e68af87a..68a102e3c 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs @@ -3,7 +3,6 @@ using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Tests; @@ -11,7 +10,7 @@ using SDSize = System.Drawing.Size; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpegParseStreamOnly { [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] @@ -23,9 +22,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [GlobalSetup] public void Setup() - { - this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); - } + => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath); [Benchmark(Baseline = true, Description = "System.Drawing FULL")] public SDSize JpegSystemDrawing() diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs index b92a66ebd..cabc0ed91 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs @@ -2,22 +2,20 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; - using SDImage = System.Drawing.Image; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { /// - /// An expensive Jpeg benchmark, running on a wide range of input images, showing aggregate results. + /// An expensive Jpeg benchmark, running on a wide range of input images, + /// showing aggregate results. /// - [Config(typeof(MultiImageBenchmarkBase.Config))] + [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase { protected override IEnumerable InputImageSubfoldersOrFiles => @@ -35,14 +33,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public void ImageSharp() - { - this.ForEachStream(ms => Image.Load(ms, new JpegDecoder())); - } + => this.ForEachStream(ms => Image.Load(ms, new JpegDecoder())); [Benchmark(Baseline = true)] public void SystemDrawing() - { - this.ForEachStream(SDImage.FromStream); - } + => this.ForEachStream(SDImage.FromStream); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs index 65866e8ba..b0ac1c0fc 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs @@ -3,11 +3,6 @@ using System.IO; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Environments; -using BenchmarkDotNet.Jobs; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; @@ -20,22 +15,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg /// /// Image-specific Jpeg benchmarks /// - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class DecodeJpeg_ImageSpecific { - public class Config : ManualConfig - { - public Config() => this.AddDiagnoser(MemoryDiagnoser.Default); - - public class ShortClr : Benchmarks.Config - { - public ShortClr() => - - // Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3), - this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3)); - } - } - private byte[] jpegBytes; private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs index 4c326fb3a..bfbd150fe 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs @@ -1,23 +1,20 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Drawing.Imaging; +using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - using System.Drawing; - using System.Drawing.Imaging; - using System.IO; - using SixLabors.ImageSharp.Tests; - using CoreImage = SixLabors.ImageSharp.Image; - - public class EncodeJpeg : BenchmarkBase + public class EncodeJpeg { // System.Drawing needs this. private Stream bmpStream; - private Image bmpDrawing; + private SDImage bmpDrawing; private Image bmpCore; [GlobalSetup] @@ -27,9 +24,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { const string TestImage = TestImages.Bmp.Car; this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); - this.bmpCore = CoreImage.Load(this.bmpStream); + this.bmpCore = Image.Load(this.bmpStream); this.bmpStream.Position = 0; - this.bmpDrawing = Image.FromStream(this.bmpStream); + this.bmpDrawing = SDImage.FromStream(this.bmpStream); } } @@ -37,6 +34,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg public void Cleanup() { this.bmpStream.Dispose(); + this.bmpStream = null; this.bmpCore.Dispose(); this.bmpDrawing.Dispose(); } @@ -44,19 +42,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] public void JpegSystemDrawing() { - using (var stream = new MemoryStream()) - { - this.bmpDrawing.Save(stream, ImageFormat.Jpeg); - } + using var stream = new MemoryStream(); + this.bmpDrawing.Save(stream, ImageFormat.Jpeg); } [Benchmark(Description = "ImageSharp Jpeg")] public void JpegCore() { - using (var stream = new MemoryStream()) - { - this.bmpCore.SaveAsJpeg(stream); - } + using var stream = new MemoryStream(); + this.bmpCore.SaveAsJpeg(stream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs index 14b240339..71276597a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Jpeg; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] // It's long enough to iterate through multiple files + [Config(typeof(Config.ShortMultiFramework))] public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded { protected override IEnumerable InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; @@ -17,22 +17,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] public void EncodeJpegImageSharp() - { - this.ForEachImageSharpImage((img, ms) => + => this.ForEachImageSharpImage((img, ms) => { img.Save(ms, new JpegEncoder()); return null; }); - } [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] public void EncodeJpegSystemDrawing() - { - this.ForEachSystemDrawingImage((img, ms) => + => this.ForEachSystemDrawingImage((img, ms) => { img.Save(ms, ImageFormat.Jpeg); return null; }); - } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs index 74b3e6db6..1eba1571d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class GrayscaleColorConversion : ColorConversionBenchmark { public GrayscaleColorConversion() @@ -17,17 +17,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs index 43d4ccf07..aae144ce0 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs @@ -1,15 +1,14 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class IdentifyJpeg { private byte[] jpegBytes; @@ -31,11 +30,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public IImageInfo Identify() { - using (var memoryStream = new MemoryStream(this.jpegBytes)) - { - var decoder = new JpegDecoder(); - return decoder.Identify(Configuration.Default, memoryStream); - } + using var memoryStream = new MemoryStream(this.jpegBytes); + var decoder = new JpegDecoder(); + return decoder.Identify(Configuration.Default, memoryStream); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs index 2edc3e7af..731850c43 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs @@ -6,9 +6,7 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; @@ -17,7 +15,7 @@ using SixLabors.ImageSharp.Tests; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(MultiImageBenchmarkBase.Config))] + [Config(typeof(Config.ShortMultiFramework))] public class LoadResizeSave_Aggregate : MultiImageBenchmarkBase { protected override IEnumerable InputImageSubfoldersOrFiles => @@ -48,49 +46,43 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void SystemDrawing() - { - this.ForEachStream( + => this.ForEachStream( sourceStream => + { + using (var destStream = new MemoryStream(this.destBytes)) + using (var source = System.Drawing.Image.FromStream(sourceStream)) + using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) { - using (var destStream = new MemoryStream(this.destBytes)) - using (var source = System.Drawing.Image.FromStream(sourceStream)) - using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) + using (var g = Graphics.FromImage(destination)) { - using (var g = Graphics.FromImage(destination)) - { - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - g.DrawImage(source, 0, 0, 400, 400); - } - - destination.Save(destStream, ImageFormat.Jpeg); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.DrawImage(source, 0, 0, 400, 400); } - return null; - }); - } + destination.Save(destStream, ImageFormat.Jpeg); + } + + return null; + }); [Benchmark] public void ImageSharp() - { - this.ForEachStream( + => this.ForEachStream( sourceStream => + { + using (var source = Image.Load( + this.configuration, + sourceStream, + new JpegDecoder { IgnoreMetadata = true })) { - using (var source = Image.Load( - this.configuration, - sourceStream, - new JpegDecoder { IgnoreMetadata = true })) - { - using (var destStream = new MemoryStream(this.destBytes)) - { - source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); - source.SaveAsJpeg(destStream); - } - } + using var destStream = new MemoryStream(this.destBytes); + source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); + source.SaveAsJpeg(destStream); + } - return null; - }); - } + return null; + }); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs index b46e74777..cfa39bd5a 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs @@ -7,17 +7,15 @@ using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; - using SDImage = System.Drawing.Image; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class LoadResizeSave_ImageSpecific { private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); @@ -32,6 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] + public string TestImage { get; set; } [Params(false, true)] @@ -51,28 +50,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void SystemDrawing() { - using (var sourceStream = new MemoryStream(this.sourceBytes)) - using (var destStream = new MemoryStream(this.destBytes)) - using (var source = SDImage.FromStream(sourceStream)) - using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) + using var sourceStream = new MemoryStream(this.sourceBytes); + using var destStream = new MemoryStream(this.destBytes); + using var source = SDImage.FromStream(sourceStream); + using var destination = new Bitmap(source.Width / 4, source.Height / 4); + using (var g = Graphics.FromImage(destination)) { - using (var g = Graphics.FromImage(destination)) - { - g.InterpolationMode = InterpolationMode.HighQualityBicubic; - g.PixelOffsetMode = PixelOffsetMode.HighQuality; - g.CompositingQuality = CompositingQuality.HighQuality; - g.DrawImage(source, 0, 0, 400, 400); - } - - destination.Save(destStream, ImageFormat.Jpeg); + g.InterpolationMode = InterpolationMode.HighQualityBicubic; + g.PixelOffsetMode = PixelOffsetMode.HighQuality; + g.CompositingQuality = CompositingQuality.HighQuality; + g.DrawImage(source, 0, 0, 400, 400); } + + destination.Save(destStream, ImageFormat.Jpeg); } [Benchmark] public void ImageSharp() { - var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true }); - using (source) + using (var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true })) using (var destStream = new MemoryStream(this.destBytes)) { source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs index 68dc0f04e..c1598be04 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class RgbColorConversion : ColorConversionBenchmark { public RgbColorConversion() @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromRgbBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromRgbVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromRgbAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs index 94b28e4d9..6c5732c99 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs @@ -2,12 +2,11 @@ // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class YCbCrColorConversion : ColorConversionBenchmark { public YCbCrColorConversion() @@ -18,33 +17,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgba(values, this.Output); } [Benchmark(Baseline = true)] public void SimdVector() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs index ed8758131..0a9bdb8fd 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class YccKColorConverter : ColorConversionBenchmark { public YccKColorConverter() @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKBasic(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYccKBasic(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKVector8(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYccKVector8(8).ConvertToRgba(values, this.Output); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.input, 0); + var values = new JpegColorConverter.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKAvx2(8).ConvertToRgba(values, this.output); + new JpegColorConverter.FromYccKAvx2(8).ConvertToRgba(values, this.Output); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs index c2f6eab3c..b7e1e8ddb 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs @@ -8,33 +8,18 @@ using System.IO; using System.Linq; using System.Numerics; using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Environments; -using BenchmarkDotNet.Jobs; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests; -using CoreImage = SixLabors.ImageSharp.Image; namespace SixLabors.ImageSharp.Benchmarks.Codecs { public abstract class MultiImageBenchmarkBase { - public class Config : ManualConfig - { - public Config() => this.AddDiagnoser(MemoryDiagnoser.Default); - - public class ShortClr : Benchmarks.Config - { - public ShortClr() => this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2)); - } - } - protected Dictionary FileNamesToBytes { get; set; } = new Dictionary(); protected Dictionary> FileNamesToImageSharpImages { get; set; } = new Dictionary>(); - protected Dictionary FileNamesToSystemDrawingImages { get; set; } = new Dictionary(); + protected Dictionary FileNamesToSystemDrawingImages { get; set; } = new Dictionary(); /// /// The values of this enum separate input files into categories. @@ -72,7 +57,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs /// /// Gets folders containing files OR files to be processed by the benchmark. /// - protected IEnumerable AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); + protected IEnumerable AllFoldersOrFiles + => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f)); /// /// Gets the large image threshold. @@ -83,19 +69,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected IEnumerable> EnumeratePairsByBenchmarkSettings( Dictionary input, Predicate checkIfSmall) - { - switch (this.InputCategory) + => this.InputCategory switch { - case InputImageCategory.AllImages: - return input; - case InputImageCategory.SmallImagesOnly: - return input.Where(kv => checkIfSmall(kv.Value)); - case InputImageCategory.LargeImagesOnly: - return input.Where(kv => !checkIfSmall(kv.Value)); - default: - throw new ArgumentOutOfRangeException(); - } - } + InputImageCategory.AllImages => input, + InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)), + InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)), + _ => throw new ArgumentOutOfRangeException(), + }; protected IEnumerable> FileNames2Bytes => @@ -150,17 +130,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs { foreach (KeyValuePair kv in this.FileNames2Bytes) { - using (var memoryStream = new MemoryStream(kv.Value)) + using var memoryStream = new MemoryStream(kv.Value); + try { - try - { - object obj = operation(memoryStream); - (obj as IDisposable)?.Dispose(); - } - catch (Exception ex) - { - Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); - } + object obj = operation(memoryStream); + (obj as IDisposable)?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}"); } } } @@ -178,7 +156,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs using (var ms1 = new MemoryStream(bytes)) { - this.FileNamesToImageSharpImages[fn] = CoreImage.Load(ms1); + this.FileNamesToImageSharpImages[fn] = Image.Load(ms1); } this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes)); @@ -191,7 +169,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs this.FileNamesToImageSharpImages, img => img.Width * img.Height < this.LargeImageThresholdInPixels); - protected IEnumerable> FileNames2SystemDrawingImages + protected IEnumerable> FileNames2SystemDrawingImages => this.EnumeratePairsByBenchmarkSettings( this.FileNamesToSystemDrawingImages, @@ -217,22 +195,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs protected void ForEachImageSharpImage(Func, MemoryStream, object> operation) { - using (var workStream = new MemoryStream()) - { - this.ForEachImageSharpImage( - img => - { - // ReSharper disable AccessToDisposedClosure - object result = operation(img, workStream); - workStream.Seek(0, SeekOrigin.Begin); - - // ReSharper restore AccessToDisposedClosure - return result; - }); - } + using var workStream = new MemoryStream(); + this.ForEachImageSharpImage( + img => + { + // ReSharper disable AccessToDisposedClosure + object result = operation(img, workStream); + workStream.Seek(0, SeekOrigin.Begin); + + // ReSharper restore AccessToDisposedClosure + return result; + }); } - protected void ForEachSystemDrawingImage(Func operation) + protected void ForEachSystemDrawingImage(Func operation) { foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) { @@ -248,21 +224,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs } } - protected void ForEachSystemDrawingImage(Func operation) + protected void ForEachSystemDrawingImage(Func operation) { - using (var workStream = new MemoryStream()) - { - this.ForEachSystemDrawingImage( - img => - { - // ReSharper disable AccessToDisposedClosure - object result = operation(img, workStream); - workStream.Seek(0, SeekOrigin.Begin); - - // ReSharper restore AccessToDisposedClosure - return result; - }); - } + using var workStream = new MemoryStream(); + this.ForEachSystemDrawingImage( + img => + { + // ReSharper disable AccessToDisposedClosure + object result = operation(img, workStream); + workStream.Seek(0, SeekOrigin.Begin); + + // ReSharper restore AccessToDisposedClosure + return result; + }); } } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs index 5da6edc6b..ff55cc5d0 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class FromVector4_Rgb24 : FromVector4 { } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs index b090c7dc2..0426dcf09 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class ToVector4_Bgra32 : ToVector4 { [Benchmark(Baseline = true)] diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs index aecd41831..63a3148aa 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class ToVector4_Rgb24 : ToVector4 { [Benchmark(Baseline = true)] diff --git a/tests/ImageSharp.Benchmarks/Config.cs b/tests/ImageSharp.Benchmarks/Config.cs index d08e2f2d6..0c40b482a 100644 --- a/tests/ImageSharp.Benchmarks/Config.cs +++ b/tests/ImageSharp.Benchmarks/Config.cs @@ -35,12 +35,12 @@ namespace SixLabors.ImageSharp.Benchmarks Job.Default.WithRuntime(CoreRuntime.Core31)); } - public class ShortClr : Config + public class ShortMultiFramework : Config { - public ShortClr() => this.AddJob( + public ShortMultiFramework() => this.AddJob( Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), - Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); + Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3), + Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3)); } public class ShortCore31 : Config diff --git a/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs b/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs new file mode 100644 index 000000000..1db407293 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder +{ + public class YCbCrForwardConverterBenchmark + { + private RgbToYCbCrConverterLut converter; + private Rgb24[] data; + + [GlobalSetup] + public void Setup() + { + this.converter = RgbToYCbCrConverterLut.Create(); + + var r = new Random(42); + this.data = new Rgb24[64]; + + var d = new byte[3]; + for (int i = 0; i < this.data.Length; i++) + { + r.NextBytes(d); + this.data[i] = new Rgb24(d[0], d[1], d[2]); + } + } + + [Benchmark(Baseline = true)] + public void ConvertLut() + { + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + this.converter.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); + } + + [Benchmark] + public void ConvertVectorized() + { + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + if (RgbToYCbCrConverterVectorized.IsSupported) + { + RgbToYCbCrConverterVectorized.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs index 4cf442fc8..416a62833 100644 --- a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs @@ -8,7 +8,7 @@ using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32; namespace SixLabors.ImageSharp.Benchmarks.General { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class Adler32Benchmark { private byte[] data; diff --git a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs index 96a95942c..d21e44a1c 100644 --- a/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs +++ b/tests/ImageSharp.Benchmarks/General/CopyBuffers.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General /// - Span.CopyTo() has terrible performance on classic .NET Framework /// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning) /// - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class CopyBuffers { private byte[] destArray; diff --git a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs index 82f0aa33d..8a90b44cf 100644 --- a/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs +++ b/tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs @@ -8,7 +8,7 @@ using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32; namespace SixLabors.ImageSharp.Benchmarks.General { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class Crc32Benchmark { private byte[] data; diff --git a/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs b/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs new file mode 100644 index 000000000..6bb269e4c --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/GetSetPixel.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks +{ + public class GetSetPixel + { + [Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")] + public System.Drawing.Color GetSetSystemDrawing() + { + using var source = new Bitmap(400, 400); + source.SetPixel(200, 200, System.Drawing.Color.White); + return source.GetPixel(200, 200); + } + + [Benchmark(Description = "ImageSharp GetSet pixel")] + public Rgba32 GetSetImageSharp() + { + using var image = new Image(400, 400); + image[200, 200] = Color.White; + return image[200, 200]; + } + } +} diff --git a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs index f2ff49d4e..f5cf5211e 100644 --- a/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs +++ b/tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Benchmarks.IO { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class BufferedStreams { private readonly byte[] buffer = CreateTestBytes(); diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs index 15c4b8c05..5ec5f1d18 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs @@ -8,7 +8,7 @@ using BenchmarkDotNet.Attributes; namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class UInt32ToSingle { private float[] data; diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs index a0049f984..20d3bd9d6 100644 --- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.ShortMultiFramework))] public class WidenBytesToUInt32 { private byte[] source; diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index e8ad660ad..2a209683b 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -43,7 +43,7 @@ - + diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index 2b6657a22..cf7807837 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -1,21 +1,17 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; - using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; namespace SixLabors.ImageSharp.Benchmarks { - using CoreSize = SixLabors.ImageSharp.Size; - - public class PorterDuffBulkVsPixel : BenchmarkBase + public class PorterDuffBulkVsPixel { private Configuration Configuration => Configuration.Default; @@ -30,23 +26,21 @@ namespace SixLabors.ImageSharp.Benchmarks Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); - using (IMemoryOwner buffer = - Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3)) - { - Span destinationSpan = buffer.Slice(0, destination.Length); - Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); - Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + using IMemoryOwner buffer = + Configuration.Default.MemoryAllocator.Allocate(destination.Length * 3); + Span destinationSpan = buffer.Slice(0, destination.Length); + Span backgroundSpan = buffer.Slice(destination.Length, destination.Length); + Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); - PixelOperations.Instance.ToVector4(this.Configuration, background, backgroundSpan); - PixelOperations.Instance.ToVector4(this.Configuration, source, sourceSpan); + PixelOperations.Instance.ToVector4(this.Configuration, background, backgroundSpan); + PixelOperations.Instance.ToVector4(this.Configuration, source, sourceSpan); - for (int i = 0; i < destination.Length; i++) - { - destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); - } - - PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); } + + PixelOperations.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination); } private void BulkPixelConvert( @@ -67,44 +61,36 @@ namespace SixLabors.ImageSharp.Benchmarks } [Benchmark(Description = "ImageSharp BulkVectorConvert")] - public CoreSize BulkVectorConvert() + public Size BulkVectorConvert() { - using (var image = new Image(800, 800)) - { - using (IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width)) - { - amounts.GetSpan().Fill(1); + using var image = new Image(800, 800); + using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); + amounts.GetSpan().Fill(1); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) - { - Span span = pixels.GetRowSpan(y); - this.BulkVectorConvert(span, span, span, amounts.GetSpan()); - } - - return new CoreSize(image.Width, image.Height); - } + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) + { + Span span = pixels.GetRowSpan(y); + this.BulkVectorConvert(span, span, span, amounts.GetSpan()); } + + return new Size(image.Width, image.Height); } [Benchmark(Description = "ImageSharp BulkPixelConvert")] - public CoreSize BulkPixelConvert() + public Size BulkPixelConvert() { - using (var image = new Image(800, 800)) + using var image = new Image(800, 800); + using IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width); + amounts.GetSpan().Fill(1); + Buffer2D pixels = image.GetRootFramePixelBuffer(); + for (int y = 0; y < image.Height; y++) { - using (IMemoryOwner amounts = Configuration.Default.MemoryAllocator.Allocate(image.Width)) - { - amounts.GetSpan().Fill(1); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int y = 0; y < image.Height; y++) - { - Span span = pixels.GetRowSpan(y); - this.BulkPixelConvert(span, span, span, amounts.GetSpan()); - } - - return new CoreSize(image.Width, image.Height); - } + Span span = pixels.GetRowSpan(y); + this.BulkPixelConvert(span, span, span, amounts.GetSpan()); } + + return new Size(image.Width, image.Height); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs b/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs similarity index 60% rename from tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs rename to tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs index 1c3b1a7b2..7a1c7260e 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs +++ b/tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs @@ -5,7 +5,7 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { [Config(typeof(Config.MultiFramework))] public class BokehBlur @@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public void Blur() { - using (var image = new Image(Configuration.Default, 400, 400, Color.White)) - { - image.Mutate(c => c.BokehBlur()); - } + using var image = new Image(Configuration.Default, 400, 400, Color.White); + image.Mutate(c => c.BokehBlur()); } } } diff --git a/tests/ImageSharp.Benchmarks/Processing/Crop.cs b/tests/ImageSharp.Benchmarks/Processing/Crop.cs new file mode 100644 index 000000000..9f08adc9d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Processing/Crop.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Drawing; +using System.Drawing.Drawing2D; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SDRectangle = System.Drawing.Rectangle; +using SDSize = System.Drawing.Size; + +namespace SixLabors.ImageSharp.Benchmarks.Processing +{ + [Config(typeof(Config.MultiFramework))] + public class Crop + { + [Benchmark(Baseline = true, Description = "System.Drawing Crop")] + public SDSize CropSystemDrawing() + { + using var source = new Bitmap(800, 800); + using var destination = new Bitmap(100, 100); + using var graphics = Graphics.FromImage(destination); + + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); + + return destination.Size; + } + + [Benchmark(Description = "ImageSharp Crop")] + public Size CropImageSharp() + { + using var image = new Image(800, 800); + image.Mutate(x => x.Crop(100, 100)); + return new Size(image.Width, image.Height); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs similarity index 79% rename from tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs rename to tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs index ce2fa988c..f3d6d03af 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs @@ -1,18 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.IO; +using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks { - using System.IO; - - using BenchmarkDotNet.Attributes; - - using SixLabors.ImageSharp.Processing; - using CoreImage = SixLabors.ImageSharp.Image; - - public class DetectEdges : BenchmarkBase + [Config(typeof(Config.MultiFramework))] + public class DetectEdges { private Image image; @@ -21,10 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks { if (this.image == null) { - using (FileStream stream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp")) - { - this.image = CoreImage.Load(stream); - } + this.image = Image.Load(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car))); } } @@ -32,6 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks public void Cleanup() { this.image.Dispose(); + this.image = null; } [Benchmark(Description = "ImageSharp DetectEdges")] diff --git a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs b/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs similarity index 76% rename from tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs rename to tests/ImageSharp.Benchmarks/Processing/Diffuse.cs index 354d105e6..d706938c8 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Diffuse.cs @@ -5,31 +5,27 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.MultiFramework))] public class Diffuse { [Benchmark] public Size DoDiffuse() { - using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); + using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); + image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg)); - return image.Size(); - } + return image.Size(); } [Benchmark] public Size DoDither() { - using (var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Dither()); + using var image = new Image(Configuration.Default, 800, 800, Color.BlanchedAlmond); + image.Mutate(x => x.Dither()); - return image.Size(); - } + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs b/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs similarity index 69% rename from tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs rename to tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs index 8f009e58f..8a4ab8747 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs +++ b/tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs @@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers [Benchmark] public void Blur() { - using (var image = new Image(Configuration.Default, 400, 400, Color.White)) - { - image.Mutate(c => c.GaussianBlur()); - } + using var image = new Image(Configuration.Default, 400, 400, Color.White); + image.Mutate(c => c.GaussianBlur()); } } } diff --git a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs b/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs index 081d3e8e3..cfcb69a0a 100644 --- a/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs +++ b/tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs @@ -10,8 +10,8 @@ using SixLabors.ImageSharp.Tests; namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] - public class HistogramEqualization : BenchmarkBase + [Config(typeof(Config.MultiFramework))] + public class HistogramEqualization { private Image image; @@ -28,26 +28,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Processing public void Cleanup() { this.image.Dispose(); + this.image = null; } [Benchmark(Description = "Global Histogram Equalization")] public void GlobalHistogramEqualization() - { - this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions() - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.Global - })); - } + => this.image.Mutate(img => img.HistogramEqualization( + new HistogramEqualizationOptions() + { + LuminanceLevels = 256, + Method = HistogramEqualizationMethod.Global + })); [Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")] public void AdaptiveHistogramEqualization() - { - this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions() - { - LuminanceLevels = 256, - Method = HistogramEqualizationMethod.AdaptiveTileInterpolation - })); - } + => this.image.Mutate(img => img.HistogramEqualization( + new HistogramEqualizationOptions() + { + LuminanceLevels = 256, + Method = HistogramEqualizationMethod.AdaptiveTileInterpolation + })); } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs b/tests/ImageSharp.Benchmarks/Processing/Resize.cs similarity index 87% rename from tests/ImageSharp.Benchmarks/Samplers/Resize.cs rename to tests/ImageSharp.Benchmarks/Processing/Resize.cs index 63a85c757..571c92cc8 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Resize.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Resize.cs @@ -4,47 +4,49 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Globalization; - +using System.IO; using BenchmarkDotNet.Attributes; - using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Tests; +using SDImage = System.Drawing.Image; namespace SixLabors.ImageSharp.Benchmarks { - [Config(typeof(Config.ShortClr))] -#pragma warning disable SA1649 // File name should match first type name - public abstract class ResizeBenchmarkBase -#pragma warning restore SA1649 // File name should match first type name + [Config(typeof(Config.MultiFramework))] + public abstract class Resize where TPixel : unmanaged, IPixel { - protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); + private byte[] bytes = null; private Image sourceImage; - private Bitmap sourceBitmap; + private SDImage sourceBitmap; - [Params("3032-400")] - public virtual string SourceToDest { get; set; } - - protected int SourceSize { get; private set; } + protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule()); protected int DestSize { get; private set; } [GlobalSetup] public virtual void Setup() { - string[] stuff = this.SourceToDest.Split('-'); - this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture); - this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture); - this.sourceImage = new Image(this.Configuration, this.SourceSize, this.SourceSize); - this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize); + if (this.bytes is null) + { + this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake)); + + this.sourceImage = Image.Load(this.bytes); + + var ms1 = new MemoryStream(this.bytes); + this.sourceBitmap = SDImage.FromStream(ms1); + this.DestSize = this.sourceBitmap.Width / 2; + } } [GlobalCleanup] public void Cleanup() { + this.bytes = null; this.sourceImage.Dispose(); this.sourceBitmap.Dispose(); } @@ -96,12 +98,10 @@ namespace SixLabors.ImageSharp.Benchmarks protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); } - public class Resize_Bicubic_Rgba32 : ResizeBenchmarkBase + public class Resize_Bicubic_Rgba32 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); // RESULTS - 2019 April - ResizeWorker: // @@ -133,9 +133,6 @@ namespace SixLabors.ImageSharp.Benchmarks [Params(128, 512, 1024, 8 * 1024)] public int WorkingBufferSizeHintInKilobytes { get; set; } - [Params("3032-400", "4000-300")] - public override string SourceToDest { get; set; } - public override void Setup() { this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; @@ -143,12 +140,10 @@ namespace SixLabors.ImageSharp.Benchmarks } } - public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase + public class Resize_Bicubic_Bgra32 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); // RESULTS (2019 April): // @@ -171,12 +166,10 @@ namespace SixLabors.ImageSharp.Benchmarks // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B | } - public class Resize_Bicubic_Rgb24 : ResizeBenchmarkBase + public class Resize_Bicubic_Rgb24 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic); // RESULTS (2019 April): // @@ -197,12 +190,10 @@ namespace SixLabors.ImageSharp.Benchmarks // 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B | } - public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase + public class Resize_BicubicCompand_Rgba32 : Resize { protected override void ExecuteResizeOperation(IImageProcessingContext ctx) - { - ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); - } + => ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true); // RESULTS (2019 April): // diff --git a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs b/tests/ImageSharp.Benchmarks/Processing/Rotate.cs similarity index 81% rename from tests/ImageSharp.Benchmarks/Samplers/Rotate.cs rename to tests/ImageSharp.Benchmarks/Processing/Rotate.cs index 94594c787..107c47f06 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Rotate.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Rotate.cs @@ -5,20 +5,18 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.MultiFramework))] public class Rotate { [Benchmark] public Size DoRotate() { - using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Rotate(37.5F)); + using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); + image.Mutate(x => x.Rotate(37.5F)); - return image.Size(); - } + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs b/tests/ImageSharp.Benchmarks/Processing/Skew.cs similarity index 81% rename from tests/ImageSharp.Benchmarks/Samplers/Skew.cs rename to tests/ImageSharp.Benchmarks/Processing/Skew.cs index 2758bed7a..b77f0dcd6 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Skew.cs +++ b/tests/ImageSharp.Benchmarks/Processing/Skew.cs @@ -5,20 +5,18 @@ using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -namespace SixLabors.ImageSharp.Benchmarks.Samplers +namespace SixLabors.ImageSharp.Benchmarks.Processing { - [Config(typeof(Config.ShortClr))] + [Config(typeof(Config.MultiFramework))] public class Skew { [Benchmark] public Size DoSkew() { - using (var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond)) - { - image.Mutate(x => x.Skew(20, 10)); + using var image = new Image(Configuration.Default, 400, 400, Color.BlanchedAlmond); + image.Mutate(x => x.Skew(20, 10)); - return image.Size(); - } + return image.Size(); } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs b/tests/ImageSharp.Benchmarks/Samplers/Crop.cs deleted file mode 100644 index a62b68557..000000000 --- a/tests/ImageSharp.Benchmarks/Samplers/Crop.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Drawing; -using System.Drawing.Drawing2D; -using BenchmarkDotNet.Attributes; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -using SDRectangle = System.Drawing.Rectangle; -using SDSize = System.Drawing.Size; - -namespace SixLabors.ImageSharp.Benchmarks -{ - [Config(typeof(Config.ShortClr))] - public class Crop : BenchmarkBase - { - [Benchmark(Baseline = true, Description = "System.Drawing Crop")] - public SDSize CropSystemDrawing() - { - using (var source = new Bitmap(800, 800)) - using (var destination = new Bitmap(100, 100)) - using (var graphics = Graphics.FromImage(destination)) - { - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel); - - return destination.Size; - } - } - - [Benchmark(Description = "ImageSharp Crop")] - public Size CropResizeCore() - { - using (var image = new Image(800, 800)) - { - image.Mutate(x => x.Crop(100, 100)); - return new Size(image.Width, image.Height); - } - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs new file mode 100644 index 000000000..9a6fc8d6f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs @@ -0,0 +1,91 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.ColorSpaces; +using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; +using Xunit; +using Xunit.Abstractions; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Jpg +{ + public class RgbToYCbCrConverterTests + { + public RgbToYCbCrConverterTests(ITestOutputHelper output) + { + this.Output = output; + } + + private ITestOutputHelper Output { get; } + + [Fact] + public void TestLutConverter() + { + Rgb24[] data = CreateTestData(); + var target = RgbToYCbCrConverterLut.Create(); + + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + target.Convert(data.AsSpan(), ref y, ref cb, ref cr); + + Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); + } + + [Fact] + public void TestVectorizedConverter() + { + if (!RgbToYCbCrConverterVectorized.IsSupported) + { + this.Output.WriteLine("No AVX and/or FMA present, skipping test!"); + return; + } + + Rgb24[] data = CreateTestData(); + + Block8x8F y = default; + Block8x8F cb = default; + Block8x8F cr = default; + + RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr); + + Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); + } + + private static void Verify(ReadOnlySpan data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, ApproximateColorSpaceComparer comparer) + { + for (int i = 0; i < data.Length; i++) + { + int r = data[i].R; + int g = data[i].G; + int b = data[i].B; + + float y = (0.299F * r) + (0.587F * g) + (0.114F * b); + float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); + float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); + + Assert.True(comparer.Equals(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y} == {yResult[i]}, {cb} == {cbResult[i]}, {cr} == {crResult[i]}"); + } + } + + private static Rgb24[] CreateTestData() + { + var data = new Rgb24[64]; + var r = new Random(); + + var random = new byte[3]; + for (int i = 0; i < data.Length; i++) + { + r.NextBytes(random); + data[i] = new Rgb24(random[0], random[1], random[2]); + } + + return data; + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index 39786a217..cc7f32bef 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations (s, d) => { Span destPixels = d.GetSpan(); - this.Operations.FromVector4Destructive(this.Configuration, (Span)s, destPixels, PixelConversionModifiers.Scale); + this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale); }); } @@ -168,15 +168,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [MemberData(nameof(ArraySizesData))] public void FromCompandedScaledVector4(int count) { - void SourceAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); - } + void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v); - void ExpectedAction(ref Vector4 v) - { - SRgbCompanding.Compress(ref v); - } + void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v); Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -188,7 +182,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations this.Configuration, s, d.GetSpan(), - PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); + PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale), + false); } [Theory] @@ -219,7 +214,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); @@ -254,7 +250,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive( @@ -297,7 +294,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations expected, (s, d) => { - PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply + PixelConversionModifiers modifiers = this.HasUnassociatedAlpha + ? PixelConversionModifiers.Premultiply : PixelConversionModifiers.None; this.Operations.FromVector4Destructive( @@ -305,7 +303,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations s, d.GetSpan(), modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); - }); + }, + false); } [Theory] @@ -343,7 +342,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations PixelConverterTests.ReferenceImplementations.To(this.Configuration, source, expected); - TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, (ReadOnlySpan)s, d.GetSpan())); + TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan())); } [Theory] @@ -356,11 +355,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations TestOperation( source, expected, - (s, d) => - { - Span destVectors = d.GetSpan(); - this.Operations.ToVector4(this.Configuration, (ReadOnlySpan)s, destVectors, PixelConversionModifiers.Scale); - }); + (s, d) => this.Operations.ToVector4( + this.Configuration, + s, + d.GetSpan(), + PixelConversionModifiers.Scale)); } [Theory] @@ -369,13 +368,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - SRgbCompanding.Compress(ref v); } - void ExpectedAction(ref Vector4 v) - { - SRgbCompanding.Expand(ref v); - } + void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -396,13 +391,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); } - void ExpectedAction(ref Vector4 v) - { - Numerics.Premultiply(ref v); - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -419,13 +410,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); } - void ExpectedAction(ref Vector4 v) - { - Numerics.Premultiply(ref v); - } + void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v)); @@ -446,8 +433,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { void SourceAction(ref Vector4 v) { - Numerics.UnPremultiply(ref v); - SRgbCompanding.Compress(ref v); } void ExpectedAction(ref Vector4 v) @@ -1006,15 +991,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations [Theory] [MemberData(nameof(ArraySizesData))] public void PackFromRgbPlanes(int count) - { - SimdUtilsTests.TestPackFromRgbPlanes( + => SimdUtilsTests.TestPackFromRgbPlanes( count, - ( - r, - g, - b, - actual) => PixelOperations.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual)); - } + (r, g, b, actual) => PixelOperations.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual)); public delegate void RefAction(ref T1 arg1); @@ -1053,11 +1032,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations internal static void TestOperation( TSource[] source, TDest[] expected, - Action> action) + Action> action, + bool preferExactComparison = true) where TSource : struct where TDest : struct { - using (var buffers = new TestBuffers(source, expected)) + using (var buffers = new TestBuffers(source, expected, preferExactComparison)) { action(buffers.SourceBuffer, buffers.ActualDestBuffer); buffers.Verify(); @@ -1071,7 +1051,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); result[i] = v; @@ -1088,7 +1068,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); @@ -1106,7 +1086,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations for (int i = 0; i < result.Length; i++) { - Vector4 v = GetVector(rnd); + Vector4 v = GetScaledVector(rnd); vectorModifier?.Invoke(ref v); @@ -1129,10 +1109,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations return result; } - internal static Vector4 GetVector(Random rnd) - { - return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); - } + internal static Vector4 GetScaledVector(Random rnd) + => new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble()); [StructLayout(LayoutKind.Sequential)] internal unsafe struct OctetBytes @@ -1160,11 +1138,14 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public TDest[] ExpectedDestBuffer { get; } - public TestBuffers(TSource[] source, TDest[] expectedDest) + public bool PreferExactComparison { get; } + + public TestBuffers(TSource[] source, TDest[] expectedDest, bool preferExactComparison = true) { this.SourceBuffer = source; this.ExpectedDestBuffer = expectedDest; this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate(expectedDest.Length); + this.PreferExactComparison = preferExactComparison; } public void Dispose() => this.ActualDestBuffer.Dispose(); @@ -1177,26 +1158,54 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { Span expected = MemoryMarshal.Cast(this.ExpectedDestBuffer.AsSpan()); Span actual = MemoryMarshal.Cast(this.ActualDestBuffer.GetSpan()); + var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); - var comparer = new ApproximateFloatComparer(0.001f); for (int i = 0; i < count; i++) { - // ReSharper disable PossibleNullReferenceException Assert.Equal(expected[i], actual[i], comparer); + } + } + else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel()) + { + Span expected = this.ExpectedDestBuffer.AsSpan(); + Span actual = this.ActualDestBuffer.GetSpan(); + var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F); - // ReSharper restore PossibleNullReferenceException + for (int i = 0; i < count; i++) + { + Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer); } } else { Span expected = this.ExpectedDestBuffer.AsSpan(); Span actual = this.ActualDestBuffer.GetSpan(); + for (int i = 0; i < count; i++) { Assert.Equal(expected[i], actual[i]); } } } + + // TODO: We really need a PixelTypeInfo.BitsPerComponent property!! + private static bool IsComplexPixel() + { + switch (default(TDest)) + { + case HalfSingle _: + case HalfVector2 _: + case L16 _: + case La32 _: + case NormalizedShort2 _: + case Rg32 _: + case Short2 _: + return true; + + default: + return Unsafe.SizeOf() > sizeof(int); + } + } } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 4a20f4e56..58b7fd12e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms testOutputDetails: workingBufferLimitInRows, appendPixelTypeToFileName: false); image.CompareToReferenceOutput( - ImageComparer.TolerantPercentage(0.001f), + ImageComparer.TolerantPercentage(0.004f), provider, testOutputDetails: workingBufferLimitInRows, appendPixelTypeToFileName: false); @@ -216,6 +216,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms appendSourceFileOrDescription: false); } + [Theory] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)] + [WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)] + public void Resize_PremultiplyAlpha(TestImageProvider provider, bool premultiplyAlpha) + where TPixel : unmanaged, IPixel + { + string details = premultiplyAlpha ? "On" : "Off"; + + provider.RunValidatingProcessorTest( + x => + { + var resizeOptions = new ResizeOptions() + { + Size = x.GetCurrentSize() / 2, + Mode = ResizeMode.Crop, + Sampler = KnownResamplers.Bicubic, + Compand = false, + PremultiplyAlpha = premultiplyAlpha + }; + x.Resize(resizeOptions); + }, + details, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + } + [Theory] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)] public void Resize_IsAppliedToAllFrames(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index 7d57d8c49..6eb0f51a3 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -110,6 +110,21 @@ namespace SixLabors.ImageSharp.Tests } } + // Test case for issue: https://github.com/SixLabors/ImageSharp/issues/1505 + [Theory] + [WithFile(TestImages.Gif.Issues.Issue1505, PixelTypes.Rgba32)] + public void Issue1505(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var octreeQuantizer = new OctreeQuantizer(); + IQuantizer quantizer = octreeQuantizer.CreatePixelSpecificQuantizer(Configuration.Default, new QuantizerOptions() { MaxColors = 128 }); + ImageFrame frame = image.Frames[0]; + quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds()); + } + } + private int GetTransparentIndex(IndexedImageFrame quantized) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 92d5f1bcf..e8baad2f7 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -415,6 +415,7 @@ namespace SixLabors.ImageSharp.Tests public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif"; public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif"; + public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png"; } public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 }; diff --git a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs index b2f390dcd..2a0ed19b4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests { @@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests internal readonly struct ApproximateFloatComparer : IEqualityComparer, IEqualityComparer, + IEqualityComparer, IEqualityComparer, IEqualityComparer { @@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests } /// - public int GetHashCode(float obj) => obj.GetHashCode(); + public int GetHashCode(float obj) + => obj.GetHashCode(); /// - public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); + public bool Equals(Vector2 x, Vector2 y) + => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y); /// - public int GetHashCode(Vector2 obj) => obj.GetHashCode(); + public int GetHashCode(Vector2 obj) + => obj.GetHashCode(); /// - public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W); + public bool Equals(IPixel x, IPixel y) + => this.Equals(x.ToScaledVector4(), y.ToScaledVector4()); + + public int GetHashCode(IPixel obj) + => obj.ToScaledVector4().GetHashCode(); + + /// + public bool Equals(Vector4 x, Vector4 y) + => this.Equals(x.X, y.X) + && this.Equals(x.Y, y.Y) + && this.Equals(x.Z, y.Z) + && this.Equals(x.W, y.W); /// - public int GetHashCode(Vector4 obj) => obj.GetHashCode(); + public int GetHashCode(Vector4 obj) + => obj.GetHashCode(); /// public bool Equals(ColorMatrix x, ColorMatrix y) - { - return - this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) + => this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14) && this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) && this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34) && this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44) && this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54); - } /// public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); diff --git a/tests/Images/External b/tests/Images/External index 8b43d14d2..346070e5b 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit 8b43d14d21ce9b436af3d12a70d38402cdba176b +Subproject commit 346070e5ba538f1a3bbafc0ea7367404c5f8c9ab diff --git a/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png b/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png new file mode 100644 index 000000000..15dfa52a8 --- /dev/null +++ b/tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bea0faf09782d0d972e72ad94e53b8ca9c823fd3056fc6a97aba8c43105fcd66 +size 102581