Browse Source

Merge branch 'master' into tiff-format

pull/1570/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
d3bfc5f0e4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      shared-infrastructure
  2. 186
      src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs
  3. 40
      src/ImageSharp/Common/Helpers/Numerics.cs
  4. 35
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
  5. 120
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
  6. 28
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  7. 4
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs
  8. 3
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  9. 106
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  10. 6
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  11. 24
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  12. 8
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  13. 6
      src/ImageSharp/Processing/ResizeOptions.cs
  14. 19
      tests/ImageSharp.Benchmarks/BenchmarkBase.cs
  15. 29
      tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs
  16. 48
      tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs
  17. 29
      tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs
  18. 29
      tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs
  19. 34
      tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
  20. 23
      tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs
  21. 10
      tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs
  22. 19
      tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs
  23. 10
      tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs
  24. 59
      tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs
  25. 19
      tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
  26. 34
      tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
  27. 32
      tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs
  28. 16
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs
  29. 20
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs
  30. 7
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  31. 16
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs
  32. 20
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
  33. 32
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  34. 10
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs
  35. 10
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs
  36. 13
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  37. 64
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs
  38. 32
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs
  39. 14
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs
  40. 19
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs
  41. 14
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs
  42. 112
      tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs
  43. 2
      tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs
  44. 2
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs
  45. 2
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs
  46. 8
      tests/ImageSharp.Benchmarks/Config.cs
  47. 56
      tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs
  48. 2
      tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs
  49. 2
      tests/ImageSharp.Benchmarks/General/CopyBuffers.cs
  50. 2
      tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs
  51. 28
      tests/ImageSharp.Benchmarks/General/GetSetPixel.cs
  52. 2
      tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs
  53. 2
      tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs
  54. 2
      tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs
  55. 2
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  56. 86
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
  57. 8
      tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs
  58. 40
      tests/ImageSharp.Benchmarks/Processing/Crop.cs
  59. 20
      tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs
  60. 20
      tests/ImageSharp.Benchmarks/Processing/Diffuse.cs
  61. 6
      tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs
  62. 31
      tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs
  63. 63
      tests/ImageSharp.Benchmarks/Processing/Resize.cs
  64. 12
      tests/ImageSharp.Benchmarks/Processing/Rotate.cs
  65. 12
      tests/ImageSharp.Benchmarks/Processing/Skew.cs
  66. 45
      tests/ImageSharp.Benchmarks/Samplers/Crop.cs
  67. 91
      tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs
  68. 125
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs
  69. 28
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  70. 15
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  71. 1
      tests/ImageSharp.Tests/TestImages.cs
  72. 32
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  73. 2
      tests/Images/External
  74. 3
      tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 3ad6e96a5f900fecd134f0dbba937cb97c7fb94f
Subproject commit b7b9a2755e456a96acbf103494228226d92eddf3

186
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
/// </remarks>
public static class SRgbCompanding
{
private const int Length = Scale + 2; // 256kb @ 16bit precision.
private const int Scale = (1 << 16) - 1;
private static readonly Lazy<float[]> LazyCompressTable = new Lazy<float[]>(
() =>
{
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<float[]> LazyExpandTable = new Lazy<float[]>(
() =>
{
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;
/// <summary>
/// Expands the companded vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(Span<Vector4> 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.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Compress(Span<Vector4> vectors)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Compress(Span<Vector4> 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.
/// </summary>
/// <param name="vector">The vector.</param>
[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.
/// </summary>
/// <param name="vector">The vector.</param>
[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
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the linear channel value.</returns>
[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);
/// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the nonlinear channel value.</returns>
[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<Vector4> vectors, float[] table)
{
fixed (float* tablePointer = &table[0])
{
var scale = Vector256.Create((float)Scale);
Vector256<float> zero = Vector256<float>.Zero;
var offset = Vector256.Create(1);
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> vectorsBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(vectors));
ref Vector256<float> vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u));
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
{
Vector256<float> multiplied = Avx.Multiply(scale, vectorsBase);
multiplied = Avx.Min(Avx.Max(zero, multiplied), scale);
Vector256<int> truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied);
Vector256<float> truncatedF = Avx.ConvertToVector256Single(truncated);
Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float));
Vector256<float> 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<float> 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<Vector4> 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);
}
}
}
}
}

40
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
/// <summary>
/// Performs a linear interpolation between two values based on the given weighting.
/// </summary>
/// <param name="value1">The first value.</param>
/// <param name="value2">The second value.</param>
/// <param name="amount">Values between 0 and 1 that indicates the weight of <paramref name="value2"/>.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Lerp(
in Vector256<float> value1,
in Vector256<float> value2,
in Vector256<float> amount)
{
Vector256<float> diff = Avx.Subtract(value2, value1);
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(diff, amount, value1);
}
else
{
return Avx.Add(Avx.Multiply(diff, amount), value1);
}
}
#endif
/// <summary>
/// Performs a linear interpolation between two values based on the given weighting.
/// </summary>
/// <param name="value1">The first value.</param>
/// <param name="value2">The second value.</param>
/// <param name="amount">A value between 0 and 1 that indicates the weight of <paramref name="value2"/>.</param>
/// <returns>The <see cref="float"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount)
=> ((value2 - value1) * amount) + value1;
}
}

35
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs → 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
{
/// <summary>
/// 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)!
/// </summary>
internal unsafe struct RgbToYCbCrTables
internal unsafe struct RgbToYCbCrConverterLut
{
/// <summary>
/// The red luminance table
@ -63,10 +64,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Initializes the YCbCr tables
/// </summary>
/// <returns>The initialized <see cref="RgbToYCbCrTables"/></returns>
public static RgbToYCbCrTables Create()
/// <returns>The initialized <see cref="RgbToYCbCrConverterLut"/></returns>
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
}
/// <summary>
/// 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.
/// </summary>
[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<Rgb24> 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);

120
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<byte> 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<byte> 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<byte> 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<Rgb24> 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<byte> inRef = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> destYRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref yBlock);
ref Vector256<float> destCbRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock);
ref Vector256<float> destCrRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock);
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> 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<byte, Vector256<uint>>(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
}
}
}

28
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
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrTables colorTables;
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 8x8 block to hold TPixel data
@ -48,7 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public static YCbCrForwardConverter<TPixel> Create()
{
var result = default(YCbCrForwardConverter<TPixel>);
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);
}
}
}

4
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
}
}
}
}
}

3
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<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors));
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, this.maxColors));
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.palette = result;

106
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
/// <returns>The weighted sum</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 Convolve(Span<Vector4> 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<float>(this.bufferPtr);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Fma.IsSupported)
{
float* bufferStart = this.bufferPtr;
float* bufferEnd = bufferStart + (this.Length & ~3);
Vector256<float> result256_0 = Vector256<float>.Zero;
Vector256<float> result256_1 = Vector256<float>.Zero;
ReadOnlySpan<byte> 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<int> mask = Unsafe.ReadUnaligned<Vector256<int>>(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<Vector4, Vector256<float>>(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<Vector4, Vector256<float>>(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<Vector4, Vector256<float>>(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<float> result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper());
if ((this.Length & 1) != 0)
{
result128 = Fma.MultiplyAdd(
Unsafe.As<Vector4, Vector128<float>>(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;
}
}
/// <summary>
@ -91,9 +167,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
[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<double> values)
{

6
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;
}
/// <summary>
@ -53,6 +54,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
public bool Compand { get; }
/// <summary>
/// Gets a value indicating whether to premultiply the alpha (if it exists) during the resize operation.
/// </summary>
public bool PremultiplyAlpha { get; }
/// <inheritdoc />
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new ResizeProcessor<TPixel>(configuration, this, source, sourceRectangle);

24
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<TPixel> destination;
public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> 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<TPixel> 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<TPixel> sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle);

8
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs

@ -105,14 +105,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[MethodImpl(InliningOptions.ShortMethod)]
public Span<Vector4> 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<TPixel> destination)
{

6
src/ImageSharp/Processing/ResizeOptions.cs

@ -45,5 +45,11 @@ namespace SixLabors.ImageSharp.Processing
/// Gets or sets the target rectangle to resize into.
/// </summary>
public Rectangle? TargetRectangle { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to premultiply
/// the alpha (if it exists) during the resize operation.
/// </summary>
public bool PremultiplyAlpha { get; set; } = true;
}
}

19
tests/ImageSharp.Benchmarks/BenchmarkBase.cs

@ -1,19 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Benchmarks
{
/// <summary>
/// The image benchmark base class.
/// </summary>
public abstract class BenchmarkBase
{
/// <summary>
/// Initializes a new instance of the <see cref="BenchmarkBase"/> class.
/// </summary>
protected BenchmarkBase()
{
// Add Image Formats
}
}
}

29
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<Rgba32>(memoryStream))
{
return new Size(image.Width, image.Height);
}
}
using var memoryStream = new MemoryStream(this.bmpBytes);
using var image = Image.Load<Rgba32>(memoryStream);
return new Size(image.Width, image.Height);
}
}
}

48
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<Rgba32>(bytes))
{
return image.Size();
}
using var image = Image.Load<Rgba32>(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);
}
}
}

29
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<Rgba32>(memoryStream))
{
return new Size(image.Width, image.Height);
}
}
using var memoryStream = new MemoryStream(this.gifBytes);
using var image = Image.Load<Rgba32>(memoryStream);
return new Size(image.Width, image.Height);
}
}
}

29
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<Rgba32>(memoryStream))
{
return image.Size();
}
}
using var memoryStream = new MemoryStream(this.pngBytes);
using var image = Image.Load<Rgba32>(memoryStream);
return image.Size();
}
}
}

34
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<Bgr24>(this.data, new TgaDecoder()))
{
return image.Width;
}
using var image = Image.Load<Bgr24>(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<byte> shared = ArrayPool<byte>.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)
{

23
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);
}
}
}
}

10
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<string> 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;
});
}
}
}

19
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);
}
}
}

10
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;
});
}
}
}

59
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
{
/// <summary>
/// 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.
/// </summary>
[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<Rgba32>(this.bmpStream);
this.bmpCore = Image.Load<Rgba32>(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);
}
}
}

19
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);
}
}
}

34
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<Rgba32> 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);
}
}
}

32
tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs

@ -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<Rgba32>(400, 400))
{
image[200, 200] = Color.White;
return image[200, 200];
}
}
}
}

16
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);
}
}
}

20
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<float>[] 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<float>[] 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<float> buffer in this.input)
foreach (Buffer2D<float> buffer in this.Input)
{
buffer.Dispose();
}

7
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()

16
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
{
/// <summary>
/// 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.
/// </summary>
[Config(typeof(MultiImageBenchmarkBase.Config))]
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles =>
@ -35,14 +33,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark]
public void ImageSharp()
{
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
}
=> this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
[Benchmark(Baseline = true)]
public void SystemDrawing()
{
this.ForEachStream(SDImage.FromStream);
}
=> this.ForEachStream(SDImage.FromStream);
}
}

20
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
/// <summary>
/// Image-specific Jpeg benchmarks
/// </summary>
[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);

32
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<Rgba32> 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<Rgba32>(this.bmpStream);
this.bmpCore = Image.Load<Rgba32>(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);
}
}
}

10
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<string> 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;
});
}
}
}

10
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);
}
}
}

13
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);
}
}
}

64
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<string> 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<Rgba32>(
this.configuration,
sourceStream,
new JpegDecoder { IgnoreMetadata = true }))
{
using (var source = Image.Load<Rgba32>(
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;
});
}
}

32
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));

14
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);
}
}
}

19
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);
}
}
}

14
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);
}
}
}

112
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<string, byte[]> FileNamesToBytes { get; set; } = new Dictionary<string, byte[]>();
protected Dictionary<string, Image<Rgba32>> FileNamesToImageSharpImages { get; set; } = new Dictionary<string, Image<Rgba32>>();
protected Dictionary<string, Bitmap> FileNamesToSystemDrawingImages { get; set; } = new Dictionary<string, System.Drawing.Bitmap>();
protected Dictionary<string, Bitmap> FileNamesToSystemDrawingImages { get; set; } = new Dictionary<string, Bitmap>();
/// <summary>
/// The values of this enum separate input files into categories.
@ -72,7 +57,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
/// <summary>
/// Gets folders containing files OR files to be processed by the benchmark.
/// </summary>
protected IEnumerable<string> AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f));
protected IEnumerable<string> AllFoldersOrFiles
=> this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f));
/// <summary>
/// Gets the large image threshold.
@ -83,19 +69,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
protected IEnumerable<KeyValuePair<string, T>> EnumeratePairsByBenchmarkSettings<T>(
Dictionary<string, T> input,
Predicate<T> 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<KeyValuePair<string, byte[]>> FileNames2Bytes
=>
@ -150,17 +130,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
foreach (KeyValuePair<string, byte[]> 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<Rgba32>(ms1);
this.FileNamesToImageSharpImages[fn] = Image.Load<Rgba32>(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<KeyValuePair<string, System.Drawing.Bitmap>> FileNames2SystemDrawingImages
protected IEnumerable<KeyValuePair<string, Bitmap>> FileNames2SystemDrawingImages
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToSystemDrawingImages,
@ -217,22 +195,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
protected void ForEachImageSharpImage(Func<Image<Rgba32>, 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<System.Drawing.Bitmap, object> operation)
protected void ForEachSystemDrawingImage(Func<Bitmap, object> operation)
{
foreach (KeyValuePair<string, Bitmap> kv in this.FileNames2SystemDrawingImages)
{
@ -248,21 +224,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
}
}
protected void ForEachSystemDrawingImage(Func<System.Drawing.Bitmap, MemoryStream, object> operation)
protected void ForEachSystemDrawingImage(Func<Bitmap, MemoryStream, object> 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;
});
}
}
}

2
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<Rgb24>
{
}

2
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<Bgra32>
{
[Benchmark(Baseline = true)]

2
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<Rgb24>
{
[Benchmark(Baseline = true)]

8
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

56
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);
}
}
}
}

2
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;

2
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)
/// </summary>
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class CopyBuffers
{
private byte[] destArray;

2
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;

28
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<Rgba32>(400, 400);
image[200, 200] = Color.White;
return image[200, 200];
}
}
}

2
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();

2
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;

2
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;

2
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -43,7 +43,7 @@
<Compile Remove="General\BasicMath\ModuloPowerOfTwoConstant.cs" />
<Compile Remove="General\BasicMath\ModuloPowerOfTwoVariable.cs" />
<Compile Remove="PixelBlenders\**" />
<Compile Remove="Samplers\Resize.cs" />
<Compile Remove="Processing\Resize.cs" />
</ItemGroup>
</Project>

86
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<Vector4> buffer =
Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
using IMemoryOwner<Vector4> buffer =
Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3);
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, source, sourceSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, background, backgroundSpan);
PixelOperations<TPixel>.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<TPixel>.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<TPixel>.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination);
}
private void BulkPixelConvert<TPixel>(
@ -67,44 +61,36 @@ namespace SixLabors.ImageSharp.Benchmarks
}
[Benchmark(Description = "ImageSharp BulkVectorConvert")]
public CoreSize BulkVectorConvert()
public Size BulkVectorConvert()
{
using (var image = new Image<Rgba32>(800, 800))
{
using (IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width))
{
amounts.GetSpan().Fill(1);
using var image = new Image<Rgba32>(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkVectorConvert(span, span, span, amounts.GetSpan());
}
return new CoreSize(image.Width, image.Height);
}
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> 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<Rgba32>(800, 800))
using var image = new Image<Rgba32>(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
using (IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width))
{
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkPixelConvert(span, span, span, amounts.GetSpan());
}
return new CoreSize(image.Width, image.Height);
}
Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkPixelConvert(span, span, span, amounts.GetSpan());
}
return new Size(image.Width, image.Height);
}
}
}
}

8
tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs → 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<Rgba32>(Configuration.Default, 400, 400, Color.White))
{
image.Mutate(c => c.BokehBlur());
}
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White);
image.Mutate(c => c.BokehBlur());
}
}
}

40
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<Rgba32>(800, 800);
image.Mutate(x => x.Crop(100, 100));
return new Size(image.Width, image.Height);
}
}
}

20
tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs → 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<Rgba32> 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<Rgba32>(stream);
}
this.image = Image.Load<Rgba32>(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")]

20
tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs → 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<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond))
{
image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
using var image = new Image<Rgba32>(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<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond))
{
image.Mutate(x => x.Dither());
using var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond);
image.Mutate(x => x.Dither());
return image.Size();
}
return image.Size();
}
}
}

6
tests/ImageSharp.Benchmarks/Samplers/GaussianBlur.cs → tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs

@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark]
public void Blur()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White))
{
image.Mutate(c => c.GaussianBlur());
}
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White);
image.Mutate(c => c.GaussianBlur());
}
}
}

31
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<Rgba32> 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
}));
}
}

63
tests/ImageSharp.Benchmarks/Samplers/Resize.cs → 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<TPixel>
#pragma warning restore SA1649 // File name should match first type name
[Config(typeof(Config.MultiFramework))]
public abstract class Resize<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule());
private byte[] bytes = null;
private Image<TPixel> 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<TPixel>(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<TPixel>(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<Rgba32>
public class Resize_Bicubic_Rgba32 : Resize<Rgba32>
{
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<Bgra32>
public class Resize_Bicubic_Bgra32 : Resize<Bgra32>
{
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<Rgb24>
public class Resize_Bicubic_Rgb24 : Resize<Rgb24>
{
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<Rgba32>
public class Resize_BicubicCompand_Rgba32 : Resize<Rgba32>
{
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):
//

12
tests/ImageSharp.Benchmarks/Samplers/Rotate.cs → 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<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond))
{
image.Mutate(x => x.Rotate(37.5F));
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond);
image.Mutate(x => x.Rotate(37.5F));
return image.Size();
}
return image.Size();
}
}
}

12
tests/ImageSharp.Benchmarks/Samplers/Skew.cs → 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<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond))
{
image.Mutate(x => x.Skew(20, 10));
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond);
image.Mutate(x => x.Skew(20, 10));
return image.Size();
}
return image.Size();
}
}
}

45
tests/ImageSharp.Benchmarks/Samplers/Crop.cs

@ -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<Rgba32>(800, 800))
{
image.Mutate(x => x.Crop(100, 100));
return new Size(image.Width, image.Height);
}
}
}
}

91
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<Rgb24> 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;
}
}
}

125
tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs

@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
(s, d) =>
{
Span<TPixel> destPixels = d.GetSpan();
this.Operations.FromVector4Destructive(this.Configuration, (Span<Vector4>)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<TPixel, TDestPixel>(this.Configuration, source, expected);
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, (ReadOnlySpan<TPixel>)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<Vector4> destVectors = d.GetSpan();
this.Operations.ToVector4(this.Configuration, (ReadOnlySpan<TPixel>)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<TPixel>(
=> SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
count,
(
r,
g,
b,
actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
}
(r, g, b, actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
public delegate void RefAction<T1>(ref T1 arg1);
@ -1053,11 +1032,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<TSource[], IMemoryOwner<TDest>> action)
Action<TSource[], IMemoryOwner<TDest>> action,
bool preferExactComparison = true)
where TSource : struct
where TDest : struct
{
using (var buffers = new TestBuffers<TSource, TDest>(source, expected))
using (var buffers = new TestBuffers<TSource, TDest>(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<TDest>(expectedDest.Length);
this.PreferExactComparison = preferExactComparison;
}
public void Dispose() => this.ActualDestBuffer.Dispose();
@ -1177,26 +1158,54 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
Span<Vector4> expected = MemoryMarshal.Cast<TDest, Vector4>(this.ExpectedDestBuffer.AsSpan());
Span<Vector4> actual = MemoryMarshal.Cast<TDest, Vector4>(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<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> 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<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> 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<TDest>() > sizeof(int);
}
}
}
}
}

28
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<TPixel>(TestImageProvider<TPixel> provider, bool premultiplyAlpha)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(TestImageProvider<TPixel> provider)

15
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
var octreeQuantizer = new OctreeQuantizer();
IQuantizer<TPixel> quantizer = octreeQuantizer.CreatePixelSpecificQuantizer<TPixel>(Configuration.Default, new QuantizerOptions() { MaxColors = 128 });
ImageFrame<TPixel> frame = image.Frames[0];
quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
}
}
private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel>
{

1
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 };

32
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<float>,
IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>
{
@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests
}
/// <inheritdoc/>
public int GetHashCode(float obj) => obj.GetHashCode();
public int GetHashCode(float obj)
=> obj.GetHashCode();
/// <inheritdoc/>
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);
/// <inheritdoc/>
public int GetHashCode(Vector2 obj) => obj.GetHashCode();
public int GetHashCode(Vector2 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
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();
/// <inheritdoc/>
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);
/// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode();
public int GetHashCode(Vector4 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();

2
tests/Images/External

@ -1 +1 @@
Subproject commit 8b43d14d21ce9b436af3d12a70d38402cdba176b
Subproject commit 346070e5ba538f1a3bbafc0ea7367404c5f8c9ab

3
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
Loading…
Cancel
Save