Browse Source

Merge branch 'master' into tiff-format

pull/1570/head
James Jackson-South 6 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.ColorSpaces.Companding namespace SixLabors.ImageSharp.ColorSpaces.Companding
{ {
@ -18,21 +22,83 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// </remarks> /// </remarks>
public static class SRgbCompanding 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> /// <summary>
/// Expands the companded vectors to their linear equivalents with respect to the energy. /// Expands the companded vectors to their linear equivalents with respect to the energy.
/// </summary> /// </summary>
/// <param name="vectors">The span of vectors.</param> /// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(Span<Vector4> vectors) public static void Expand(Span<Vector4> vectors)
{ {
ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); if (Avx2.IsSupported && vectors.Length >= 2)
while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd))
{ {
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. /// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy.
/// </summary> /// </summary>
/// <param name="vectors">The span of vectors.</param> /// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(Span<Vector4> vectors) public static unsafe void Compress(Span<Vector4> vectors)
{ {
ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors); #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length); if (Avx2.IsSupported && vectors.Length >= 2)
while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd))
{ {
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. /// Expands a companded vector to its linear equivalent with respect to the energy.
/// </summary> /// </summary>
/// <param name="vector">The vector.</param> /// <param name="vector">The vector.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(ref Vector4 vector) 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.X = Expand(vector.X);
vector.Y = Expand(vector.Y); vector.Y = Expand(vector.Y);
vector.Z = Expand(vector.Z); vector.Z = Expand(vector.Z);
@ -70,9 +144,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// Compresses an uncompanded vector (linear) to its nonlinear equivalent. /// Compresses an uncompanded vector (linear) to its nonlinear equivalent.
/// </summary> /// </summary>
/// <param name="vector">The vector.</param> /// <param name="vector">The vector.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(ref Vector4 vector) 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.X = Compress(vector.X);
vector.Y = Compress(vector.Y); vector.Y = Compress(vector.Y);
vector.Z = Compress(vector.Z); vector.Z = Compress(vector.Z);
@ -83,15 +158,84 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// </summary> /// </summary>
/// <param name="channel">The channel value.</param> /// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the linear channel value.</returns> /// <returns>The <see cref="float"/> representing the linear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F); public static float Expand(float channel)
=> channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
/// <summary> /// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent. /// Compresses an uncompanded channel (linear) to its nonlinear equivalent.
/// </summary> /// </summary>
/// <param name="channel">The channel value.</param> /// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the nonlinear channel value.</returns> /// <returns>The <see cref="float"/> representing the nonlinear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F; 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 internal static class Numerics
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #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; private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif #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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace. /// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
/// Methods to build the tables are based on libjpeg implementation. /// 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> /// </summary>
internal unsafe struct RgbToYCbCrTables internal unsafe struct RgbToYCbCrConverterLut
{ {
/// <summary> /// <summary>
/// The red luminance table /// The red luminance table
@ -63,10 +64,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// Initializes the YCbCr tables /// Initializes the YCbCr tables
/// </summary> /// </summary>
/// <returns>The initialized <see cref="RgbToYCbCrTables"/></returns> /// <returns>The initialized <see cref="RgbToYCbCrConverterLut"/></returns>
public static RgbToYCbCrTables Create() public static RgbToYCbCrConverterLut Create()
{ {
RgbToYCbCrTables tables = default; RgbToYCbCrConverterLut tables = default;
for (int i = 0; i <= 255; i++) for (int i = 0; i <= 255; i++)
{ {
@ -92,11 +93,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
/// <summary> /// <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. /// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ConvertPixelInto( private void ConvertPixelInto(
int r, int r,
int g, int g,
int b, 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)); // float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits; 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; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x) private static int Fix(float x)
=> (int)((x * (1L << ScaleBits)) + 0.5F); => (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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -33,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// The color conversion tables /// The color conversion tables
/// </summary> /// </summary>
private RgbToYCbCrTables colorTables; private RgbToYCbCrConverterLut colorTables;
/// <summary> /// <summary>
/// Temporal 8x8 block to hold TPixel data /// Temporal 8x8 block to hold TPixel data
@ -48,7 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public static YCbCrForwardConverter<TPixel> Create() public static YCbCrForwardConverter<TPixel> Create()
{ {
var result = default(YCbCrForwardConverter<TPixel>); 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; return result;
} }
@ -65,20 +69,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F yBlock = ref this.Y; ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb; ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr; 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); RgbToYCbCrConverterVectorized.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
this.colorTables.ConvertPixelInto( else
c.R, {
c.G, this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
c.B,
ref yBlock,
ref cbBlock,
ref crBlock,
i);
} }
} }
} }

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. // Licensed under the Apache License, Version 2.0.
using System; 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.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -96,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex);
// Length of reduced palette + transparency. // 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.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.palette = result; this.palette = result;

106
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs

@ -4,6 +4,11 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; 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 namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{ {
@ -61,28 +66,99 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns>The weighted sum</returns> /// <returns>The weighted sum</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Vector4 Convolve(Span<Vector4> rowSpan) public Vector4 Convolve(Span<Vector4> rowSpan)
{ => this.ConvolveCore(ref rowSpan[this.StartIndex]);
return this.ConvolveCore(ref rowSpan[this.StartIndex]);
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Vector4 ConvolveCore(ref Vector4 rowStartRef) 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 while (bufferStart < bufferEnd)
Vector4 result = Vector4.Zero; {
// 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++) result256_1 = Fma.MultiplyAdd(
{ Unsafe.As<Vector4, Vector256<float>>(ref Unsafe.Add(ref rowStartRef, 2)),
float weight = Unsafe.Add(ref horizontalValues, i); 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]; Vector128<float> result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper());
Vector4 v = Unsafe.Add(ref rowStartRef, i);
result += v * weight; 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> /// <summary>
@ -91,9 +167,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel AlterLeftValue(int left) internal ResizeKernel AlterLeftValue(int left)
{ => new ResizeKernel(left, this.bufferPtr, this.Length);
return new ResizeKernel(left, this.bufferPtr, this.Length);
}
internal void Fill(Span<double> values) 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.DestinationHeight = size.Height;
this.DestinationRectangle = rectangle; this.DestinationRectangle = rectangle;
this.Compand = options.Compand; this.Compand = options.Compand;
this.PremultiplyAlpha = options.PremultiplyAlpha;
} }
/// <summary> /// <summary>
@ -53,6 +54,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary> /// </summary>
public bool Compand { get; } 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 /> /// <inheritdoc />
public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public override ICloningImageProcessor<TPixel> CreatePixelSpecificCloningProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
=> new ResizeProcessor<TPixel>(configuration, this, source, 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 IResampler resampler;
private readonly Rectangle destinationRectangle; private readonly Rectangle destinationRectangle;
private readonly bool compand; private readonly bool compand;
private readonly bool premultiplyAlpha;
private Image<TPixel> destination; private Image<TPixel> destination;
public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle) 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.destinationHeight = definition.DestinationHeight;
this.destinationRectangle = definition.DestinationRectangle; this.destinationRectangle = definition.DestinationRectangle;
this.resampler = definition.Sampler; this.resampler = definition.Sampler;
this.premultiplyAlpha = definition.PremultiplyAlpha;
this.compand = definition.Compand; this.compand = definition.Compand;
} }
@ -60,6 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle sourceRectangle = this.SourceRectangle; Rectangle sourceRectangle = this.SourceRectangle;
Rectangle destinationRectangle = this.destinationRectangle; Rectangle destinationRectangle = this.destinationRectangle;
bool compand = this.compand; bool compand = this.compand;
bool premultiplyAlpha = this.premultiplyAlpha;
// Handle resize dimensions identical to the original // Handle resize dimensions identical to the original
if (source.Width == destination.Width if (source.Width == destination.Width
@ -128,7 +131,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
sourceRectangle, sourceRectangle,
destinationRectangle, destinationRectangle,
interest, interest,
compand); compand,
premultiplyAlpha);
} }
} }
@ -159,6 +163,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
in operation); 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( private static void ApplyResizeFrameTransform(
Configuration configuration, Configuration configuration,
ImageFrame<TPixel> source, ImageFrame<TPixel> source,
@ -168,10 +184,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle sourceRectangle, Rectangle sourceRectangle,
Rectangle destinationRectangle, Rectangle destinationRectangle,
Rectangle interest, Rectangle interest,
bool compand) bool compand,
bool premultiplyAlpha)
{ {
PixelConversionModifiers conversionModifiers = PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha);
PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
Buffer2DRegion<TPixel> sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle); 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)] [MethodImpl(InliningOptions.ShortMethod)]
public Span<Vector4> GetColumnSpan(int x, int startY) public Span<Vector4> GetColumnSpan(int x, int startY)
{ => this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
}
public void Initialize() public void Initialize()
{ => this.CalculateFirstPassValues(this.currentWindow);
this.CalculateFirstPassValues(this.currentWindow);
}
public void FillDestinationPixels(RowInterval rowInterval, Buffer2D<TPixel> destination) 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. /// Gets or sets the target rectangle to resize into.
/// </summary> /// </summary>
public Rectangle? TargetRectangle { get; set; } 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 namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodeBmp : BenchmarkBase public class DecodeBmp
{ {
private byte[] bmpBytes; private byte[] bmpBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); private string TestImageFullPath
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages()
@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")] [Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public SDSize BmpSystemDrawing() public SDSize BmpSystemDrawing()
{ {
using (var memoryStream = new MemoryStream(this.bmpBytes)) using var memoryStream = new MemoryStream(this.bmpBytes);
{ using var image = SDImage.FromStream(memoryStream);
using (var image = SDImage.FromStream(memoryStream)) return image.Size;
{
return image.Size;
}
}
} }
[Benchmark(Description = "ImageSharp Bmp")] [Benchmark(Description = "ImageSharp Bmp")]
public Size BmpCore() public Size BmpImageSharp()
{ {
using (var memoryStream = new MemoryStream(this.bmpBytes)) using var memoryStream = new MemoryStream(this.bmpBytes);
{ using var image = Image.Load<Rgba32>(memoryStream);
using (var image = Image.Load<Rgba32>(memoryStream)) return new Size(image.Width, image.Height);
{
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 BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using CoreSize = SixLabors.ImageSharp.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodeFilteredPng : BenchmarkBase public class DecodeFilteredPng
{ {
private byte[] filter0; private byte[] filter0;
private byte[] filter1; private byte[] filter1;
@ -30,44 +29,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
} }
[Benchmark(Baseline = true, Description = "None-filtered PNG file")] [Benchmark(Baseline = true, Description = "None-filtered PNG file")]
public CoreSize PngFilter0() public Size PngFilter0()
{ => LoadPng(this.filter0);
return LoadPng(this.filter0);
}
[Benchmark(Description = "Sub-filtered PNG file")] [Benchmark(Description = "Sub-filtered PNG file")]
public CoreSize PngFilter1() public Size PngFilter1()
{ => LoadPng(this.filter1);
return LoadPng(this.filter1);
}
[Benchmark(Description = "Up-filtered PNG file")] [Benchmark(Description = "Up-filtered PNG file")]
public CoreSize PngFilter2() public Size PngFilter2()
{ => LoadPng(this.filter2);
return LoadPng(this.filter2);
}
[Benchmark(Description = "Average-filtered PNG file")] [Benchmark(Description = "Average-filtered PNG file")]
public CoreSize PngFilter3() public Size PngFilter3()
{ => LoadPng(this.filter3);
return LoadPng(this.filter3);
}
[Benchmark(Description = "Paeth-filtered PNG file")] [Benchmark(Description = "Paeth-filtered PNG file")]
public CoreSize PngFilter4() public Size PngFilter4()
{ => LoadPng(this.filter4);
return LoadPng(this.filter4);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static CoreSize LoadPng(byte[] bytes) private static Size LoadPng(byte[] bytes)
{ {
using (var image = Image.Load<Rgba32>(bytes)) using var image = Image.Load<Rgba32>(bytes);
{ return image.Size();
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 namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodeGif : BenchmarkBase public class DecodeGif
{ {
private byte[] gifBytes; private byte[] gifBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); private string TestImageFullPath
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages()
@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Gif")] [Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public SDSize GifSystemDrawing() public SDSize GifSystemDrawing()
{ {
using (var memoryStream = new MemoryStream(this.gifBytes)) using var memoryStream = new MemoryStream(this.gifBytes);
{ using var image = SDImage.FromStream(memoryStream);
using (var image = SDImage.FromStream(memoryStream)) return image.Size;
{
return image.Size;
}
}
} }
[Benchmark(Description = "ImageSharp Gif")] [Benchmark(Description = "ImageSharp Gif")]
public Size GifCore() public Size GifImageSharp()
{ {
using (var memoryStream = new MemoryStream(this.gifBytes)) using var memoryStream = new MemoryStream(this.gifBytes);
{ using var image = Image.Load<Rgba32>(memoryStream);
using (var image = Image.Load<Rgba32>(memoryStream)) return new Size(image.Width, image.Height);
{
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 namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodePng : BenchmarkBase public class DecodePng
{ {
private byte[] pngBytes; 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)] [Params(TestImages.Png.Splash)]
public string TestImage { get; set; } public string TestImage { get; set; }
@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Png")] [Benchmark(Baseline = true, Description = "System.Drawing Png")]
public SDSize PngSystemDrawing() public SDSize PngSystemDrawing()
{ {
using (var memoryStream = new MemoryStream(this.pngBytes)) using var memoryStream = new MemoryStream(this.pngBytes);
{ using var image = SDImage.FromStream(memoryStream);
using (var image = SDImage.FromStream(memoryStream)) return image.Size;
{
return image.Size;
}
}
} }
[Benchmark(Description = "ImageSharp Png")] [Benchmark(Description = "ImageSharp Png")]
public Size PngCore() public Size PngImageSharp()
{ {
using (var memoryStream = new MemoryStream(this.pngBytes)) using var memoryStream = new MemoryStream(this.pngBytes);
{ using var image = Image.Load<Rgba32>(memoryStream);
using (var image = Image.Load<Rgba32>(memoryStream)) return image.Size();
{
return image.Size();
}
}
} }
} }
} }

34
tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs

@ -5,7 +5,6 @@ using System.Buffers;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using ImageMagick; using ImageMagick;
using Pfim; using Pfim;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
@ -14,8 +13,8 @@ using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodeTga : BenchmarkBase public class DecodeTga
{ {
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
@ -28,36 +27,28 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[GlobalSetup] [GlobalSetup]
public void SetupData() public void SetupData()
{ => this.data = File.ReadAllBytes(this.TestImageFullPath);
this.data = File.ReadAllBytes(this.TestImageFullPath);
}
[Benchmark(Baseline = true, Description = "ImageMagick Tga")] [Benchmark(Baseline = true, Description = "ImageMagick Tga")]
public int TgaImageMagick() public int TgaImageMagick()
{ {
var settings = new MagickReadSettings { Format = MagickFormat.Tga }; var settings = new MagickReadSettings { Format = MagickFormat.Tga };
using (var image = new MagickImage(new MemoryStream(this.data), settings)) using var image = new MagickImage(new MemoryStream(this.data), settings);
{ return image.Width;
return image.Width;
}
} }
[Benchmark(Description = "ImageSharp Tga")] [Benchmark(Description = "ImageSharp Tga")]
public int TgaCore() public int TgaImageSharp()
{ {
using (var image = Image.Load<Bgr24>(this.data, new TgaDecoder())) using var image = Image.Load<Bgr24>(this.data, new TgaDecoder());
{ return image.Width;
return image.Width;
}
} }
[Benchmark(Description = "Pfim Tga")] [Benchmark(Description = "Pfim Tga")]
public int TgaPfim() public int TgaPfim()
{ {
using (var image = Targa.Create(this.data, this.pfimConfig)) using var image = Targa.Create(this.data, this.pfimConfig);
{ return image.Width;
return image.Width;
}
} }
private class PfimAllocator : IImageAllocator private class PfimAllocator : IImageAllocator
@ -65,10 +56,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private int rented; private int rented;
private readonly ArrayPool<byte> shared = ArrayPool<byte>.Shared; private readonly ArrayPool<byte> shared = ArrayPool<byte>.Shared;
public byte[] Rent(int size) public byte[] Rent(int size) => this.shared.Rent(size);
{
return this.shared.Rent(size);
}
public void Return(byte[] data) 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. // Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging; using System.Drawing.Imaging;
@ -10,8 +10,8 @@ using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodeBmp : BenchmarkBase public class EncodeBmp
{ {
private Stream bmpStream; private Stream bmpStream;
private SDImage bmpDrawing; private SDImage bmpDrawing;
@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup() public void Cleanup()
{ {
this.bmpStream.Dispose(); this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose(); this.bmpCore.Dispose();
this.bmpDrawing.Dispose(); this.bmpDrawing.Dispose();
} }
@ -40,19 +41,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")] [Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public void BmpSystemDrawing() public void BmpSystemDrawing()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp);
this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp);
}
} }
[Benchmark(Description = "ImageSharp Bmp")] [Benchmark(Description = "ImageSharp Bmp")]
public void BmpCore() public void BmpImageSharp()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ this.bmpCore.SaveAsBmp(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 namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{ {
protected override IEnumerable<string> InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; protected override IEnumerable<string> InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" };
[Benchmark(Description = "EncodeBmpMultiple - ImageSharp")] [Benchmark(Description = "EncodeBmpMultiple - ImageSharp")]
public void EncodeBmpImageSharp() public void EncodeBmpImageSharp()
{ => this.ForEachImageSharpImage((img, ms) =>
this.ForEachImageSharpImage((img, ms) =>
{ {
img.Save(ms, new BmpEncoder()); img.Save(ms, new BmpEncoder());
return null; return null;
}); });
}
[Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")] [Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")]
public void EncodeBmpSystemDrawing() public void EncodeBmpSystemDrawing()
{ => this.ForEachSystemDrawingImage((img, ms) =>
this.ForEachSystemDrawingImage((img, ms) =>
{ {
img.Save(ms, ImageFormat.Bmp); img.Save(ms, ImageFormat.Bmp);
return null; return null;
}); });
}
} }
} }

19
tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs

@ -13,8 +13,8 @@ using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodeGif : BenchmarkBase public class EncodeGif
{ {
// System.Drawing needs this. // System.Drawing needs this.
private Stream bmpStream; private Stream bmpStream;
@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup() public void Cleanup()
{ {
this.bmpStream.Dispose(); this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose(); this.bmpCore.Dispose();
this.bmpDrawing.Dispose(); this.bmpDrawing.Dispose();
} }
@ -53,19 +54,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Gif")] [Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public void GifSystemDrawing() public void GifSystemDrawing()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ this.bmpDrawing.Save(memoryStream, ImageFormat.Gif);
this.bmpDrawing.Save(memoryStream, ImageFormat.Gif);
}
} }
[Benchmark(Description = "ImageSharp Gif")] [Benchmark(Description = "ImageSharp Gif")]
public void GifCore() public void GifImageSharp()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ this.bmpCore.SaveAsGif(memoryStream, this.encoder);
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 namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{ {
[Params(InputImageCategory.AllImages)] [Params(InputImageCategory.AllImages)]
@ -20,8 +20,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "EncodeGifMultiple - ImageSharp")] [Benchmark(Description = "EncodeGifMultiple - ImageSharp")]
public void EncodeGifImageSharp() public void EncodeGifImageSharp()
{ => this.ForEachImageSharpImage((img, ms) =>
this.ForEachImageSharpImage((img, ms) =>
{ {
// Try to get as close to System.Drawing's output as possible // Try to get as close to System.Drawing's output as possible
var options = new GifEncoder var options = new GifEncoder
@ -32,16 +31,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
img.Save(ms, options); img.Save(ms, options);
return null; return null;
}); });
}
[Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")] [Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")]
public void EncodeGifSystemDrawing() public void EncodeGifSystemDrawing()
{ => this.ForEachSystemDrawingImage((img, ms) =>
this.ForEachSystemDrawingImage((img, ms) =>
{ {
img.Save(ms, ImageFormat.Gif); img.Save(ms, ImageFormat.Gif);
return null; 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;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
/// <summary> /// <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> /// </summary>
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodeIndexedPng : BenchmarkBase public class EncodeIndexedPng
{ {
// System.Drawing needs this. // System.Drawing needs this.
private Stream bmpStream; private Stream bmpStream;
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
if (this.bmpStream == null) if (this.bmpStream == null)
{ {
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)); 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; this.bmpStream.Position = 0;
} }
} }
@ -37,67 +37,56 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup() public void Cleanup()
{ {
this.bmpStream.Dispose(); this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose(); this.bmpCore.Dispose();
} }
[Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")]
public void PngCoreOctree() public void PngCoreOctree()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ var options = new PngEncoder { Quantizer = KnownQuantizers.Octree };
var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; this.bmpCore.SaveAsPng(memoryStream, options);
this.bmpCore.SaveAsPng(memoryStream, options);
}
} }
[Benchmark(Description = "ImageSharp Octree NoDither Png")] [Benchmark(Description = "ImageSharp Octree NoDither Png")]
public void PngCoreOctreeNoDither() public void PngCoreOctreeNoDither()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) };
var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options);
this.bmpCore.SaveAsPng(memoryStream, options);
}
} }
[Benchmark(Description = "ImageSharp Palette Png")] [Benchmark(Description = "ImageSharp Palette Png")]
public void PngCorePalette() public void PngCorePalette()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe };
var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; this.bmpCore.SaveAsPng(memoryStream, options);
this.bmpCore.SaveAsPng(memoryStream, options);
}
} }
[Benchmark(Description = "ImageSharp Palette NoDither Png")] [Benchmark(Description = "ImageSharp Palette NoDither Png")]
public void PngCorePaletteNoDither() public void PngCorePaletteNoDither()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) };
var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options);
this.bmpCore.SaveAsPng(memoryStream, options);
}
} }
[Benchmark(Description = "ImageSharp Wu Png")] [Benchmark(Description = "ImageSharp Wu Png")]
public void PngCoreWu() public void PngCoreWu()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ var options = new PngEncoder { Quantizer = KnownQuantizers.Wu };
var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; this.bmpCore.SaveAsPng(memoryStream, options);
this.bmpCore.SaveAsPng(memoryStream, options);
}
} }
[Benchmark(Description = "ImageSharp Wu NoDither Png")] [Benchmark(Description = "ImageSharp Wu NoDither Png")]
public void PngCoreWuNoDither() public void PngCoreWuNoDither()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) };
var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; this.bmpCore.SaveAsPng(memoryStream, options);
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 namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodePng : BenchmarkBase public class EncodePng
{ {
// System.Drawing needs this. // System.Drawing needs this.
private Stream bmpStream; private Stream bmpStream;
@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup() public void Cleanup()
{ {
this.bmpStream.Dispose(); this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose(); this.bmpCore.Dispose();
this.bmpDrawing.Dispose(); this.bmpDrawing.Dispose();
} }
@ -46,20 +47,16 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Png")] [Benchmark(Baseline = true, Description = "System.Drawing Png")]
public void PngSystemDrawing() public void PngSystemDrawing()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ this.bmpDrawing.Save(memoryStream, ImageFormat.Png);
this.bmpDrawing.Save(memoryStream, ImageFormat.Png);
}
} }
[Benchmark(Description = "ImageSharp Png")] [Benchmark(Description = "ImageSharp Png")]
public void PngCore() public void PngCore()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None };
var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; this.bmpCore.SaveAsPng(memoryStream, encoder);
this.bmpCore.SaveAsPng(memoryStream, encoder);
}
} }
} }
} }

34
tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs

@ -2,23 +2,21 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using ImageMagick; using ImageMagick;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class EncodeTga : BenchmarkBase public class EncodeTga
{ {
private MagickImage tgaMagick; private MagickImage tgaMagick;
private Image<Rgba32> tgaCore; 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)] [Params(TestImages.Tga.Bit24BottomLeft)]
public string TestImage { get; set; } 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")] [Benchmark(Baseline = true, Description = "Magick Tga")]
public void BmpSystemDrawing() public void BmpImageMagick()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
}
} }
[Benchmark(Description = "ImageSharp Tga")] [Benchmark(Description = "ImageSharp Tga")]
public void BmpCore() public void BmpImageSharp()
{ {
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ this.tgaCore.SaveAsBmp(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. // Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class CmykColorConversion : ColorConversionBenchmark public class CmykColorConversion : ColorConversionBenchmark
{ {
public CmykColorConversion() public CmykColorConversion()
@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Scalar() 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] [Benchmark]
public void SimdVector8() 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] [Benchmark]
public void SimdVectorAvx2() 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. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -11,27 +11,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public abstract class ColorConversionBenchmark public abstract class ColorConversionBenchmark
{ {
private readonly int componentCount; private readonly int componentCount;
protected Buffer2D<float>[] input;
protected Vector4[] output; public const int Count = 128;
protected ColorConversionBenchmark(int componentCount) 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] [GlobalSetup]
public void Setup() public void Setup()
{ {
this.input = CreateRandomValues(this.componentCount, Count); this.Input = CreateRandomValues(this.componentCount, Count);
this.output = new Vector4[Count]; this.Output = new Vector4[Count];
} }
[GlobalCleanup] [GlobalCleanup]
public void Cleanup() public void Cleanup()
{ {
foreach (Buffer2D<float> buffer in this.input) foreach (Buffer2D<float> buffer in this.Input)
{ {
buffer.Dispose(); buffer.Dispose();
} }

7
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs

@ -3,7 +3,6 @@
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
@ -11,7 +10,7 @@ using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodeJpegParseStreamOnly public class DecodeJpegParseStreamOnly
{ {
[Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)] [Params(TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr)]
@ -23,9 +22,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ => this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
}
[Benchmark(Baseline = true, Description = "System.Drawing FULL")] [Benchmark(Baseline = true, Description = "System.Drawing FULL")]
public SDSize JpegSystemDrawing() public SDSize JpegSystemDrawing()

16
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs

@ -2,22 +2,20 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image; using SDImage = System.Drawing.Image;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
/// <summary> /// <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> /// </summary>
[Config(typeof(MultiImageBenchmarkBase.Config))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase
{ {
protected override IEnumerable<string> InputImageSubfoldersOrFiles => protected override IEnumerable<string> InputImageSubfoldersOrFiles =>
@ -35,14 +33,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark] [Benchmark]
public void ImageSharp() public void ImageSharp()
{ => this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
}
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void SystemDrawing() 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 System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
@ -20,22 +15,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
/// <summary> /// <summary>
/// Image-specific Jpeg benchmarks /// Image-specific Jpeg benchmarks
/// </summary> /// </summary>
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class DecodeJpeg_ImageSpecific 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 byte[] jpegBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); 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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
using System.Drawing; public class EncodeJpeg
using System.Drawing.Imaging;
using System.IO;
using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
public class EncodeJpeg : BenchmarkBase
{ {
// System.Drawing needs this. // System.Drawing needs this.
private Stream bmpStream; private Stream bmpStream;
private Image bmpDrawing; private SDImage bmpDrawing;
private Image<Rgba32> bmpCore; private Image<Rgba32> bmpCore;
[GlobalSetup] [GlobalSetup]
@ -27,9 +24,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
const string TestImage = TestImages.Bmp.Car; const string TestImage = TestImages.Bmp.Car;
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); 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.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() public void Cleanup()
{ {
this.bmpStream.Dispose(); this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose(); this.bmpCore.Dispose();
this.bmpDrawing.Dispose(); this.bmpDrawing.Dispose();
} }
@ -44,19 +42,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] [Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public void JpegSystemDrawing() public void JpegSystemDrawing()
{ {
using (var stream = new MemoryStream()) using var stream = new MemoryStream();
{ this.bmpDrawing.Save(stream, ImageFormat.Jpeg);
this.bmpDrawing.Save(stream, ImageFormat.Jpeg);
}
} }
[Benchmark(Description = "ImageSharp Jpeg")] [Benchmark(Description = "ImageSharp Jpeg")]
public void JpegCore() public void JpegCore()
{ {
using (var stream = new MemoryStream()) using var stream = new MemoryStream();
{ this.bmpCore.SaveAsJpeg(stream);
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 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 public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{ {
protected override IEnumerable<string> InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" }; protected override IEnumerable<string> InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" };
@ -17,22 +17,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Description = "EncodeJpegMultiple - ImageSharp")] [Benchmark(Description = "EncodeJpegMultiple - ImageSharp")]
public void EncodeJpegImageSharp() public void EncodeJpegImageSharp()
{ => this.ForEachImageSharpImage((img, ms) =>
this.ForEachImageSharpImage((img, ms) =>
{ {
img.Save(ms, new JpegEncoder()); img.Save(ms, new JpegEncoder());
return null; return null;
}); });
}
[Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")] [Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")]
public void EncodeJpegSystemDrawing() public void EncodeJpegSystemDrawing()
{ => this.ForEachSystemDrawingImage((img, ms) =>
this.ForEachSystemDrawingImage((img, ms) =>
{ {
img.Save(ms, ImageFormat.Jpeg); img.Save(ms, ImageFormat.Jpeg);
return null; 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 namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class GrayscaleColorConversion : ColorConversionBenchmark public class GrayscaleColorConversion : ColorConversionBenchmark
{ {
public GrayscaleColorConversion() public GrayscaleColorConversion()
@ -17,17 +17,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Scalar() 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] [Benchmark]
public void SimdVectorAvx2() 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. // Licensed under the Apache License, Version 2.0.
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class IdentifyJpeg public class IdentifyJpeg
{ {
private byte[] jpegBytes; private byte[] jpegBytes;
@ -31,11 +30,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark] [Benchmark]
public IImageInfo Identify() public IImageInfo Identify()
{ {
using (var memoryStream = new MemoryStream(this.jpegBytes)) using var memoryStream = new MemoryStream(this.jpegBytes);
{ var decoder = new JpegDecoder();
var decoder = new JpegDecoder(); return decoder.Identify(Configuration.Default, memoryStream);
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.Drawing2D;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -17,7 +15,7 @@ using SixLabors.ImageSharp.Tests;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(MultiImageBenchmarkBase.Config))] [Config(typeof(Config.ShortMultiFramework))]
public class LoadResizeSave_Aggregate : MultiImageBenchmarkBase public class LoadResizeSave_Aggregate : MultiImageBenchmarkBase
{ {
protected override IEnumerable<string> InputImageSubfoldersOrFiles => protected override IEnumerable<string> InputImageSubfoldersOrFiles =>
@ -48,49 +46,43 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void SystemDrawing() public void SystemDrawing()
{ => this.ForEachStream(
this.ForEachStream(
sourceStream => 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 g = Graphics.FromImage(destination))
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)) g.InterpolationMode = InterpolationMode.HighQualityBicubic;
{ g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.CompositingQuality = CompositingQuality.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.DrawImage(source, 0, 0, 400, 400);
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(source, 0, 0, 400, 400);
}
destination.Save(destStream, ImageFormat.Jpeg);
} }
return null; destination.Save(destStream, ImageFormat.Jpeg);
}); }
}
return null;
});
[Benchmark] [Benchmark]
public void ImageSharp() public void ImageSharp()
{ => this.ForEachStream(
this.ForEachStream(
sourceStream => sourceStream =>
{
using (var source = Image.Load<Rgba32>(
this.configuration,
sourceStream,
new JpegDecoder { IgnoreMetadata = true }))
{ {
using (var source = Image.Load<Rgba32>( using var destStream = new MemoryStream(this.destBytes);
this.configuration, source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4));
sourceStream, source.SaveAsJpeg(destStream);
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);
}
}
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.Drawing.Imaging;
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image; using SDImage = System.Drawing.Image;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class LoadResizeSave_ImageSpecific public class LoadResizeSave_ImageSpecific
{ {
private readonly Configuration configuration = new Configuration(new JpegConfigurationModule()); 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.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr, TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr,
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)] TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)]
public string TestImage { get; set; } public string TestImage { get; set; }
[Params(false, true)] [Params(false, true)]
@ -51,28 +50,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void SystemDrawing() public void SystemDrawing()
{ {
using (var sourceStream = new MemoryStream(this.sourceBytes)) using var sourceStream = new MemoryStream(this.sourceBytes);
using (var destStream = new MemoryStream(this.destBytes)) using var destStream = new MemoryStream(this.destBytes);
using (var source = SDImage.FromStream(sourceStream)) using var source = SDImage.FromStream(sourceStream);
using (var destination = new Bitmap(source.Width / 4, source.Height / 4)) 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.InterpolationMode = InterpolationMode.HighQualityBicubic; g.CompositingQuality = CompositingQuality.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.DrawImage(source, 0, 0, 400, 400);
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(source, 0, 0, 400, 400);
}
destination.Save(destStream, ImageFormat.Jpeg);
} }
destination.Save(destStream, ImageFormat.Jpeg);
} }
[Benchmark] [Benchmark]
public void ImageSharp() public void ImageSharp()
{ {
var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true }); using (var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true }))
using (source)
using (var destStream = new MemoryStream(this.destBytes)) using (var destStream = new MemoryStream(this.destBytes))
{ {
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4)); 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 namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class RgbColorConversion : ColorConversionBenchmark public class RgbColorConversion : ColorConversionBenchmark
{ {
public RgbColorConversion() public RgbColorConversion()
@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Scalar() 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] [Benchmark]
public void SimdVector8() 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] [Benchmark]
public void SimdVectorAvx2() 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. // Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class YCbCrColorConversion : ColorConversionBenchmark public class YCbCrColorConversion : ColorConversionBenchmark
{ {
public YCbCrColorConversion() public YCbCrColorConversion()
@ -18,33 +17,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark] [Benchmark]
public void Scalar() 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)] [Benchmark(Baseline = true)]
public void SimdVector() 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] [Benchmark]
public void SimdVector8() 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] [Benchmark]
public void SimdVectorAvx2() 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 namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class YccKColorConverter : ColorConversionBenchmark public class YccKColorConverter : ColorConversionBenchmark
{ {
public YccKColorConverter() public YccKColorConverter()
@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Scalar() 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] [Benchmark]
public void SimdVector8() 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] [Benchmark]
public void SimdVectorAvx2() 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.Linq;
using System.Numerics; using System.Numerics;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
public abstract class MultiImageBenchmarkBase 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, byte[]> FileNamesToBytes { get; set; } = new Dictionary<string, byte[]>();
protected Dictionary<string, Image<Rgba32>> FileNamesToImageSharpImages { get; set; } = new Dictionary<string, Image<Rgba32>>(); 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> /// <summary>
/// The values of this enum separate input files into categories. /// The values of this enum separate input files into categories.
@ -72,7 +57,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
/// <summary> /// <summary>
/// Gets folders containing files OR files to be processed by the benchmark. /// Gets folders containing files OR files to be processed by the benchmark.
/// </summary> /// </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> /// <summary>
/// Gets the large image threshold. /// Gets the large image threshold.
@ -83,19 +69,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
protected IEnumerable<KeyValuePair<string, T>> EnumeratePairsByBenchmarkSettings<T>( protected IEnumerable<KeyValuePair<string, T>> EnumeratePairsByBenchmarkSettings<T>(
Dictionary<string, T> input, Dictionary<string, T> input,
Predicate<T> checkIfSmall) Predicate<T> checkIfSmall)
{ => this.InputCategory switch
switch (this.InputCategory)
{ {
case InputImageCategory.AllImages: InputImageCategory.AllImages => input,
return input; InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)),
case InputImageCategory.SmallImagesOnly: InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)),
return input.Where(kv => checkIfSmall(kv.Value)); _ => throw new ArgumentOutOfRangeException(),
case InputImageCategory.LargeImagesOnly: };
return input.Where(kv => !checkIfSmall(kv.Value));
default:
throw new ArgumentOutOfRangeException();
}
}
protected IEnumerable<KeyValuePair<string, byte[]>> FileNames2Bytes protected IEnumerable<KeyValuePair<string, byte[]>> FileNames2Bytes
=> =>
@ -150,17 +130,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{ {
foreach (KeyValuePair<string, byte[]> kv in this.FileNames2Bytes) 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();
object obj = operation(memoryStream); }
(obj as IDisposable)?.Dispose(); catch (Exception ex)
} {
catch (Exception ex) Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
{
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)) 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)); this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes));
@ -191,7 +169,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
this.FileNamesToImageSharpImages, this.FileNamesToImageSharpImages,
img => img.Width * img.Height < this.LargeImageThresholdInPixels); img => img.Width * img.Height < this.LargeImageThresholdInPixels);
protected IEnumerable<KeyValuePair<string, System.Drawing.Bitmap>> FileNames2SystemDrawingImages protected IEnumerable<KeyValuePair<string, Bitmap>> FileNames2SystemDrawingImages
=> =>
this.EnumeratePairsByBenchmarkSettings( this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToSystemDrawingImages, this.FileNamesToSystemDrawingImages,
@ -217,22 +195,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
protected void ForEachImageSharpImage(Func<Image<Rgba32>, MemoryStream, object> operation) protected void ForEachImageSharpImage(Func<Image<Rgba32>, MemoryStream, object> operation)
{ {
using (var workStream = new MemoryStream()) using var workStream = new MemoryStream();
{ this.ForEachImageSharpImage(
this.ForEachImageSharpImage( img =>
img => {
{ // ReSharper disable AccessToDisposedClosure
// ReSharper disable AccessToDisposedClosure object result = operation(img, workStream);
object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin);
workStream.Seek(0, SeekOrigin.Begin);
// ReSharper restore AccessToDisposedClosure
// ReSharper restore AccessToDisposedClosure return result;
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) 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()) using var workStream = new MemoryStream();
{ this.ForEachSystemDrawingImage(
this.ForEachSystemDrawingImage( img =>
img => {
{ // ReSharper disable AccessToDisposedClosure
// ReSharper disable AccessToDisposedClosure object result = operation(img, workStream);
object result = operation(img, workStream); workStream.Seek(0, SeekOrigin.Begin);
workStream.Seek(0, SeekOrigin.Begin);
// ReSharper restore AccessToDisposedClosure
// ReSharper restore AccessToDisposedClosure return result;
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 namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class FromVector4_Rgb24 : FromVector4<Rgb24> 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 namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class ToVector4_Bgra32 : ToVector4<Bgra32> public class ToVector4_Bgra32 : ToVector4<Bgra32>
{ {
[Benchmark(Baseline = true)] [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 namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class ToVector4_Rgb24 : ToVector4<Rgb24> public class ToVector4_Rgb24 : ToVector4<Rgb24>
{ {
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]

8
tests/ImageSharp.Benchmarks/Config.cs

@ -35,12 +35,12 @@ namespace SixLabors.ImageSharp.Benchmarks
Job.Default.WithRuntime(CoreRuntime.Core31)); 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(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 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 namespace SixLabors.ImageSharp.Benchmarks.General
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class Adler32Benchmark public class Adler32Benchmark
{ {
private byte[] data; 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 /// - Span.CopyTo() has terrible performance on classic .NET Framework
/// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning) /// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning)
/// </summary> /// </summary>
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class CopyBuffers public class CopyBuffers
{ {
private byte[] destArray; 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 namespace SixLabors.ImageSharp.Benchmarks.General
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class Crc32Benchmark public class Crc32Benchmark
{ {
private byte[] data; 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 namespace SixLabors.ImageSharp.Benchmarks.IO
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class BufferedStreams public class BufferedStreams
{ {
private readonly byte[] buffer = CreateTestBytes(); 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 namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class UInt32ToSingle public class UInt32ToSingle
{ {
private float[] data; 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 namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.ShortMultiFramework))]
public class WidenBytesToUInt32 public class WidenBytesToUInt32
{ {
private byte[] source; private byte[] source;

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

@ -43,7 +43,7 @@
<Compile Remove="General\BasicMath\ModuloPowerOfTwoConstant.cs" /> <Compile Remove="General\BasicMath\ModuloPowerOfTwoConstant.cs" />
<Compile Remove="General\BasicMath\ModuloPowerOfTwoVariable.cs" /> <Compile Remove="General\BasicMath\ModuloPowerOfTwoVariable.cs" />
<Compile Remove="PixelBlenders\**" /> <Compile Remove="PixelBlenders\**" />
<Compile Remove="Samplers\Resize.cs" /> <Compile Remove="Processing\Resize.cs" />
</ItemGroup> </ItemGroup>
</Project> </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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers; using System.Buffers;
using System.Numerics; using System.Numerics;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.Benchmarks namespace SixLabors.ImageSharp.Benchmarks
{ {
using CoreSize = SixLabors.ImageSharp.Size; public class PorterDuffBulkVsPixel
public class PorterDuffBulkVsPixel : BenchmarkBase
{ {
private Configuration Configuration => Configuration.Default; private Configuration Configuration => Configuration.Default;
@ -30,23 +26,21 @@ namespace SixLabors.ImageSharp.Benchmarks
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer = using IMemoryOwner<Vector4> buffer =
Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3)) Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3);
{ Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length); Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length); Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, 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, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, source, sourceSpan); PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, source, sourceSpan);
for (int i = 0; i < destination.Length; i++) for (int i = 0; i < destination.Length; i++)
{ {
destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]); destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination);
} }
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination);
} }
private void BulkPixelConvert<TPixel>( private void BulkPixelConvert<TPixel>(
@ -67,44 +61,36 @@ namespace SixLabors.ImageSharp.Benchmarks
} }
[Benchmark(Description = "ImageSharp BulkVectorConvert")] [Benchmark(Description = "ImageSharp BulkVectorConvert")]
public CoreSize BulkVectorConvert() public Size BulkVectorConvert()
{ {
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);
using (IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width)) amounts.GetSpan().Fill(1);
{
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer(); Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++) for (int y = 0; y < image.Height; y++)
{ {
Span<Rgba32> span = pixels.GetRowSpan(y); Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkVectorConvert(span, span, span, amounts.GetSpan()); this.BulkVectorConvert(span, span, span, amounts.GetSpan());
}
return new CoreSize(image.Width, image.Height);
}
} }
return new Size(image.Width, image.Height);
} }
[Benchmark(Description = "ImageSharp BulkPixelConvert")] [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)) Span<Rgba32> span = pixels.GetRowSpan(y);
{ this.BulkPixelConvert(span, span, span, amounts.GetSpan());
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);
}
} }
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.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks.Samplers namespace SixLabors.ImageSharp.Benchmarks.Processing
{ {
[Config(typeof(Config.MultiFramework))] [Config(typeof(Config.MultiFramework))]
public class BokehBlur public class BokehBlur
@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark] [Benchmark]
public void Blur() public void Blur()
{ {
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White)) using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White);
{ image.Mutate(c => c.BokehBlur());
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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks namespace SixLabors.ImageSharp.Benchmarks
{ {
using System.IO; [Config(typeof(Config.MultiFramework))]
public class DetectEdges
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Processing;
using CoreImage = SixLabors.ImageSharp.Image;
public class DetectEdges : BenchmarkBase
{ {
private Image<Rgba32> image; private Image<Rgba32> image;
@ -21,10 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks
{ {
if (this.image == null) if (this.image == null)
{ {
using (FileStream stream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp")) this.image = Image.Load<Rgba32>(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)));
{
this.image = CoreImage.Load<Rgba32>(stream);
}
} }
} }
@ -32,6 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks
public void Cleanup() public void Cleanup()
{ {
this.image.Dispose(); this.image.Dispose();
this.image = null;
} }
[Benchmark(Description = "ImageSharp DetectEdges")] [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.PixelFormats;
using SixLabors.ImageSharp.Processing; 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 public class Diffuse
{ {
[Benchmark] [Benchmark]
public Size DoDiffuse() public Size DoDiffuse()
{ {
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond)) using var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond);
{ image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
return image.Size(); return image.Size();
}
} }
[Benchmark] [Benchmark]
public Size DoDither() public Size DoDither()
{ {
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond)) using var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond);
{ image.Mutate(x => x.Dither());
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] [Benchmark]
public void Blur() public void Blur()
{ {
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White)) using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White);
{ image.Mutate(c => c.GaussianBlur());
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 namespace SixLabors.ImageSharp.Benchmarks.Processing
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.MultiFramework))]
public class HistogramEqualization : BenchmarkBase public class HistogramEqualization
{ {
private Image<Rgba32> image; private Image<Rgba32> image;
@ -28,26 +28,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Processing
public void Cleanup() public void Cleanup()
{ {
this.image.Dispose(); this.image.Dispose();
this.image = null;
} }
[Benchmark(Description = "Global Histogram Equalization")] [Benchmark(Description = "Global Histogram Equalization")]
public void GlobalHistogramEqualization() public void GlobalHistogramEqualization()
{ => this.image.Mutate(img => img.HistogramEqualization(
this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions() new HistogramEqualizationOptions()
{ {
LuminanceLevels = 256, LuminanceLevels = 256,
Method = HistogramEqualizationMethod.Global Method = HistogramEqualizationMethod.Global
})); }));
}
[Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")] [Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")]
public void AdaptiveHistogramEqualization() public void AdaptiveHistogramEqualization()
{ => this.image.Mutate(img => img.HistogramEqualization(
this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions() new HistogramEqualizationOptions()
{ {
LuminanceLevels = 256, LuminanceLevels = 256,
Method = HistogramEqualizationMethod.AdaptiveTileInterpolation 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;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Globalization; using System.Globalization;
using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks namespace SixLabors.ImageSharp.Benchmarks
{ {
[Config(typeof(Config.ShortClr))] [Config(typeof(Config.MultiFramework))]
#pragma warning disable SA1649 // File name should match first type name public abstract class Resize<TPixel>
public abstract class ResizeBenchmarkBase<TPixel>
#pragma warning restore SA1649 // File name should match first type name
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule()); private byte[] bytes = null;
private Image<TPixel> sourceImage; private Image<TPixel> sourceImage;
private Bitmap sourceBitmap; private SDImage sourceBitmap;
[Params("3032-400")] protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule());
public virtual string SourceToDest { get; set; }
protected int SourceSize { get; private set; }
protected int DestSize { get; private set; } protected int DestSize { get; private set; }
[GlobalSetup] [GlobalSetup]
public virtual void Setup() public virtual void Setup()
{ {
string[] stuff = this.SourceToDest.Split('-'); if (this.bytes is null)
this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture); {
this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture); this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake));
this.sourceImage = new Image<TPixel>(this.Configuration, this.SourceSize, this.SourceSize);
this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize); 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] [GlobalCleanup]
public void Cleanup() public void Cleanup()
{ {
this.bytes = null;
this.sourceImage.Dispose(); this.sourceImage.Dispose();
this.sourceBitmap.Dispose(); this.sourceBitmap.Dispose();
} }
@ -96,12 +98,10 @@ namespace SixLabors.ImageSharp.Benchmarks
protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); 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) 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: // RESULTS - 2019 April - ResizeWorker:
// //
@ -133,9 +133,6 @@ namespace SixLabors.ImageSharp.Benchmarks
[Params(128, 512, 1024, 8 * 1024)] [Params(128, 512, 1024, 8 * 1024)]
public int WorkingBufferSizeHintInKilobytes { get; set; } public int WorkingBufferSizeHintInKilobytes { get; set; }
[Params("3032-400", "4000-300")]
public override string SourceToDest { get; set; }
public override void Setup() public override void Setup()
{ {
this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024; 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) 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): // 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 | // '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) 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): // 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 | // '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) 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): // 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.PixelFormats;
using SixLabors.ImageSharp.Processing; 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 public class Rotate
{ {
[Benchmark] [Benchmark]
public Size DoRotate() public Size DoRotate()
{ {
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond)) using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond);
{ image.Mutate(x => x.Rotate(37.5F));
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.PixelFormats;
using SixLabors.ImageSharp.Processing; 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 public class Skew
{ {
[Benchmark] [Benchmark]
public Size DoSkew() public Size DoSkew()
{ {
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond)) using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond);
{ image.Mutate(x => x.Skew(20, 10));
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) => (s, d) =>
{ {
Span<TPixel> destPixels = d.GetSpan(); 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))] [MemberData(nameof(ArraySizesData))]
public void FromCompandedScaledVector4(int count) public void FromCompandedScaledVector4(int count)
{ {
void SourceAction(ref Vector4 v) void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
{
SRgbCompanding.Expand(ref v);
}
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v);
{
SRgbCompanding.Compress(ref v);
}
Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v)); Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v));
TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v)); TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -188,7 +182,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
this.Configuration, this.Configuration,
s, s,
d.GetSpan(), d.GetSpan(),
PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale)); PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale),
false);
} }
[Theory] [Theory]
@ -219,7 +214,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected, expected,
(s, d) => (s, d) =>
{ {
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None; : PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers); this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers);
@ -254,7 +250,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected, expected,
(s, d) => (s, d) =>
{ {
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None; : PixelConversionModifiers.None;
this.Operations.FromVector4Destructive( this.Operations.FromVector4Destructive(
@ -297,7 +294,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected, expected,
(s, d) => (s, d) =>
{ {
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None; : PixelConversionModifiers.None;
this.Operations.FromVector4Destructive( this.Operations.FromVector4Destructive(
@ -305,7 +303,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
s, s,
d.GetSpan(), d.GetSpan(),
modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale); modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale);
}); },
false);
} }
[Theory] [Theory]
@ -343,7 +342,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected); 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] [Theory]
@ -356,11 +355,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
TestOperation( TestOperation(
source, source,
expected, expected,
(s, d) => (s, d) => this.Operations.ToVector4(
{ this.Configuration,
Span<Vector4> destVectors = d.GetSpan(); s,
this.Operations.ToVector4(this.Configuration, (ReadOnlySpan<TPixel>)s, destVectors, PixelConversionModifiers.Scale); d.GetSpan(),
}); PixelConversionModifiers.Scale));
} }
[Theory] [Theory]
@ -369,13 +368,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{ {
void SourceAction(ref Vector4 v) void SourceAction(ref Vector4 v)
{ {
SRgbCompanding.Compress(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
{
SRgbCompanding.Expand(ref v);
}
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(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) void SourceAction(ref Vector4 v)
{ {
Numerics.UnPremultiply(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
{
Numerics.Premultiply(ref v);
}
TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(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) void SourceAction(ref Vector4 v)
{ {
Numerics.UnPremultiply(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
{
Numerics.Premultiply(ref v);
}
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v)); TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(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) void SourceAction(ref Vector4 v)
{ {
Numerics.UnPremultiply(ref v);
SRgbCompanding.Compress(ref v);
} }
void ExpectedAction(ref Vector4 v) void ExpectedAction(ref Vector4 v)
@ -1006,15 +991,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[Theory] [Theory]
[MemberData(nameof(ArraySizesData))] [MemberData(nameof(ArraySizesData))]
public void PackFromRgbPlanes(int count) public void PackFromRgbPlanes(int count)
{ => SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
count, 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); public delegate void RefAction<T1>(ref T1 arg1);
@ -1053,11 +1032,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
internal static void TestOperation<TSource, TDest>( internal static void TestOperation<TSource, TDest>(
TSource[] source, TSource[] source,
TDest[] expected, TDest[] expected,
Action<TSource[], IMemoryOwner<TDest>> action) Action<TSource[], IMemoryOwner<TDest>> action,
bool preferExactComparison = true)
where TSource : struct where TSource : struct
where TDest : 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); action(buffers.SourceBuffer, buffers.ActualDestBuffer);
buffers.Verify(); buffers.Verify();
@ -1071,7 +1051,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++) for (int i = 0; i < result.Length; i++)
{ {
Vector4 v = GetVector(rnd); Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v); vectorModifier?.Invoke(ref v);
result[i] = v; result[i] = v;
@ -1088,7 +1068,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++) for (int i = 0; i < result.Length; i++)
{ {
Vector4 v = GetVector(rnd); Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v); vectorModifier?.Invoke(ref v);
@ -1106,7 +1086,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++) for (int i = 0; i < result.Length; i++)
{ {
Vector4 v = GetVector(rnd); Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v); vectorModifier?.Invoke(ref v);
@ -1129,10 +1109,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
return result; return result;
} }
internal static Vector4 GetVector(Random rnd) internal static Vector4 GetScaledVector(Random rnd)
{ => new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
}
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct OctetBytes internal unsafe struct OctetBytes
@ -1160,11 +1138,14 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
public TDest[] ExpectedDestBuffer { get; } 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.SourceBuffer = source;
this.ExpectedDestBuffer = expectedDest; this.ExpectedDestBuffer = expectedDest;
this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate<TDest>(expectedDest.Length); this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate<TDest>(expectedDest.Length);
this.PreferExactComparison = preferExactComparison;
} }
public void Dispose() => this.ActualDestBuffer.Dispose(); 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> expected = MemoryMarshal.Cast<TDest, Vector4>(this.ExpectedDestBuffer.AsSpan());
Span<Vector4> actual = MemoryMarshal.Cast<TDest, Vector4>(this.ActualDestBuffer.GetSpan()); 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++) for (int i = 0; i < count; i++)
{ {
// ReSharper disable PossibleNullReferenceException
Assert.Equal(expected[i], actual[i], comparer); 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 else
{ {
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan(); Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> actual = this.ActualDestBuffer.GetSpan(); Span<TDest> actual = this.ActualDestBuffer.GetSpan();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
Assert.Equal(expected[i], actual[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, testOutputDetails: workingBufferLimitInRows,
appendPixelTypeToFileName: false); appendPixelTypeToFileName: false);
image.CompareToReferenceOutput( image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.001f), ImageComparer.TolerantPercentage(0.004f),
provider, provider,
testOutputDetails: workingBufferLimitInRows, testOutputDetails: workingBufferLimitInRows,
appendPixelTypeToFileName: false); appendPixelTypeToFileName: false);
@ -216,6 +216,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
appendSourceFileOrDescription: false); 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] [Theory]
[WithFile(TestImages.Gif.Giphy, DefaultPixelType)] [WithFile(TestImages.Gif.Giphy, DefaultPixelType)]
public void Resize_IsAppliedToAllFrames<TPixel>(TestImageProvider<TPixel> provider) 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) private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel> 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 = "Gif/issues/issue405_badappextlength252.gif";
public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif"; public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif";
public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.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 }; 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.Collections.Generic;
using System.Numerics; using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests
{ {
@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests
internal readonly struct ApproximateFloatComparer : internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>, IEqualityComparer<float>,
IEqualityComparer<Vector2>, IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>, IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix> IEqualityComparer<ColorMatrix>
{ {
@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests
} }
/// <inheritdoc/> /// <inheritdoc/>
public int GetHashCode(float obj) => obj.GetHashCode(); public int GetHashCode(float obj)
=> obj.GetHashCode();
/// <inheritdoc/> /// <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/> /// <inheritdoc/>
public int GetHashCode(Vector2 obj) => obj.GetHashCode(); public int GetHashCode(Vector2 obj)
=> obj.GetHashCode();
/// <inheritdoc/> /// <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/> /// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode(); public int GetHashCode(Vector4 obj)
=> obj.GetHashCode();
/// <inheritdoc/> /// <inheritdoc/>
public bool Equals(ColorMatrix x, ColorMatrix y) public bool Equals(ColorMatrix x, ColorMatrix y)
{ => this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
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.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24) && 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.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.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); && 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/> /// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode(); 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