Browse Source

Merge branch 'tiff-format' into tiff-format

pull/1570/head
Brian Popow 5 years ago
committed by GitHub
parent
commit
d8e086c6c4
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. 2
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  5. 51
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  6. 35
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
  7. 120
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
  8. 28
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  9. 36
      src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs
  10. 4
      src/ImageSharp/Formats/Tiff/Compression/ModifiedHuffmanTiffCompression.cs
  11. 74
      src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
  12. 17
      src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs
  13. 11
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
  14. 2
      src/ImageSharp/Formats/Tiff/README.md
  15. 19
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  16. 13
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  17. 4
      src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs
  18. 3
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  19. 106
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernel.cs
  20. 6
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs
  21. 24
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs
  22. 8
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs
  23. 6
      src/ImageSharp/Processing/ResizeOptions.cs
  24. 19
      tests/ImageSharp.Benchmarks/BenchmarkBase.cs
  25. 29
      tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs
  26. 48
      tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs
  27. 29
      tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs
  28. 29
      tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs
  29. 34
      tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
  30. 72
      tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs
  31. 93
      tests/ImageSharp.Benchmarks/Codecs/DecodeTiffBig.cs
  32. 23
      tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs
  33. 10
      tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs
  34. 19
      tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs
  35. 10
      tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs
  36. 59
      tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs
  37. 19
      tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
  38. 34
      tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
  39. 114
      tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs
  40. 32
      tests/ImageSharp.Benchmarks/Codecs/GetSetPixel.cs
  41. 35
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs
  42. 16
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs
  43. 20
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs
  44. 7
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  45. 16
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs
  46. 20
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
  47. 32
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  48. 10
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs
  49. 10
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs
  50. 13
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs
  51. 64
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs
  52. 32
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs
  53. 14
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs
  54. 19
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs
  55. 14
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs
  56. 112
      tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs
  57. 2
      tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs
  58. 2
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs
  59. 2
      tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs
  60. 10
      tests/ImageSharp.Benchmarks/Config.cs
  61. 56
      tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs
  62. 2
      tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs
  63. 2
      tests/ImageSharp.Benchmarks/General/CopyBuffers.cs
  64. 2
      tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs
  65. 28
      tests/ImageSharp.Benchmarks/General/GetSetPixel.cs
  66. 2
      tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs
  67. 2
      tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs
  68. 2
      tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs
  69. 2
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  70. 86
      tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs
  71. 8
      tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs
  72. 40
      tests/ImageSharp.Benchmarks/Processing/Crop.cs
  73. 20
      tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs
  74. 20
      tests/ImageSharp.Benchmarks/Processing/Diffuse.cs
  75. 6
      tests/ImageSharp.Benchmarks/Processing/GaussianBlur.cs
  76. 31
      tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs
  77. 63
      tests/ImageSharp.Benchmarks/Processing/Resize.cs
  78. 12
      tests/ImageSharp.Benchmarks/Processing/Rotate.cs
  79. 12
      tests/ImageSharp.Benchmarks/Processing/Skew.cs
  80. 45
      tests/ImageSharp.Benchmarks/Samplers/Crop.cs
  81. 91
      tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs
  82. 41
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  83. 125
      tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs
  84. 28
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  85. 15
      tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs
  86. 2
      tests/ImageSharp.Tests/TestImages.cs
  87. 32
      tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs
  88. 2
      tests/Images/External
  89. 3
      tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png
  90. 2
      tests/Images/Input/Tiff/Benchmarks/.gitignore
  91. 4
      tests/Images/Input/Tiff/Benchmarks/gen_big.ps1
  92. 4
      tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1
  93. 3
      tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff

2
shared-infrastructure

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

186
src/ImageSharp/ColorSpaces/Companding/SRgbCompanding.cs

@ -5,6 +5,10 @@ using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.ColorSpaces.Companding
{
@ -18,21 +22,83 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// </remarks>
public static class SRgbCompanding
{
private const int Length = Scale + 2; // 256kb @ 16bit precision.
private const int Scale = (1 << 16) - 1;
private static readonly Lazy<float[]> LazyCompressTable = new Lazy<float[]>(
() =>
{
var result = new float[Length];
for (int i = 0; i < result.Length; i++)
{
double d = (double)i / Scale;
if (d <= (0.04045 / 12.92))
{
d *= 12.92;
}
else
{
d = (1.055 * Math.Pow(d, 1.0 / 2.4)) - 0.055;
}
result[i] = (float)d;
}
return result;
},
true);
private static readonly Lazy<float[]> LazyExpandTable = new Lazy<float[]>(
() =>
{
var result = new float[Length];
for (int i = 0; i < result.Length; i++)
{
double d = (double)i / Scale;
if (d <= 0.04045)
{
d /= 12.92;
}
else
{
d = Math.Pow((d + 0.055) / 1.055, 2.4);
}
result[i] = (float)d;
}
return result;
},
true);
private static float[] ExpandTable => LazyExpandTable.Value;
private static float[] CompressTable => LazyCompressTable.Value;
/// <summary>
/// Expands the companded vectors to their linear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(Span<Vector4> vectors)
{
ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors);
ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length);
while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd))
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && vectors.Length >= 2)
{
Expand(ref vectorsStart);
CompandAvx2(vectors, ExpandTable);
vectorsStart = ref Unsafe.Add(ref vectorsStart, 1);
if (Numerics.Modulo2(vectors.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
Expand(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1)));
}
}
else
#endif
{
CompandScalar(vectors, ExpandTable);
}
}
@ -40,17 +106,24 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// Compresses the uncompanded vectors to their nonlinear equivalents with respect to the energy.
/// </summary>
/// <param name="vectors">The span of vectors.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void Compress(Span<Vector4> vectors)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe void Compress(Span<Vector4> vectors)
{
ref Vector4 vectorsStart = ref MemoryMarshal.GetReference(vectors);
ref Vector4 vectorsEnd = ref Unsafe.Add(ref vectorsStart, vectors.Length);
while (Unsafe.IsAddressLessThan(ref vectorsStart, ref vectorsEnd))
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported && vectors.Length >= 2)
{
Compress(ref vectorsStart);
CompandAvx2(vectors, CompressTable);
vectorsStart = ref Unsafe.Add(ref vectorsStart, 1);
if (Numerics.Modulo2(vectors.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
Compress(ref MemoryMarshal.GetReference(vectors.Slice(vectors.Length - 1)));
}
}
else
#endif
{
CompandScalar(vectors, CompressTable);
}
}
@ -58,9 +131,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// Expands a companded vector to its linear equivalent with respect to the energy.
/// </summary>
/// <param name="vector">The vector.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Expand(ref Vector4 vector)
{
// Alpha is already a linear representation of opacity so we do not want to convert it.
vector.X = Expand(vector.X);
vector.Y = Expand(vector.Y);
vector.Z = Expand(vector.Z);
@ -70,9 +144,10 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// Compresses an uncompanded vector (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="vector">The vector.</param>
[MethodImpl(InliningOptions.ShortMethod)]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Compress(ref Vector4 vector)
{
// Alpha is already a linear representation of opacity so we do not want to convert it.
vector.X = Compress(vector.X);
vector.Y = Compress(vector.Y);
vector.Z = Compress(vector.Z);
@ -83,15 +158,84 @@ namespace SixLabors.ImageSharp.ColorSpaces.Companding
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the linear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Expand(float channel) => channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Expand(float channel)
=> channel <= 0.04045F ? channel / 12.92F : MathF.Pow((channel + 0.055F) / 1.055F, 2.4F);
/// <summary>
/// Compresses an uncompanded channel (linear) to its nonlinear equivalent.
/// </summary>
/// <param name="channel">The channel value.</param>
/// <returns>The <see cref="float"/> representing the nonlinear channel value.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static float Compress(float channel) => channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Compress(float channel)
=> channel <= 0.0031308F ? 12.92F * channel : (1.055F * MathF.Pow(channel, 0.416666666666667F)) - 0.055F;
#if SUPPORTS_RUNTIME_INTRINSICS
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CompandAvx2(Span<Vector4> vectors, float[] table)
{
fixed (float* tablePointer = &table[0])
{
var scale = Vector256.Create((float)Scale);
Vector256<float> zero = Vector256<float>.Zero;
var offset = Vector256.Create(1);
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256<float>
ref Vector256<float> vectorsBase = ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(vectors));
ref Vector256<float> vectorsLast = ref Unsafe.Add(ref vectorsBase, (IntPtr)((uint)vectors.Length / 2u));
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
{
Vector256<float> multiplied = Avx.Multiply(scale, vectorsBase);
multiplied = Avx.Min(Avx.Max(zero, multiplied), scale);
Vector256<int> truncated = Avx.ConvertToVector256Int32WithTruncation(multiplied);
Vector256<float> truncatedF = Avx.ConvertToVector256Single(truncated);
Vector256<float> low = Avx2.GatherVector256(tablePointer, truncated, sizeof(float));
Vector256<float> high = Avx2.GatherVector256(tablePointer, Avx2.Add(truncated, offset), sizeof(float));
// Alpha is already a linear representation of opacity so we do not want to convert it.
Vector256<float> companded = Numerics.Lerp(low, high, Avx.Subtract(multiplied, truncatedF));
vectorsBase = Avx.Blend(companded, vectorsBase, Numerics.BlendAlphaControl);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
}
}
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static unsafe void CompandScalar(Span<Vector4> vectors, float[] table)
{
fixed (float* tablePointer = &table[0])
{
Vector4 zero = Vector4.Zero;
var scale = new Vector4(Scale);
ref Vector4 vectorsBase = ref MemoryMarshal.GetReference(vectors);
ref Vector4 vectorsLast = ref Unsafe.Add(ref vectorsBase, vectors.Length);
while (Unsafe.IsAddressLessThan(ref vectorsBase, ref vectorsLast))
{
Vector4 multiplied = Numerics.Clamp(vectorsBase * Scale, zero, scale);
float f0 = multiplied.X;
float f1 = multiplied.Y;
float f2 = multiplied.Z;
uint i0 = (uint)f0;
uint i1 = (uint)f1;
uint i2 = (uint)f2;
// Alpha is already a linear representation of opacity so we do not want to convert it.
vectorsBase.X = Numerics.Lerp(tablePointer[i0], tablePointer[i0 + 1], f0 - (int)i0);
vectorsBase.Y = Numerics.Lerp(tablePointer[i1], tablePointer[i1 + 1], f1 - (int)i1);
vectorsBase.Z = Numerics.Lerp(tablePointer[i2], tablePointer[i2 + 1], f2 - (int)i2);
vectorsBase = ref Unsafe.Add(ref vectorsBase, 1);
}
}
}
}
}

40
src/ImageSharp/Common/Helpers/Numerics.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp
internal static class Numerics
{
#if SUPPORTS_RUNTIME_INTRINSICS
private const int BlendAlphaControl = 0b_10_00_10_00;
public const int BlendAlphaControl = 0b_10_00_10_00;
private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif
@ -710,5 +710,43 @@ namespace SixLabors.ImageSharp
}
}
}
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Performs a linear interpolation between two values based on the given weighting.
/// </summary>
/// <param name="value1">The first value.</param>
/// <param name="value2">The second value.</param>
/// <param name="amount">Values between 0 and 1 that indicates the weight of <paramref name="value2"/>.</param>
/// <returns>The <see cref="Vector256{Single}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector256<float> Lerp(
in Vector256<float> value1,
in Vector256<float> value2,
in Vector256<float> amount)
{
Vector256<float> diff = Avx.Subtract(value2, value1);
if (Fma.IsSupported)
{
return Fma.MultiplyAdd(diff, amount, value1);
}
else
{
return Avx.Add(Avx.Multiply(diff, amount), value1);
}
}
#endif
/// <summary>
/// Performs a linear interpolation between two values based on the given weighting.
/// </summary>
/// <param name="value1">The first value.</param>
/// <param name="value2">The second value.</param>
/// <param name="amount">A value between 0 and 1 that indicates the weight of <paramref name="value2"/>.</param>
/// <returns>The <see cref="float"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount)
=> ((value2 - value1) * amount) + value1;
}
}

2
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp
public static ReadOnlySpan<byte> PermuteMaskEvenOdd8x32 => new byte[] { 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0 };
public static ReadOnlySpan<byte> PermuteMaskSwitchInnerDWords8x32 => new byte[] { 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0 };
private static ReadOnlySpan<byte> ShuffleMaskPad4Nx16 => new byte[] { 0, 1, 2, 0x80, 3, 4, 5, 0x80, 6, 7, 8, 0x80, 9, 10, 11, 0x80 };
private static ReadOnlySpan<byte> ShuffleMaskSlice4Nx16 => new byte[] { 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 0x80, 0x80, 0x80, 0x80 };

51
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -485,6 +485,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="source">The source block.</param>
public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Scale16X16To8X8Vectorized(ref destination, source);
return;
}
#endif
Scale16X16To8X8Scalar(ref destination, source);
}
private static void Scale16X16To8X8Vectorized(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
#if SUPPORTS_RUNTIME_INTRINSICS
Debug.Assert(Avx2.IsSupported, "AVX2 is required to execute this method");
var f2 = Vector256.Create(2f);
var f025 = Vector256.Create(0.25f);
Vector256<int> switchInnerDoubleWords = Unsafe.As<byte, Vector256<int>>(ref MemoryMarshal.GetReference(SimdUtils.HwIntrinsics.PermuteMaskSwitchInnerDWords8x32));
ref Vector256<float> in1 = ref Unsafe.As<Block8x8F, Vector256<float>>(ref MemoryMarshal.GetReference(source));
ref Vector256<float> in2 = ref Unsafe.As<Block8x8F, Vector256<float>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(source), 1));
ref Vector256<float> destRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref destination);
for (int i = 0; i < 8; i++)
{
Vector256<float> a = in1;
Vector256<float> b = Unsafe.Add(ref in1, 1);
Vector256<float> c = in2;
Vector256<float> d = Unsafe.Add(ref in2, 1);
Vector256<float> calc1 = Avx.Shuffle(a, c, 0b10_00_10_00);
Vector256<float> calc2 = Avx.Shuffle(a, c, 0b11_01_11_01);
Vector256<float> calc3 = Avx.Shuffle(b, d, 0b10_00_10_00);
Vector256<float> calc4 = Avx.Shuffle(b, d, 0b11_01_11_01);
Vector256<float> sum = Avx.Add(Avx.Add(calc1, calc2), Avx.Add(calc3, calc4));
Vector256<float> add = Avx.Add(sum, f2);
Vector256<float> res = Avx.Multiply(add, f025);
destRef = Avx2.PermuteVar8x32(res, switchInnerDoubleWords);
destRef = ref Unsafe.Add(ref destRef, 1);
in1 = ref Unsafe.Add(ref in1, 2);
in2 = ref Unsafe.Add(ref in2, 2);
}
#endif
}
private static unsafe void Scale16X16To8X8Scalar(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
for (int i = 0; i < 4; i++)
{
int dstOff = ((i & 2) << 4) | ((i & 1) << 2);

35
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrTables.cs → src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs

@ -1,16 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Provides 8-bit lookup tables for converting from Rgb to YCbCr colorspace.
/// Methods to build the tables are based on libjpeg implementation.
/// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)!
/// </summary>
internal unsafe struct RgbToYCbCrTables
internal unsafe struct RgbToYCbCrConverterLut
{
/// <summary>
/// The red luminance table
@ -63,10 +64,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Initializes the YCbCr tables
/// </summary>
/// <returns>The initialized <see cref="RgbToYCbCrTables"/></returns>
public static RgbToYCbCrTables Create()
/// <returns>The initialized <see cref="RgbToYCbCrConverterLut"/></returns>
public static RgbToYCbCrConverterLut Create()
{
RgbToYCbCrTables tables = default;
RgbToYCbCrConverterLut tables = default;
for (int i = 0; i <= 255; i++)
{
@ -92,11 +93,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
/// <summary>
/// TODO: Replace this logic with SIMD conversion (similar to the one in the decoder)!
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ConvertPixelInto(
private void ConvertPixelInto(
int r,
int g,
int b,
@ -111,10 +111,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
// float cr = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
// float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
}
public void Convert(Span<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 64; i++)
{
ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i);
this.ConvertPixelInto(
c.R,
c.G,
c.B,
ref yBlock,
ref cbBlock,
ref crBlock,
i);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x)
=> (int)((x * (1L << ScaleBits)) + 0.5F);

120
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs

@ -0,0 +1,120 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Diagnostics;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal static class RgbToYCbCrConverterVectorized
{
public static bool IsSupported
{
get
{
#if SUPPORTS_RUNTIME_INTRINSICS
return Avx2.IsSupported;
#else
return false;
#endif
}
}
#if SUPPORTS_RUNTIME_INTRINSICS
private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[]
{
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0
};
private static ReadOnlySpan<byte> MoveLast24BytesToSeparateLanes => new byte[]
{
2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0
};
private static ReadOnlySpan<byte> ExtractRgb => new byte[]
{
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF,
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF
};
#endif
public static void Convert(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{
Debug.Assert(IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
var fn0168736 = Vector256.Create(-0.168736f);
var fn0331264 = Vector256.Create(-0.331264f);
var f128 = Vector256.Create(128f);
var fn0418688 = Vector256.Create(-0.418688f);
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> inRef = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> destYRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref yBlock);
ref Vector256<float> destCbRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock);
ref Vector256<float> destCrRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock);
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b;
for (int i = 0; i < 7; i++)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)(24 * i)).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref destYRef, i) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
}
extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveLast24BytesToSeparateLanes));
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)160).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref destYRef, 7) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
Unsafe.Add(ref destCbRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
#endif
}
}
}

28
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
@ -33,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrTables colorTables;
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 8x8 block to hold TPixel data
@ -48,7 +47,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
public static YCbCrForwardConverter<TPixel> Create()
{
var result = default(YCbCrForwardConverter<TPixel>);
result.colorTables = RgbToYCbCrTables.Create();
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
// Avoid creating lookup tables, when vectorized converter is supported
result.colorTables = RgbToYCbCrConverterLut.Create();
}
return result;
}
@ -65,20 +69,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 64; i++)
if (RgbToYCbCrConverterVectorized.IsSupported)
{
ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i);
this.colorTables.ConvertPixelInto(
c.R,
c.G,
c.B,
ref yBlock,
ref cbBlock,
ref crBlock,
i);
RgbToYCbCrConverterVectorized.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
else
{
this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
}
}

36
src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs

@ -0,0 +1,36 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
/// <summary>
/// Fax compression options, see TIFF spec page 51f (T4Options).
/// </summary>
[Flags]
public enum FaxCompressionOptions : uint
{
/// <summary>
/// No options.
/// </summary>
None = 0,
/// <summary>
/// If set, 2-dimensional coding is used (otherwise 1-dimensional is assumed).
/// </summary>
TwoDimensionalCoding = 1,
/// <summary>
/// If set, uncompressed mode is used.
/// </summary>
UncompressedMode = 2,
/// <summary>
/// If set, fill bits have been added as necessary before EOL codes such that
/// EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte
/// preceded by a zero nibble: xxxx-0000 0000-0001.
/// </summary>
EolPadding = 4
}
}

4
src/ImageSharp/Formats/Tiff/Compression/ModifiedHuffmanTiffCompression.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
/// <param name="photometricInterpretation">The photometric interpretation.</param>
/// <param name="width">The image width.</param>
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
: base(allocator, photometricInterpretation, width)
: base(allocator, FaxCompressionOptions.None, photometricInterpretation, width)
{
}
@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
byte whiteValue = (byte)(isWhiteZero ? 0 : 1);
byte blackValue = (byte)(isWhiteZero ? 1 : 0);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, isModifiedHuffman: true);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true);
buffer.Clear();
uint bitsWritten = 0;

74
src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs

@ -35,11 +35,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
/// </summary>
private ulong position;
/// <summary>
/// Indicates, if the current run are white pixels.
/// </summary>
private bool isWhiteRun;
/// <summary>
/// Indicates whether its the first line of data which is read from the image.
/// </summary>
@ -50,24 +45,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
/// </summary>
private bool terminationCodeFound;
/// <summary>
/// Number of pixels in the current run.
/// </summary>
private uint runLength;
/// <summary>
/// We keep track if its the start of the row, because each run is expected to start with a white run.
/// If the image row itself starts with black, a white run of zero is expected.
/// </summary>
private bool isStartOfRow;
/// <summary>
/// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used.
/// </summary>
private readonly bool isModifiedHuffmanRle;
/// <summary>
/// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.
/// </summary>
private readonly bool eolPadding;
private readonly int dataLength;
private const int MinCodeLength = 2;
private const int MaxCodeLength = 13;
private readonly int maxCodeLength = 13;
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
{
@ -225,8 +223,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
/// <param name="input">The compressed input stream.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation.</param>
public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool isModifiedHuffman = false)
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation. Defaults to false.</param>
public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
{
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
@ -237,11 +236,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
this.value = 0;
this.curValueBitsRead = 0;
this.position = 0;
this.isWhiteRun = true;
this.IsWhiteRun = true;
this.isFirstScanLine = true;
this.isStartOfRow = true;
this.terminationCodeFound = false;
this.runLength = 0;
this.RunLength = 0;
this.eolPadding = eolPadding;
if (this.eolPadding)
{
this.maxCodeLength = 24;
}
}
/// <summary>
@ -268,17 +273,28 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
/// <summary>
/// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
/// </summary>
public bool IsWhiteRun => this.isWhiteRun;
public bool IsWhiteRun { get; private set; }
/// <summary>
/// Gets the number of pixels in the current run.
/// </summary>
public uint RunLength => this.runLength;
public uint RunLength { get; private set; }
/// <summary>
/// Gets a value indicating whether the end of a pixel row has been reached.
/// </summary>
public bool IsEndOfScanLine => this.curValueBitsRead == 12 && this.value == 1;
public bool IsEndOfScanLine
{
get
{
if (this.eolPadding)
{
return this.curValueBitsRead >= 12 && this.value == 1;
}
return this.curValueBitsRead == 12 && this.value == 1;
}
}
/// <summary>
/// Read the next run of pixels.
@ -287,7 +303,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
if (this.terminationCodeFound)
{
this.isWhiteRun = !this.IsWhiteRun;
this.IsWhiteRun = !this.IsWhiteRun;
this.terminationCodeFound = false;
}
@ -296,7 +312,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
if (this.isFirstScanLine && !this.isModifiedHuffmanRle)
{
// We expect an EOL before the first data.
this.value = this.ReadValue(12);
this.value = this.ReadValue(this.eolPadding ? 16 : 12);
if (!this.IsEndOfScanLine)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found");
@ -310,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
do
{
if (this.curValueBitsRead > MaxCodeLength)
if (this.curValueBitsRead > this.maxCodeLength)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read");
}
@ -320,11 +337,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
if (this.IsWhiteRun)
{
this.runLength += this.WhiteMakeupCodeRunLength();
this.RunLength += this.WhiteMakeupCodeRunLength();
}
else
{
this.runLength += this.BlackMakeupCodeRunLength();
this.RunLength += this.BlackMakeupCodeRunLength();
}
this.isStartOfRow = false;
@ -338,7 +355,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
// Each line starts with a white run. If the image starts with black, a white run with length zero is written.
if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0)
{
this.isWhiteRun = !this.IsWhiteRun;
this.IsWhiteRun = !this.IsWhiteRun;
this.Reset();
this.isStartOfRow = false;
continue;
@ -346,11 +363,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
if (this.IsWhiteRun)
{
this.runLength += this.WhiteTerminatingCodeRunLength();
this.RunLength += this.WhiteTerminatingCodeRunLength();
}
else
{
this.runLength += this.BlackTerminatingCodeRunLength();
this.RunLength += this.BlackTerminatingCodeRunLength();
}
this.terminationCodeFound = true;
@ -374,7 +391,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
public void StartNewRow()
{
// Each new row starts with a white run.
this.isWhiteRun = true;
this.IsWhiteRun = true;
this.isStartOfRow = true;
this.terminationCodeFound = false;
@ -770,14 +787,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
if (resetRunLength)
{
this.runLength = 0;
this.RunLength = 0;
}
}
private uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
Guard.MustBeLessThanOrEqualTo(nBits, 12, nameof(nBits));
uint v = 0;
int shift = nBits;

17
src/ImageSharp/Formats/Tiff/Compression/T4TiffCompression.cs

@ -14,25 +14,32 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
/// </summary>
internal class T4TiffCompression : TiffBaseCompression
{
private readonly FaxCompressionOptions faxCompressionOptions;
/// <summary>
/// Initializes a new instance of the <see cref="T4TiffCompression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="faxOptions">Fax compression options.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
/// <param name="width">The image width.</param>
public T4TiffCompression(MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width)
: base(allocator, photometricInterpretation, width)
{
}
public T4TiffCompression(MemoryAllocator allocator, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation, int width)
: base(allocator, photometricInterpretation, width) => this.faxCompressionOptions = faxOptions;
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding))
{
TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported");
}
bool isWhiteZero = this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
byte whiteValue = (byte)(isWhiteZero ? 0 : 1);
byte blackValue = (byte)(isWhiteZero ? 1 : 0);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator);
var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding);
buffer.Clear();
uint bitsWritten = 0;

11
src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs

@ -8,7 +8,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
internal static class TiffCompressionFactory
{
public static TiffBaseCompression Create(TiffDecoderCompressionType compressionType, MemoryAllocator allocator, TiffPhotometricInterpretation photometricInterpretation, int width, int bitsPerPixel, TiffPredictor predictor)
public static TiffBaseCompression Create(
TiffDecoderCompressionType compressionType,
MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation,
int width,
int bitsPerPixel,
TiffPredictor predictor,
FaxCompressionOptions faxOptions)
{
switch (compressionType)
{
@ -28,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");
return new T4TiffCompression(allocator, photometricInterpretation, width);
return new T4TiffCompression(allocator, faxOptions, photometricInterpretation, width);
case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "predictor");

2
src/ImageSharp/Formats/Tiff/README.md

@ -121,7 +121,7 @@ Decoder:
|PageName | | | |
|XPosition | | | |
|YPosition | | | |
|T4Options | | | |
|T4Options | | Y | |
|T6Options | | | |
|PageNumber | | | |
|TransferFunction | | | |

19
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -19,6 +19,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
internal class TiffDecoderCore : IImageDecoderInternals
{
/// <summary>
/// The global configuration.
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
@ -53,6 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public ushort[] BitsPerSample { get; set; }
/// <summary>
/// Gets or sets the bits per pixel.
/// </summary>
public int BitsPerPixel { get; set; }
/// <summary>
@ -70,6 +78,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public TiffDecoderCompressionType CompressionType { get; set; }
/// <summary>
/// Gets or sets the Fax specific compression options.
/// </summary>
public FaxCompressionOptions FaxCompressionOptions { get; set; }
/// <summary>
/// Gets or sets the planar configuration type to use when decoding the image.
/// </summary>
@ -81,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
/// <summary>
/// Gets or sets the predictor.
/// Gets or sets the horizontal predictor.
/// </summary>
public TiffPredictor Predictor { get; set; }
@ -243,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
}
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor);
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
@ -280,7 +293,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer;
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor);
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);

13
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -57,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
options.BitsPerPixel = entries.BitsPerPixel;
ParseColorType(options, entries);
ParseCompression(options, entries.Compression);
ParseCompression(options, entries);
}
private static void ParseColorType(this TiffDecoderCore options, TiffFrameMetadata entries)
@ -208,8 +209,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
private static void ParseCompression(this TiffDecoderCore options, TiffCompression compression)
private static void ParseCompression(this TiffDecoderCore options, TiffFrameMetadata entries)
{
TiffCompression compression = entries.Compression;
switch (compression)
{
case TiffCompression.None:
@ -240,6 +242,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
case TiffCompression.CcittGroup3Fax:
{
options.CompressionType = TiffDecoderCompressionType.T4;
IExifValue t4options = entries.FrameTags.Find(tag => tag.Tag == ExifTag.T4Options);
if (t4options != null)
{
var t4OptionValue = (FaxCompressionOptions)t4options.GetValue();
options.FaxCompressionOptions = t4OptionValue;
}
break;
}

4
src/ImageSharp/PixelFormats/Utils/Vector4Converters.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -45,4 +45,4 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils
}
}
}
}
}

3
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -96,7 +95,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex);
// Length of reduced palette + transparency.
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, QuantizerConstants.MaxColors));
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, this.maxColors));
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.palette = result;

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

@ -4,6 +4,11 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Processing.Processors.Transforms
{
@ -61,28 +66,99 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// <returns>The weighted sum</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 Convolve(Span<Vector4> rowSpan)
{
return this.ConvolveCore(ref rowSpan[this.StartIndex]);
}
=> this.ConvolveCore(ref rowSpan[this.StartIndex]);
[MethodImpl(InliningOptions.ShortMethod)]
public Vector4 ConvolveCore(ref Vector4 rowStartRef)
{
ref float horizontalValues = ref Unsafe.AsRef<float>(this.bufferPtr);
#if SUPPORTS_RUNTIME_INTRINSICS
if (Fma.IsSupported)
{
float* bufferStart = this.bufferPtr;
float* bufferEnd = bufferStart + (this.Length & ~3);
Vector256<float> result256_0 = Vector256<float>.Zero;
Vector256<float> result256_1 = Vector256<float>.Zero;
ReadOnlySpan<byte> maskBytes = new byte[]
{
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0,
1, 0, 0, 0, 1, 0, 0, 0,
};
Vector256<int> mask = Unsafe.ReadUnaligned<Vector256<int>>(ref MemoryMarshal.GetReference(maskBytes));
// Destination color components
Vector4 result = Vector4.Zero;
while (bufferStart < bufferEnd)
{
// It is important to use a single expression here so that the JIT will correctly use vfmadd231ps
// for the FMA operation, and execute it directly on the target register and reading directly from
// memory for the first parameter. This skips initializing a SIMD register, and an extra copy.
// The code below should compile in the following assembly on .NET 5 x64:
//
// vmovsd xmm2, [rax] ; load *(double*)bufferStart into xmm2 as [ab, _]
// vpermps ymm2, ymm1, ymm2 ; permute as a float YMM register to [a, a, a, a, b, b, b, b]
// vfmadd231ps ymm0, ymm2, [r8] ; result256_0 = FMA(pixels, factors) + result256_0
//
// For tracking the codegen issue with FMA, see: https://github.com/dotnet/runtime/issues/12212.
// Additionally, we're also unrolling two computations per each loop iterations to leverage the
// fact that most CPUs have two ports to schedule multiply operations for FMA instructions.
result256_0 = Fma.MultiplyAdd(
Unsafe.As<Vector4, Vector256<float>>(ref rowStartRef),
Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask),
result256_0);
for (int i = 0; i < this.Length; i++)
{
float weight = Unsafe.Add(ref horizontalValues, i);
result256_1 = Fma.MultiplyAdd(
Unsafe.As<Vector4, Vector256<float>>(ref Unsafe.Add(ref rowStartRef, 2)),
Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)(bufferStart + 2)).AsSingle(), mask),
result256_1);
bufferStart += 4;
rowStartRef = ref Unsafe.Add(ref rowStartRef, 4);
}
result256_0 = Avx.Add(result256_0, result256_1);
if ((this.Length & 3) >= 2)
{
result256_0 = Fma.MultiplyAdd(
Unsafe.As<Vector4, Vector256<float>>(ref rowStartRef),
Avx2.PermuteVar8x32(Vector256.CreateScalarUnsafe(*(double*)bufferStart).AsSingle(), mask),
result256_0);
bufferStart += 2;
rowStartRef = ref Unsafe.Add(ref rowStartRef, 2);
}
// Vector4 v = offsetedRowSpan[i];
Vector4 v = Unsafe.Add(ref rowStartRef, i);
result += v * weight;
Vector128<float> result128 = Sse.Add(result256_0.GetLower(), result256_0.GetUpper());
if ((this.Length & 1) != 0)
{
result128 = Fma.MultiplyAdd(
Unsafe.As<Vector4, Vector128<float>>(ref rowStartRef),
Vector128.Create(*bufferStart),
result128);
}
return *(Vector4*)&result128;
}
else
#endif
{
// Destination color components
Vector4 result = Vector4.Zero;
float* bufferStart = this.bufferPtr;
float* bufferEnd = this.bufferPtr + this.Length;
while (bufferStart < bufferEnd)
{
// Vector4 v = offsetedRowSpan[i];
result += rowStartRef * *bufferStart;
return result;
bufferStart++;
rowStartRef = ref Unsafe.Add(ref rowStartRef, 1);
}
return result;
}
}
/// <summary>
@ -91,9 +167,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
internal ResizeKernel AlterLeftValue(int left)
{
return new ResizeKernel(left, this.bufferPtr, this.Length);
}
=> new ResizeKernel(left, this.bufferPtr, this.Length);
internal void Fill(Span<double> values)
{

6
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor.cs

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

24
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs

@ -21,6 +21,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
private readonly IResampler resampler;
private readonly Rectangle destinationRectangle;
private readonly bool compand;
private readonly bool premultiplyAlpha;
private Image<TPixel> destination;
public ResizeProcessor(Configuration configuration, ResizeProcessor definition, Image<TPixel> source, Rectangle sourceRectangle)
@ -30,6 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
this.destinationHeight = definition.DestinationHeight;
this.destinationRectangle = definition.DestinationRectangle;
this.resampler = definition.Sampler;
this.premultiplyAlpha = definition.PremultiplyAlpha;
this.compand = definition.Compand;
}
@ -60,6 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle sourceRectangle = this.SourceRectangle;
Rectangle destinationRectangle = this.destinationRectangle;
bool compand = this.compand;
bool premultiplyAlpha = this.premultiplyAlpha;
// Handle resize dimensions identical to the original
if (source.Width == destination.Width
@ -128,7 +131,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
sourceRectangle,
destinationRectangle,
interest,
compand);
compand,
premultiplyAlpha);
}
}
@ -159,6 +163,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
in operation);
}
private static PixelConversionModifiers GetModifiers(bool compand, bool premultiplyAlpha)
{
if (premultiplyAlpha)
{
return PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
}
else
{
return PixelConversionModifiers.None.ApplyCompanding(compand);
}
}
private static void ApplyResizeFrameTransform(
Configuration configuration,
ImageFrame<TPixel> source,
@ -168,10 +184,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
Rectangle sourceRectangle,
Rectangle destinationRectangle,
Rectangle interest,
bool compand)
bool compand,
bool premultiplyAlpha)
{
PixelConversionModifiers conversionModifiers =
PixelConversionModifiers.Premultiply.ApplyCompanding(compand);
PixelConversionModifiers conversionModifiers = GetModifiers(compand, premultiplyAlpha);
Buffer2DRegion<TPixel> sourceRegion = source.PixelBuffer.GetRegion(sourceRectangle);

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

@ -105,14 +105,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[MethodImpl(InliningOptions.ShortMethod)]
public Span<Vector4> GetColumnSpan(int x, int startY)
{
return this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
}
=> this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min);
public void Initialize()
{
this.CalculateFirstPassValues(this.currentWindow);
}
=> this.CalculateFirstPassValues(this.currentWindow);
public void FillDestinationPixels(RowInterval rowInterval, Buffer2D<TPixel> destination)
{

6
src/ImageSharp/Processing/ResizeOptions.cs

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

19
tests/ImageSharp.Benchmarks/BenchmarkBase.cs

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

29
tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs

@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodeBmp : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeBmp
{
private byte[] bmpBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
private string TestImageFullPath
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[GlobalSetup]
public void ReadImages()
@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public SDSize BmpSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.bmpBytes))
{
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
}
}
using var memoryStream = new MemoryStream(this.bmpBytes);
using var image = SDImage.FromStream(memoryStream);
return image.Size;
}
[Benchmark(Description = "ImageSharp Bmp")]
public Size BmpCore()
public Size BmpImageSharp()
{
using (var memoryStream = new MemoryStream(this.bmpBytes))
{
using (var image = Image.Load<Rgba32>(memoryStream))
{
return new Size(image.Width, image.Height);
}
}
using var memoryStream = new MemoryStream(this.bmpBytes);
using var image = Image.Load<Rgba32>(memoryStream);
return new Size(image.Width, image.Height);
}
}
}

48
tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs

@ -6,12 +6,11 @@ using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using CoreSize = SixLabors.ImageSharp.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodeFilteredPng : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeFilteredPng
{
private byte[] filter0;
private byte[] filter1;
@ -30,44 +29,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
}
[Benchmark(Baseline = true, Description = "None-filtered PNG file")]
public CoreSize PngFilter0()
{
return LoadPng(this.filter0);
}
public Size PngFilter0()
=> LoadPng(this.filter0);
[Benchmark(Description = "Sub-filtered PNG file")]
public CoreSize PngFilter1()
{
return LoadPng(this.filter1);
}
public Size PngFilter1()
=> LoadPng(this.filter1);
[Benchmark(Description = "Up-filtered PNG file")]
public CoreSize PngFilter2()
{
return LoadPng(this.filter2);
}
public Size PngFilter2()
=> LoadPng(this.filter2);
[Benchmark(Description = "Average-filtered PNG file")]
public CoreSize PngFilter3()
{
return LoadPng(this.filter3);
}
public Size PngFilter3()
=> LoadPng(this.filter3);
[Benchmark(Description = "Paeth-filtered PNG file")]
public CoreSize PngFilter4()
{
return LoadPng(this.filter4);
}
public Size PngFilter4()
=> LoadPng(this.filter4);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static CoreSize LoadPng(byte[] bytes)
private static Size LoadPng(byte[] bytes)
{
using (var image = Image.Load<Rgba32>(bytes))
{
return image.Size();
}
using var image = Image.Load<Rgba32>(bytes);
return image.Size();
}
private static string TestImageFullPath(string path) => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path);
private static string TestImageFullPath(string path)
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path);
}
}
}

29
tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs

@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodeGif : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeGif
{
private byte[] gifBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
private string TestImageFullPath
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[GlobalSetup]
public void ReadImages()
@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public SDSize GifSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.gifBytes))
{
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
}
}
using var memoryStream = new MemoryStream(this.gifBytes);
using var image = SDImage.FromStream(memoryStream);
return image.Size;
}
[Benchmark(Description = "ImageSharp Gif")]
public Size GifCore()
public Size GifImageSharp()
{
using (var memoryStream = new MemoryStream(this.gifBytes))
{
using (var image = Image.Load<Rgba32>(memoryStream))
{
return new Size(image.Width, image.Height);
}
}
using var memoryStream = new MemoryStream(this.gifBytes);
using var image = Image.Load<Rgba32>(memoryStream);
return new Size(image.Width, image.Height);
}
}
}

29
tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs

@ -10,12 +10,13 @@ using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodePng : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class DecodePng
{
private byte[] pngBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
private string TestImageFullPath
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Png.Splash)]
public string TestImage { get; set; }
@ -32,25 +33,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Png")]
public SDSize PngSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.pngBytes))
{
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
}
}
using var memoryStream = new MemoryStream(this.pngBytes);
using var image = SDImage.FromStream(memoryStream);
return image.Size;
}
[Benchmark(Description = "ImageSharp Png")]
public Size PngCore()
public Size PngImageSharp()
{
using (var memoryStream = new MemoryStream(this.pngBytes))
{
using (var image = Image.Load<Rgba32>(memoryStream))
{
return image.Size();
}
}
using var memoryStream = new MemoryStream(this.pngBytes);
using var image = Image.Load<Rgba32>(memoryStream);
return image.Size();
}
}
}

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

@ -5,7 +5,6 @@ using System.Buffers;
using System.IO;
using System.Threading;
using BenchmarkDotNet.Attributes;
using ImageMagick;
using Pfim;
using SixLabors.ImageSharp.Formats.Tga;
@ -14,8 +13,8 @@ using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodeTga : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeTga
{
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
@ -28,36 +27,28 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[GlobalSetup]
public void SetupData()
{
this.data = File.ReadAllBytes(this.TestImageFullPath);
}
=> this.data = File.ReadAllBytes(this.TestImageFullPath);
[Benchmark(Baseline = true, Description = "ImageMagick Tga")]
public int TgaImageMagick()
{
var settings = new MagickReadSettings { Format = MagickFormat.Tga };
using (var image = new MagickImage(new MemoryStream(this.data), settings))
{
return image.Width;
}
using var image = new MagickImage(new MemoryStream(this.data), settings);
return image.Width;
}
[Benchmark(Description = "ImageSharp Tga")]
public int TgaCore()
public int TgaImageSharp()
{
using (var image = Image.Load<Bgr24>(this.data, new TgaDecoder()))
{
return image.Width;
}
using var image = Image.Load<Bgr24>(this.data, new TgaDecoder());
return image.Width;
}
[Benchmark(Description = "Pfim Tga")]
public int TgaPfim()
{
using (var image = Targa.Create(this.data, this.pfimConfig))
{
return image.Width;
}
using var image = Targa.Create(this.data, this.pfimConfig);
return image.Width;
}
private class PfimAllocator : IImageAllocator
@ -65,10 +56,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private int rented;
private readonly ArrayPool<byte> shared = ArrayPool<byte>.Shared;
public byte[] Rent(int size)
{
return this.shared.Rent(size);
}
public byte[] Rent(int size) => this.shared.Rent(size);
public void Return(byte[] data)
{

72
tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs

@ -1,38 +1,92 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// Enable this for using larger Tiff files. Those files are very large (> 700MB) and therefor not part of the git repo.
// Use the scripts gen_big.ps1 and gen_medium.ps1 in tests\Images\Input\Tiff\Benchmarks to generate those images.
//// #define BIG_TESTS
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodeTiff : BenchmarkBase
[MarkdownExporter]
[HtmlExporter]
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeTiff
{
private byte[] tiffBytes;
private string prevImage = null;
private byte[] data;
private Configuration configuration;
#if BIG_TESTS
private static readonly int BufferSize = 1024 * 68;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Path.Combine(TestImages.Tiff.Benchmark_Path, this.TestImage));
[Params(
TestImages.Tiff.Benchmark_BwFax3,
//// TestImages.Tiff.Benchmark_RgbFax4, // fax4 is not supported yet.
TestImages.Tiff.Benchmark_GrayscaleUncompressed,
TestImages.Tiff.Benchmark_PaletteUncompressed,
TestImages.Tiff.Benchmark_RgbDeflate,
TestImages.Tiff.Benchmark_RgbLzw,
TestImages.Tiff.Benchmark_RgbPackbits,
TestImages.Tiff.Benchmark_RgbUncompressed)]
public string TestImage { get; set; }
#else
private static readonly int BufferSize = Configuration.Default.StreamProcessingBufferSize;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.RgbPackbits)]
[Params(
TestImages.Tiff.CcittFax3AllTermCodes,
TestImages.Tiff.HuffmanRleAllMakeupCodes,
TestImages.Tiff.GrayscaleUncompressed,
TestImages.Tiff.PaletteUncompressed,
TestImages.Tiff.RgbDeflate,
TestImages.Tiff.RgbLzwPredictor,
TestImages.Tiff.RgbPackbits,
TestImages.Tiff.RgbUncompressed)]
public string TestImage { get; set; }
#endif
[GlobalSetup]
public void Config()
{
if (this.configuration == null)
{
this.configuration = new Configuration();
this.configuration.AddTiff();
this.configuration.StreamProcessingBufferSize = BufferSize;
}
}
[IterationSetup]
public void ReadImages()
{
if (this.tiffBytes == null)
if (this.prevImage != this.TestImage)
{
this.tiffBytes = File.ReadAllBytes(this.TestImageFullPath);
this.data = File.ReadAllBytes(this.TestImageFullPath);
this.prevImage = this.TestImage;
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")]
public SDSize TiffSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.tiffBytes))
using (var memoryStream = new MemoryStream(this.data))
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
@ -42,8 +96,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tiff")]
public Size TiffCore()
{
using (var memoryStream = new MemoryStream(this.tiffBytes))
using (var image = Image.Load<Rgba32>(memoryStream))
using (var ms = new MemoryStream(this.data))
using (var image = Image.Load<Rgba32>(this.configuration, ms))
{
return image.Size();
}

93
tests/ImageSharp.Benchmarks/Codecs/DecodeTiffBig.cs

@ -1,93 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(DecodeTiffBig.Config.LongClr))]
public class DecodeTiffBig : BenchmarkBase
{
private class Config : SixLabors.ImageSharp.Benchmarks.Config
{
public class LongClr : Config
{
public LongClr()
{
this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5),
Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5),
Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5));
this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(60);
}
}
}
private string prevImage = null;
private byte[] data;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Path.Combine(TestImages.Tiff.Benchmark_Path, this.TestImage));
[Params(
TestImages.Tiff.Benchmark_BwFax3,
//// TestImages.Tiff.Benchmark_RgbFax4,
TestImages.Tiff.Benchmark_BwRle,
TestImages.Tiff.Benchmark_GrayscaleUncompressed,
TestImages.Tiff.Benchmark_PaletteUncompressed,
TestImages.Tiff.Benchmark_RgbDeflate,
TestImages.Tiff.Benchmark_RgbLzw,
TestImages.Tiff.Benchmark_RgbPackbits,
TestImages.Tiff.Benchmark_RgbUncompressed)]
public string TestImage { get; set; }
[IterationSetup]
public void ReadImages()
{
if (this.prevImage != this.TestImage)
{
this.data = File.ReadAllBytes(this.TestImageFullPath);
this.prevImage = this.TestImage;
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")]
public SDSize TiffSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.data))
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
}
}
[Benchmark(Description = "ImageSharp Tiff")]
public Size TiffCore()
{
Configuration config = Configuration.Default.Clone();
config.StreamProcessingBufferSize = 1024 * 64;
config.ImageFormatsManager.AddImageFormat(Formats.Experimental.Tiff.TiffFormat.Instance);
config.ImageFormatsManager.AddImageFormatDetector(new Formats.Experimental.Tiff.TiffImageFormatDetector());
config.ImageFormatsManager.SetDecoder(Formats.Experimental.Tiff.TiffFormat.Instance, new Formats.Experimental.Tiff.TiffDecoder());
using (var ms = new MemoryStream(this.data))
using (var image = Image.Load<Rgba32>(config, ms))
{
return image.Size();
}
}
}
}

23
tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
@ -10,8 +10,8 @@ using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class EncodeBmp : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeBmp
{
private Stream bmpStream;
private SDImage bmpDrawing;
@ -33,6 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup()
{
this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose();
this.bmpDrawing.Dispose();
}
@ -40,19 +41,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public void BmpSystemDrawing()
{
using (var memoryStream = new MemoryStream())
{
this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp);
}
using var memoryStream = new MemoryStream();
this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp);
}
[Benchmark(Description = "ImageSharp Bmp")]
public void BmpCore()
public void BmpImageSharp()
{
using (var memoryStream = new MemoryStream())
{
this.bmpCore.SaveAsBmp(memoryStream);
}
using var memoryStream = new MemoryStream();
this.bmpCore.SaveAsBmp(memoryStream);
}
}
}
}

10
tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs

@ -8,29 +8,25 @@ using SixLabors.ImageSharp.Formats.Bmp;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeBmpMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" };
[Benchmark(Description = "EncodeBmpMultiple - ImageSharp")]
public void EncodeBmpImageSharp()
{
this.ForEachImageSharpImage((img, ms) =>
=> this.ForEachImageSharpImage((img, ms) =>
{
img.Save(ms, new BmpEncoder());
return null;
});
}
[Benchmark(Baseline = true, Description = "EncodeBmpMultiple - System.Drawing")]
public void EncodeBmpSystemDrawing()
{
this.ForEachSystemDrawingImage((img, ms) =>
=> this.ForEachSystemDrawingImage((img, ms) =>
{
img.Save(ms, ImageFormat.Bmp);
return null;
});
}
}
}

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

@ -13,8 +13,8 @@ using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class EncodeGif : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeGif
{
// System.Drawing needs this.
private Stream bmpStream;
@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup()
{
this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose();
this.bmpDrawing.Dispose();
}
@ -53,19 +54,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public void GifSystemDrawing()
{
using (var memoryStream = new MemoryStream())
{
this.bmpDrawing.Save(memoryStream, ImageFormat.Gif);
}
using var memoryStream = new MemoryStream();
this.bmpDrawing.Save(memoryStream, ImageFormat.Gif);
}
[Benchmark(Description = "ImageSharp Gif")]
public void GifCore()
public void GifImageSharp()
{
using (var memoryStream = new MemoryStream())
{
this.bmpCore.SaveAsGif(memoryStream, this.encoder);
}
using var memoryStream = new MemoryStream();
this.bmpCore.SaveAsGif(memoryStream, this.encoder);
}
}
}

10
tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs

@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{
[Params(InputImageCategory.AllImages)]
@ -20,8 +20,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "EncodeGifMultiple - ImageSharp")]
public void EncodeGifImageSharp()
{
this.ForEachImageSharpImage((img, ms) =>
=> this.ForEachImageSharpImage((img, ms) =>
{
// Try to get as close to System.Drawing's output as possible
var options = new GifEncoder
@ -32,16 +31,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
img.Save(ms, options);
return null;
});
}
[Benchmark(Baseline = true, Description = "EncodeGifMultiple - System.Drawing")]
public void EncodeGifSystemDrawing()
{
this.ForEachSystemDrawingImage((img, ms) =>
=> this.ForEachSystemDrawingImage((img, ms) =>
{
img.Save(ms, ImageFormat.Gif);
return null;
});
}
}
}

59
tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs

@ -8,15 +8,15 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
/// <summary>
/// Benchmarks saving png files using different quantizers. System.Drawing cannot save indexed png files so we cannot compare.
/// Benchmarks saving png files using different quantizers.
/// System.Drawing cannot save indexed png files so we cannot compare.
/// </summary>
[Config(typeof(Config.ShortClr))]
public class EncodeIndexedPng : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeIndexedPng
{
// System.Drawing needs this.
private Stream bmpStream;
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
if (this.bmpStream == null)
{
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car));
this.bmpCore = CoreImage.Load<Rgba32>(this.bmpStream);
this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpStream.Position = 0;
}
}
@ -37,67 +37,56 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup()
{
this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose();
}
[Benchmark(Baseline = true, Description = "ImageSharp Octree Png")]
public void PngCoreOctree()
{
using (var memoryStream = new MemoryStream())
{
var options = new PngEncoder { Quantizer = KnownQuantizers.Octree };
this.bmpCore.SaveAsPng(memoryStream, options);
}
using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = KnownQuantizers.Octree };
this.bmpCore.SaveAsPng(memoryStream, options);
}
[Benchmark(Description = "ImageSharp Octree NoDither Png")]
public void PngCoreOctreeNoDither()
{
using (var memoryStream = new MemoryStream())
{
var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options);
}
using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options);
}
[Benchmark(Description = "ImageSharp Palette Png")]
public void PngCorePalette()
{
using (var memoryStream = new MemoryStream())
{
var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe };
this.bmpCore.SaveAsPng(memoryStream, options);
}
using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe };
this.bmpCore.SaveAsPng(memoryStream, options);
}
[Benchmark(Description = "ImageSharp Palette NoDither Png")]
public void PngCorePaletteNoDither()
{
using (var memoryStream = new MemoryStream())
{
var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options);
}
using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options);
}
[Benchmark(Description = "ImageSharp Wu Png")]
public void PngCoreWu()
{
using (var memoryStream = new MemoryStream())
{
var options = new PngEncoder { Quantizer = KnownQuantizers.Wu };
this.bmpCore.SaveAsPng(memoryStream, options);
}
using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = KnownQuantizers.Wu };
this.bmpCore.SaveAsPng(memoryStream, options);
}
[Benchmark(Description = "ImageSharp Wu NoDither Png")]
public void PngCoreWuNoDither()
{
using (var memoryStream = new MemoryStream())
{
var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options);
}
using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options);
}
}
}

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

@ -11,8 +11,8 @@ using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class EncodePng : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class EncodePng
{
// System.Drawing needs this.
private Stream bmpStream;
@ -39,6 +39,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
public void Cleanup()
{
this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose();
this.bmpDrawing.Dispose();
}
@ -46,20 +47,16 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Baseline = true, Description = "System.Drawing Png")]
public void PngSystemDrawing()
{
using (var memoryStream = new MemoryStream())
{
this.bmpDrawing.Save(memoryStream, ImageFormat.Png);
}
using var memoryStream = new MemoryStream();
this.bmpDrawing.Save(memoryStream, ImageFormat.Png);
}
[Benchmark(Description = "ImageSharp Png")]
public void PngCore()
{
using (var memoryStream = new MemoryStream())
{
var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None };
this.bmpCore.SaveAsPng(memoryStream, encoder);
}
using var memoryStream = new MemoryStream();
var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None };
this.bmpCore.SaveAsPng(memoryStream, encoder);
}
}
}

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

@ -2,23 +2,21 @@
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using ImageMagick;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class EncodeTga : BenchmarkBase
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeTga
{
private MagickImage tgaMagick;
private Image<Rgba32> tgaCore;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
private string TestImageFullPath
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tga.Bit24BottomLeft)]
public string TestImage { get; set; }
@ -33,22 +31,26 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
}
}
[GlobalCleanup]
public void Cleanup()
{
this.tgaCore.Dispose();
this.tgaCore = null;
this.tgaMagick.Dispose();
}
[Benchmark(Baseline = true, Description = "Magick Tga")]
public void BmpSystemDrawing()
public void TgaMagick()
{
using (var memoryStream = new MemoryStream())
{
this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
}
using var memoryStream = new MemoryStream();
this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
}
[Benchmark(Description = "ImageSharp Tga")]
public void BmpCore()
public void TgaImageSharp()
{
using (var memoryStream = new MemoryStream())
{
this.tgaCore.SaveAsBmp(memoryStream);
}
using var memoryStream = new MemoryStream();
this.tgaCore.SaveAsTga(memoryStream);
}
}
}

114
tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[MarkdownExporter]
[HtmlExporter]
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeTiff
{
private System.Drawing.Image drawing;
private Image<Rgba32> core;
private Configuration configuration;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.RgbUncompressed)]
public string TestImage { get; set; }
[Params(
TiffEncoderCompression.None,
////TiffEncoderCompression.Deflate,
TiffEncoderCompression.Lzw,
////TiffEncoderCompression.PackBits,
TiffEncoderCompression.CcittGroup3Fax,
TiffEncoderCompression.ModifiedHuffman)]
public TiffEncoderCompression Compression { get; set; }
[GlobalSetup]
public void ReadImages()
{
if (this.core == null)
{
this.configuration = new Configuration();
this.configuration.AddTiff();
this.core = Image.Load<Rgba32>(this.configuration, this.TestImageFullPath);
this.drawing = System.Drawing.Image.FromFile(this.TestImageFullPath);
}
}
[GlobalCleanup]
public void Cleanup()
{
this.core.Dispose();
this.drawing.Dispose();
}
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")]
public void SystemDrawing()
{
ImageCodecInfo codec = FindCodecForType("image/tiff");
using var parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression));
using var memoryStream = new MemoryStream();
this.drawing.Save(memoryStream, codec, parameters);
}
[Benchmark(Description = "ImageSharp Tiff")]
public void TiffCore()
{
var encoder = new TiffEncoder() { Compression = this.Compression };
using var memoryStream = new MemoryStream();
this.core.SaveAsTiff(memoryStream, encoder);
}
private static ImageCodecInfo FindCodecForType(string mimeType)
{
ImageCodecInfo[] imgEncoders = ImageCodecInfo.GetImageEncoders();
for (int i = 0; i < imgEncoders.GetLength(0); i++)
{
if (imgEncoders[i].MimeType == mimeType)
{
return imgEncoders[i];
}
}
return null;
}
private static EncoderValue Cast(TiffEncoderCompression compression)
{
switch (compression)
{
case TiffEncoderCompression.None:
return EncoderValue.CompressionNone;
case TiffEncoderCompression.CcittGroup3Fax:
return EncoderValue.CompressionCCITT3;
case TiffEncoderCompression.ModifiedHuffman:
return EncoderValue.CompressionRle;
case TiffEncoderCompression.Lzw:
return EncoderValue.CompressionLZW;
default:
throw new System.ArgumentOutOfRangeException(nameof(compression));
}
}
}
}

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

35
tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs

@ -0,0 +1,35 @@
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components
{
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Block8x8F_Scale16X16To8X8
{
private Block8x8F source;
private readonly Block8x8F[] target = new Block8x8F[4];
[GlobalSetup]
public void Setup()
{
var random = new Random();
float[] f = new float[8*8];
for (int i = 0; i < f.Length; i++)
{
f[i] = (float)random.NextDouble();
}
for (int i = 0; i < 4; i++)
{
this.target[i] = Block8x8F.Load(f);
}
this.source = Block8x8F.Load(f);
}
[Benchmark]
public void Scale16X16To8X8() => Block8x8F.Scale16X16To8X8(ref this.source, this.target);
}
}

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

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes;
@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class CmykColorConversion : ColorConversionBenchmark
{
public CmykColorConversion()
@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromCmykBasic(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromCmykBasic(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromCmykVector8(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromCmykVector8(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromCmykAvx2(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromCmykAvx2(8).ConvertToRgba(values, this.Output);
}
}
}

20
tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -11,27 +11,27 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public abstract class ColorConversionBenchmark
{
private readonly int componentCount;
protected Buffer2D<float>[] input;
protected Vector4[] output;
public const int Count = 128;
protected ColorConversionBenchmark(int componentCount)
{
this.componentCount = componentCount;
}
=> this.componentCount = componentCount;
public const int Count = 128;
protected Buffer2D<float>[] Input { get; private set; }
protected Vector4[] Output { get; private set; }
[GlobalSetup]
public void Setup()
{
this.input = CreateRandomValues(this.componentCount, Count);
this.output = new Vector4[Count];
this.Input = CreateRandomValues(this.componentCount, Count);
this.Output = new Vector4[Count];
}
[GlobalCleanup]
public void Cleanup()
{
foreach (Buffer2D<float> buffer in this.input)
foreach (Buffer2D<float> buffer in this.Input)
{
buffer.Dispose();
}

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

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

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

@ -2,22 +2,20 @@
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
/// <summary>
/// An expensive Jpeg benchmark, running on a wide range of input images, showing aggregate results.
/// An expensive Jpeg benchmark, running on a wide range of input images,
/// showing aggregate results.
/// </summary>
[Config(typeof(MultiImageBenchmarkBase.Config))]
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles =>
@ -35,14 +33,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark]
public void ImageSharp()
{
this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
}
=> this.ForEachStream(ms => Image.Load<Rgba32>(ms, new JpegDecoder()));
[Benchmark(Baseline = true)]
public void SystemDrawing()
{
this.ForEachStream(SDImage.FromStream);
}
=> this.ForEachStream(SDImage.FromStream);
}
}

20
tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs

@ -3,11 +3,6 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
@ -20,22 +15,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
/// <summary>
/// Image-specific Jpeg benchmarks
/// </summary>
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class DecodeJpeg_ImageSpecific
{
public class Config : ManualConfig
{
public Config() => this.AddDiagnoser(MemoryDiagnoser.Default);
public class ShortClr : Benchmarks.Config
{
public ShortClr() =>
// Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3),
this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(2).WithIterationCount(3));
}
}
private byte[] jpegBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);

32
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs

@ -1,23 +1,20 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
public class EncodeJpeg : BenchmarkBase
public class EncodeJpeg
{
// System.Drawing needs this.
private Stream bmpStream;
private Image bmpDrawing;
private SDImage bmpDrawing;
private Image<Rgba32> bmpCore;
[GlobalSetup]
@ -27,9 +24,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
const string TestImage = TestImages.Bmp.Car;
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = CoreImage.Load<Rgba32>(this.bmpStream);
this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpStream.Position = 0;
this.bmpDrawing = Image.FromStream(this.bmpStream);
this.bmpDrawing = SDImage.FromStream(this.bmpStream);
}
}
@ -37,6 +34,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
public void Cleanup()
{
this.bmpStream.Dispose();
this.bmpStream = null;
this.bmpCore.Dispose();
this.bmpDrawing.Dispose();
}
@ -44,19 +42,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")]
public void JpegSystemDrawing()
{
using (var stream = new MemoryStream())
{
this.bmpDrawing.Save(stream, ImageFormat.Jpeg);
}
using var stream = new MemoryStream();
this.bmpDrawing.Save(stream, ImageFormat.Jpeg);
}
[Benchmark(Description = "ImageSharp Jpeg")]
public void JpegCore()
{
using (var stream = new MemoryStream())
{
this.bmpCore.SaveAsJpeg(stream);
}
using var stream = new MemoryStream();
this.bmpCore.SaveAsJpeg(stream);
}
}
}

10
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegMultiple.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Formats.Jpeg;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))] // It's long enough to iterate through multiple files
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeJpegMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles => new[] { "Bmp/", "Jpg/baseline" };
@ -17,22 +17,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Description = "EncodeJpegMultiple - ImageSharp")]
public void EncodeJpegImageSharp()
{
this.ForEachImageSharpImage((img, ms) =>
=> this.ForEachImageSharpImage((img, ms) =>
{
img.Save(ms, new JpegEncoder());
return null;
});
}
[Benchmark(Baseline = true, Description = "EncodeJpegMultiple - System.Drawing")]
public void EncodeJpegSystemDrawing()
{
this.ForEachSystemDrawingImage((img, ms) =>
=> this.ForEachSystemDrawingImage((img, ms) =>
{
img.Save(ms, ImageFormat.Jpeg);
return null;
});
}
}
}

10
tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class GrayscaleColorConversion : ColorConversionBenchmark
{
public GrayscaleColorConversion()
@ -17,17 +17,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgba(values, this.Output);
}
}
}

13
tests/ImageSharp.Benchmarks/Codecs/Jpeg/IdentifyJpeg.cs

@ -1,15 +1,14 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class IdentifyJpeg
{
private byte[] jpegBytes;
@ -31,11 +30,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark]
public IImageInfo Identify()
{
using (var memoryStream = new MemoryStream(this.jpegBytes))
{
var decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
}
using var memoryStream = new MemoryStream(this.jpegBytes);
var decoder = new JpegDecoder();
return decoder.Identify(Configuration.Default, memoryStream);
}
}
}

64
tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_Aggregate.cs

@ -6,9 +6,7 @@ using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -17,7 +15,7 @@ using SixLabors.ImageSharp.Tests;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(MultiImageBenchmarkBase.Config))]
[Config(typeof(Config.ShortMultiFramework))]
public class LoadResizeSave_Aggregate : MultiImageBenchmarkBase
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles =>
@ -48,49 +46,43 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)]
public void SystemDrawing()
{
this.ForEachStream(
=> this.ForEachStream(
sourceStream =>
{
using (var destStream = new MemoryStream(this.destBytes))
using (var source = System.Drawing.Image.FromStream(sourceStream))
using (var destination = new Bitmap(source.Width / 4, source.Height / 4))
{
using (var destStream = new MemoryStream(this.destBytes))
using (var source = System.Drawing.Image.FromStream(sourceStream))
using (var destination = new Bitmap(source.Width / 4, source.Height / 4))
using (var g = Graphics.FromImage(destination))
{
using (var g = Graphics.FromImage(destination))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(source, 0, 0, 400, 400);
}
destination.Save(destStream, ImageFormat.Jpeg);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(source, 0, 0, 400, 400);
}
return null;
});
}
destination.Save(destStream, ImageFormat.Jpeg);
}
return null;
});
[Benchmark]
public void ImageSharp()
{
this.ForEachStream(
=> this.ForEachStream(
sourceStream =>
{
using (var source = Image.Load<Rgba32>(
this.configuration,
sourceStream,
new JpegDecoder { IgnoreMetadata = true }))
{
using (var source = Image.Load<Rgba32>(
this.configuration,
sourceStream,
new JpegDecoder { IgnoreMetadata = true }))
{
using (var destStream = new MemoryStream(this.destBytes))
{
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4));
source.SaveAsJpeg(destStream);
}
}
using var destStream = new MemoryStream(this.destBytes);
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4));
source.SaveAsJpeg(destStream);
}
return null;
});
}
return null;
});
}
}

32
tests/ImageSharp.Benchmarks/Codecs/Jpeg/LoadResizeSave_ImageSpecific.cs

@ -7,17 +7,15 @@ using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class LoadResizeSave_ImageSpecific
{
private readonly Configuration configuration = new Configuration(new JpegConfigurationModule());
@ -32,6 +30,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.BadRstProgressive518_Large444YCbCr,
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr)]
public string TestImage { get; set; }
[Params(false, true)]
@ -51,28 +50,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)]
public void SystemDrawing()
{
using (var sourceStream = new MemoryStream(this.sourceBytes))
using (var destStream = new MemoryStream(this.destBytes))
using (var source = SDImage.FromStream(sourceStream))
using (var destination = new Bitmap(source.Width / 4, source.Height / 4))
using var sourceStream = new MemoryStream(this.sourceBytes);
using var destStream = new MemoryStream(this.destBytes);
using var source = SDImage.FromStream(sourceStream);
using var destination = new Bitmap(source.Width / 4, source.Height / 4);
using (var g = Graphics.FromImage(destination))
{
using (var g = Graphics.FromImage(destination))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(source, 0, 0, 400, 400);
}
destination.Save(destStream, ImageFormat.Jpeg);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(source, 0, 0, 400, 400);
}
destination.Save(destStream, ImageFormat.Jpeg);
}
[Benchmark]
public void ImageSharp()
{
var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true });
using (source)
using (var source = Image.Load(this.configuration, this.sourceBytes, new JpegDecoder { IgnoreMetadata = true }))
using (var destStream = new MemoryStream(this.destBytes))
{
source.Mutate(c => c.Resize(source.Width / 4, source.Height / 4));

14
tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class RgbColorConversion : ColorConversionBenchmark
{
public RgbColorConversion()
@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromRgbBasic(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromRgbBasic(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromRgbVector8(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromRgbVector8(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromRgbAvx2(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromRgbAvx2(8).ConvertToRgba(values, this.Output);
}
}
}

19
tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs

@ -2,12 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class YCbCrColorConversion : ColorConversionBenchmark
{
public YCbCrColorConversion()
@ -18,33 +17,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgba(values, this.Output);
}
[Benchmark(Baseline = true)]
public void SimdVector()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgba(values, this.Output);
}
}
}

14
tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class YccKColorConverter : ColorConversionBenchmark
{
public YccKColorConverter()
@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
[Benchmark(Baseline = true)]
public void Scalar()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromYccKBasic(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromYccKBasic(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVector8()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromYccKVector8(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromYccKVector8(8).ConvertToRgba(values, this.Output);
}
[Benchmark]
public void SimdVectorAvx2()
{
var values = new JpegColorConverter.ComponentValues(this.input, 0);
var values = new JpegColorConverter.ComponentValues(this.Input, 0);
new JpegColorConverter.FromYccKAvx2(8).ConvertToRgba(values, this.output);
new JpegColorConverter.FromYccKAvx2(8).ConvertToRgba(values, this.Output);
}
}
}

112
tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs

@ -8,33 +8,18 @@ using System.IO;
using System.Linq;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using CoreImage = SixLabors.ImageSharp.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
public abstract class MultiImageBenchmarkBase
{
public class Config : ManualConfig
{
public Config() => this.AddDiagnoser(MemoryDiagnoser.Default);
public class ShortClr : Benchmarks.Config
{
public ShortClr() => this.AddJob(Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(1).WithIterationCount(2));
}
}
protected Dictionary<string, byte[]> FileNamesToBytes { get; set; } = new Dictionary<string, byte[]>();
protected Dictionary<string, Image<Rgba32>> FileNamesToImageSharpImages { get; set; } = new Dictionary<string, Image<Rgba32>>();
protected Dictionary<string, Bitmap> FileNamesToSystemDrawingImages { get; set; } = new Dictionary<string, System.Drawing.Bitmap>();
protected Dictionary<string, Bitmap> FileNamesToSystemDrawingImages { get; set; } = new Dictionary<string, Bitmap>();
/// <summary>
/// The values of this enum separate input files into categories.
@ -72,7 +57,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
/// <summary>
/// Gets folders containing files OR files to be processed by the benchmark.
/// </summary>
protected IEnumerable<string> AllFoldersOrFiles => this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f));
protected IEnumerable<string> AllFoldersOrFiles
=> this.InputImageSubfoldersOrFiles.Select(f => Path.Combine(this.BaseFolder, f));
/// <summary>
/// Gets the large image threshold.
@ -83,19 +69,13 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
protected IEnumerable<KeyValuePair<string, T>> EnumeratePairsByBenchmarkSettings<T>(
Dictionary<string, T> input,
Predicate<T> checkIfSmall)
{
switch (this.InputCategory)
=> this.InputCategory switch
{
case InputImageCategory.AllImages:
return input;
case InputImageCategory.SmallImagesOnly:
return input.Where(kv => checkIfSmall(kv.Value));
case InputImageCategory.LargeImagesOnly:
return input.Where(kv => !checkIfSmall(kv.Value));
default:
throw new ArgumentOutOfRangeException();
}
}
InputImageCategory.AllImages => input,
InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)),
InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)),
_ => throw new ArgumentOutOfRangeException(),
};
protected IEnumerable<KeyValuePair<string, byte[]>> FileNames2Bytes
=>
@ -150,17 +130,15 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
foreach (KeyValuePair<string, byte[]> kv in this.FileNames2Bytes)
{
using (var memoryStream = new MemoryStream(kv.Value))
using var memoryStream = new MemoryStream(kv.Value);
try
{
try
{
object obj = operation(memoryStream);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
object obj = operation(memoryStream);
(obj as IDisposable)?.Dispose();
}
catch (Exception ex)
{
Console.WriteLine($"Operation on {kv.Key} failed with {ex.Message}");
}
}
}
@ -178,7 +156,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
using (var ms1 = new MemoryStream(bytes))
{
this.FileNamesToImageSharpImages[fn] = CoreImage.Load<Rgba32>(ms1);
this.FileNamesToImageSharpImages[fn] = Image.Load<Rgba32>(ms1);
}
this.FileNamesToSystemDrawingImages[fn] = new Bitmap(new MemoryStream(bytes));
@ -191,7 +169,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
this.FileNamesToImageSharpImages,
img => img.Width * img.Height < this.LargeImageThresholdInPixels);
protected IEnumerable<KeyValuePair<string, System.Drawing.Bitmap>> FileNames2SystemDrawingImages
protected IEnumerable<KeyValuePair<string, Bitmap>> FileNames2SystemDrawingImages
=>
this.EnumeratePairsByBenchmarkSettings(
this.FileNamesToSystemDrawingImages,
@ -217,22 +195,20 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
protected void ForEachImageSharpImage(Func<Image<Rgba32>, MemoryStream, object> operation)
{
using (var workStream = new MemoryStream())
{
this.ForEachImageSharpImage(
img =>
{
// ReSharper disable AccessToDisposedClosure
object result = operation(img, workStream);
workStream.Seek(0, SeekOrigin.Begin);
// ReSharper restore AccessToDisposedClosure
return result;
});
}
using var workStream = new MemoryStream();
this.ForEachImageSharpImage(
img =>
{
// ReSharper disable AccessToDisposedClosure
object result = operation(img, workStream);
workStream.Seek(0, SeekOrigin.Begin);
// ReSharper restore AccessToDisposedClosure
return result;
});
}
protected void ForEachSystemDrawingImage(Func<System.Drawing.Bitmap, object> operation)
protected void ForEachSystemDrawingImage(Func<Bitmap, object> operation)
{
foreach (KeyValuePair<string, Bitmap> kv in this.FileNames2SystemDrawingImages)
{
@ -248,21 +224,19 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
}
}
protected void ForEachSystemDrawingImage(Func<System.Drawing.Bitmap, MemoryStream, object> operation)
protected void ForEachSystemDrawingImage(Func<Bitmap, MemoryStream, object> operation)
{
using (var workStream = new MemoryStream())
{
this.ForEachSystemDrawingImage(
img =>
{
// ReSharper disable AccessToDisposedClosure
object result = operation(img, workStream);
workStream.Seek(0, SeekOrigin.Begin);
// ReSharper restore AccessToDisposedClosure
return result;
});
}
using var workStream = new MemoryStream();
this.ForEachSystemDrawingImage(
img =>
{
// ReSharper disable AccessToDisposedClosure
object result = operation(img, workStream);
workStream.Seek(0, SeekOrigin.Begin);
// ReSharper restore AccessToDisposedClosure
return result;
});
}
}
}

2
tests/ImageSharp.Benchmarks/Color/Bulk/FromVector4_Rgb24.cs

@ -6,7 +6,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class FromVector4_Rgb24 : FromVector4<Rgb24>
{
}

2
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Bgra32.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class ToVector4_Bgra32 : ToVector4<Bgra32>
{
[Benchmark(Baseline = true)]

2
tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4_Rgb24.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.ColorSpaces.Bulk
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class ToVector4_Rgb24 : ToVector4<Rgb24>
{
[Benchmark(Baseline = true)]

10
tests/ImageSharp.Benchmarks/Config.cs

@ -9,6 +9,7 @@ using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
namespace SixLabors.ImageSharp.Benchmarks
{
@ -25,6 +26,7 @@ namespace SixLabors.ImageSharp.Benchmarks
}
#endif
this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(40);
}
public class MultiFramework : Config
@ -35,12 +37,12 @@ namespace SixLabors.ImageSharp.Benchmarks
Job.Default.WithRuntime(CoreRuntime.Core31));
}
public class ShortClr : Config
public class ShortMultiFramework : Config
{
public ShortClr() => this.AddJob(
public ShortMultiFramework() => this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3),
Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3),
Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3));
Job.Default.WithRuntime(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3),
Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3));
}
public class ShortCore31 : Config

56
tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs

@ -0,0 +1,56 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder
{
public class YCbCrForwardConverterBenchmark
{
private RgbToYCbCrConverterLut converter;
private Rgb24[] data;
[GlobalSetup]
public void Setup()
{
this.converter = RgbToYCbCrConverterLut.Create();
var r = new Random(42);
this.data = new Rgb24[64];
var d = new byte[3];
for (int i = 0; i < this.data.Length; i++)
{
r.NextBytes(d);
this.data[i] = new Rgb24(d[0], d[1], d[2]);
}
}
[Benchmark(Baseline = true)]
public void ConvertLut()
{
Block8x8F y = default;
Block8x8F cb = default;
Block8x8F cr = default;
this.converter.Convert(this.data.AsSpan(), ref y, ref cb, ref cr);
}
[Benchmark]
public void ConvertVectorized()
{
Block8x8F y = default;
Block8x8F cb = default;
Block8x8F cr = default;
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert(this.data.AsSpan(), ref y, ref cb, ref cr);
}
}
}
}

2
tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs

@ -8,7 +8,7 @@ using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
namespace SixLabors.ImageSharp.Benchmarks.General
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class Adler32Benchmark
{
private byte[] data;

2
tests/ImageSharp.Benchmarks/General/CopyBuffers.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General
/// - Span.CopyTo() has terrible performance on classic .NET Framework
/// - Buffer.MemoryCopy() performance is good enough for all sizes (but needs pinning)
/// </summary>
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class CopyBuffers
{
private byte[] destArray;

2
tests/ImageSharp.Benchmarks/General/Crc32Benchmark.cs

@ -8,7 +8,7 @@ using SharpCrc32 = ICSharpCode.SharpZipLib.Checksum.Crc32;
namespace SixLabors.ImageSharp.Benchmarks.General
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class Crc32Benchmark
{
private byte[] data;

28
tests/ImageSharp.Benchmarks/General/GetSetPixel.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Benchmarks
{
public class GetSetPixel
{
[Benchmark(Baseline = true, Description = "System.Drawing GetSet pixel")]
public System.Drawing.Color GetSetSystemDrawing()
{
using var source = new Bitmap(400, 400);
source.SetPixel(200, 200, System.Drawing.Color.White);
return source.GetPixel(200, 200);
}
[Benchmark(Description = "ImageSharp GetSet pixel")]
public Rgba32 GetSetImageSharp()
{
using var image = new Image<Rgba32>(400, 400);
image[200, 200] = Color.White;
return image[200, 200];
}
}
}

2
tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs

@ -8,7 +8,7 @@ using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Benchmarks.IO
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class BufferedStreams
{
private readonly byte[] buffer = CreateTestBytes();

2
tests/ImageSharp.Benchmarks/General/Vectorization/UInt32ToSingle.cs

@ -8,7 +8,7 @@ using BenchmarkDotNet.Attributes;
namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class UInt32ToSingle
{
private float[] data;

2
tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs

@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.ShortMultiFramework))]
public class WidenBytesToUInt32
{
private byte[] source;

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

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

86
tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs

@ -1,21 +1,17 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
namespace SixLabors.ImageSharp.Benchmarks
{
using CoreSize = SixLabors.ImageSharp.Size;
public class PorterDuffBulkVsPixel : BenchmarkBase
public class PorterDuffBulkVsPixel
{
private Configuration Configuration => Configuration.Default;
@ -30,23 +26,21 @@ namespace SixLabors.ImageSharp.Benchmarks
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer =
Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
using IMemoryOwner<Vector4> buffer =
Configuration.Default.MemoryAllocator.Allocate<Vector4>(destination.Length * 3);
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, source, sourceSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, background, backgroundSpan);
PixelOperations<TPixel>.Instance.ToVector4(this.Configuration, source, sourceSpan);
for (int i = 0; i < destination.Length; i++)
{
destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination);
for (int i = 0; i < destination.Length; i++)
{
destinationSpan[i] = PorterDuffFunctions.NormalSrcOver(backgroundSpan[i], sourceSpan[i], amount[i]);
}
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.Configuration, destinationSpan, destination);
}
private void BulkPixelConvert<TPixel>(
@ -67,44 +61,36 @@ namespace SixLabors.ImageSharp.Benchmarks
}
[Benchmark(Description = "ImageSharp BulkVectorConvert")]
public CoreSize BulkVectorConvert()
public Size BulkVectorConvert()
{
using (var image = new Image<Rgba32>(800, 800))
{
using (IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width))
{
amounts.GetSpan().Fill(1);
using var image = new Image<Rgba32>(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkVectorConvert(span, span, span, amounts.GetSpan());
}
return new CoreSize(image.Width, image.Height);
}
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkVectorConvert(span, span, span, amounts.GetSpan());
}
return new Size(image.Width, image.Height);
}
[Benchmark(Description = "ImageSharp BulkPixelConvert")]
public CoreSize BulkPixelConvert()
public Size BulkPixelConvert()
{
using (var image = new Image<Rgba32>(800, 800))
using var image = new Image<Rgba32>(800, 800);
using IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width);
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
using (IMemoryOwner<float> amounts = Configuration.Default.MemoryAllocator.Allocate<float>(image.Width))
{
amounts.GetSpan().Fill(1);
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
for (int y = 0; y < image.Height; y++)
{
Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkPixelConvert(span, span, span, amounts.GetSpan());
}
return new CoreSize(image.Width, image.Height);
}
Span<Rgba32> span = pixels.GetRowSpan(y);
this.BulkPixelConvert(span, span, span, amounts.GetSpan());
}
return new Size(image.Width, image.Height);
}
}
}
}

8
tests/ImageSharp.Benchmarks/Samplers/BokehBlur.cs → tests/ImageSharp.Benchmarks/Processing/BokehBlur.cs

@ -5,7 +5,7 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks.Samplers
namespace SixLabors.ImageSharp.Benchmarks.Processing
{
[Config(typeof(Config.MultiFramework))]
public class BokehBlur
@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Samplers
[Benchmark]
public void Blur()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White))
{
image.Mutate(c => c.BokehBlur());
}
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.White);
image.Mutate(c => c.BokehBlur());
}
}
}

40
tests/ImageSharp.Benchmarks/Processing/Crop.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SDRectangle = System.Drawing.Rectangle;
using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Processing
{
[Config(typeof(Config.MultiFramework))]
public class Crop
{
[Benchmark(Baseline = true, Description = "System.Drawing Crop")]
public SDSize CropSystemDrawing()
{
using var source = new Bitmap(800, 800);
using var destination = new Bitmap(100, 100);
using var graphics = Graphics.FromImage(destination);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel);
return destination.Size;
}
[Benchmark(Description = "ImageSharp Crop")]
public Size CropImageSharp()
{
using var image = new Image<Rgba32>(800, 800);
image.Mutate(x => x.Crop(100, 100));
return new Size(image.Width, image.Height);
}
}
}

20
tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs → tests/ImageSharp.Benchmarks/Processing/DetectEdges.cs

@ -1,18 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks
{
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Processing;
using CoreImage = SixLabors.ImageSharp.Image;
public class DetectEdges : BenchmarkBase
[Config(typeof(Config.MultiFramework))]
public class DetectEdges
{
private Image<Rgba32> image;
@ -21,10 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks
{
if (this.image == null)
{
using (FileStream stream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"))
{
this.image = CoreImage.Load<Rgba32>(stream);
}
this.image = Image.Load<Rgba32>(File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Bmp.Car)));
}
}
@ -32,6 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks
public void Cleanup()
{
this.image.Dispose();
this.image = null;
}
[Benchmark(Description = "ImageSharp DetectEdges")]

20
tests/ImageSharp.Benchmarks/Samplers/Diffuse.cs → tests/ImageSharp.Benchmarks/Processing/Diffuse.cs

@ -5,31 +5,27 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks.Samplers
namespace SixLabors.ImageSharp.Benchmarks.Processing
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.MultiFramework))]
public class Diffuse
{
[Benchmark]
public Size DoDiffuse()
{
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond))
{
image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
using var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond);
image.Mutate(x => x.Dither(KnownDitherings.FloydSteinberg));
return image.Size();
}
return image.Size();
}
[Benchmark]
public Size DoDither()
{
using (var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond))
{
image.Mutate(x => x.Dither());
using var image = new Image<Rgba32>(Configuration.Default, 800, 800, Color.BlanchedAlmond);
image.Mutate(x => x.Dither());
return image.Size();
}
return image.Size();
}
}
}

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

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

31
tests/ImageSharp.Benchmarks/Processing/HistogramEqualization.cs

@ -10,8 +10,8 @@ using SixLabors.ImageSharp.Tests;
namespace SixLabors.ImageSharp.Benchmarks.Processing
{
[Config(typeof(Config.ShortClr))]
public class HistogramEqualization : BenchmarkBase
[Config(typeof(Config.MultiFramework))]
public class HistogramEqualization
{
private Image<Rgba32> image;
@ -28,26 +28,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Processing
public void Cleanup()
{
this.image.Dispose();
this.image = null;
}
[Benchmark(Description = "Global Histogram Equalization")]
public void GlobalHistogramEqualization()
{
this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions()
{
LuminanceLevels = 256,
Method = HistogramEqualizationMethod.Global
}));
}
=> this.image.Mutate(img => img.HistogramEqualization(
new HistogramEqualizationOptions()
{
LuminanceLevels = 256,
Method = HistogramEqualizationMethod.Global
}));
[Benchmark(Description = "AdaptiveHistogramEqualization (Tile interpolation)")]
public void AdaptiveHistogramEqualization()
{
this.image.Mutate(img => img.HistogramEqualization(new HistogramEqualizationOptions()
{
LuminanceLevels = 256,
Method = HistogramEqualizationMethod.AdaptiveTileInterpolation
}));
}
=> this.image.Mutate(img => img.HistogramEqualization(
new HistogramEqualizationOptions()
{
LuminanceLevels = 256,
Method = HistogramEqualizationMethod.AdaptiveTileInterpolation
}));
}
}

63
tests/ImageSharp.Benchmarks/Samplers/Resize.cs → tests/ImageSharp.Benchmarks/Processing/Resize.cs

@ -4,47 +4,49 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Globalization;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks
{
[Config(typeof(Config.ShortClr))]
#pragma warning disable SA1649 // File name should match first type name
public abstract class ResizeBenchmarkBase<TPixel>
#pragma warning restore SA1649 // File name should match first type name
[Config(typeof(Config.MultiFramework))]
public abstract class Resize<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
protected readonly Configuration Configuration = new Configuration(new JpegConfigurationModule());
private byte[] bytes = null;
private Image<TPixel> sourceImage;
private Bitmap sourceBitmap;
private SDImage sourceBitmap;
[Params("3032-400")]
public virtual string SourceToDest { get; set; }
protected int SourceSize { get; private set; }
protected Configuration Configuration { get; } = new Configuration(new JpegConfigurationModule());
protected int DestSize { get; private set; }
[GlobalSetup]
public virtual void Setup()
{
string[] stuff = this.SourceToDest.Split('-');
this.SourceSize = int.Parse(stuff[0], CultureInfo.InvariantCulture);
this.DestSize = int.Parse(stuff[1], CultureInfo.InvariantCulture);
this.sourceImage = new Image<TPixel>(this.Configuration, this.SourceSize, this.SourceSize);
this.sourceBitmap = new Bitmap(this.SourceSize, this.SourceSize);
if (this.bytes is null)
{
this.bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Snake));
this.sourceImage = Image.Load<TPixel>(this.bytes);
var ms1 = new MemoryStream(this.bytes);
this.sourceBitmap = SDImage.FromStream(ms1);
this.DestSize = this.sourceBitmap.Width / 2;
}
}
[GlobalCleanup]
public void Cleanup()
{
this.bytes = null;
this.sourceImage.Dispose();
this.sourceBitmap.Dispose();
}
@ -96,12 +98,10 @@ namespace SixLabors.ImageSharp.Benchmarks
protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx);
}
public class Resize_Bicubic_Rgba32 : ResizeBenchmarkBase<Rgba32>
public class Resize_Bicubic_Rgba32 : Resize<Rgba32>
{
protected override void ExecuteResizeOperation(IImageProcessingContext ctx)
{
ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic);
}
=> ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic);
// RESULTS - 2019 April - ResizeWorker:
//
@ -133,9 +133,6 @@ namespace SixLabors.ImageSharp.Benchmarks
[Params(128, 512, 1024, 8 * 1024)]
public int WorkingBufferSizeHintInKilobytes { get; set; }
[Params("3032-400", "4000-300")]
public override string SourceToDest { get; set; }
public override void Setup()
{
this.Configuration.WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInKilobytes * 1024;
@ -143,12 +140,10 @@ namespace SixLabors.ImageSharp.Benchmarks
}
}
public class Resize_Bicubic_Bgra32 : ResizeBenchmarkBase<Bgra32>
public class Resize_Bicubic_Bgra32 : Resize<Bgra32>
{
protected override void ExecuteResizeOperation(IImageProcessingContext ctx)
{
ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic);
}
=> ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic);
// RESULTS (2019 April):
//
@ -171,12 +166,10 @@ namespace SixLabors.ImageSharp.Benchmarks
// 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 96.96 ms | 7.899 ms | 0.4329 ms | 0.80 | - | - | - | 44512 B |
}
public class Resize_Bicubic_Rgb24 : ResizeBenchmarkBase<Rgb24>
public class Resize_Bicubic_Rgb24 : Resize<Rgb24>
{
protected override void ExecuteResizeOperation(IImageProcessingContext ctx)
{
ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic);
}
=> ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic);
// RESULTS (2019 April):
//
@ -197,12 +190,10 @@ namespace SixLabors.ImageSharp.Benchmarks
// 'ImageSharp, MaxDegreeOfParallelism = 1' | Core | Core | 3032 | 400 | 92.47 ms | 5.683 ms | 0.3115 ms | 0.78 | 0.01 | - | - | - | 44512 B |
}
public class Resize_BicubicCompand_Rgba32 : ResizeBenchmarkBase<Rgba32>
public class Resize_BicubicCompand_Rgba32 : Resize<Rgba32>
{
protected override void ExecuteResizeOperation(IImageProcessingContext ctx)
{
ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true);
}
=> ctx.Resize(this.DestSize, this.DestSize, KnownResamplers.Bicubic, true);
// RESULTS (2019 April):
//

12
tests/ImageSharp.Benchmarks/Samplers/Rotate.cs → tests/ImageSharp.Benchmarks/Processing/Rotate.cs

@ -5,20 +5,18 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks.Samplers
namespace SixLabors.ImageSharp.Benchmarks.Processing
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.MultiFramework))]
public class Rotate
{
[Benchmark]
public Size DoRotate()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond))
{
image.Mutate(x => x.Rotate(37.5F));
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond);
image.Mutate(x => x.Rotate(37.5F));
return image.Size();
}
return image.Size();
}
}
}

12
tests/ImageSharp.Benchmarks/Samplers/Skew.cs → tests/ImageSharp.Benchmarks/Processing/Skew.cs

@ -5,20 +5,18 @@ using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Benchmarks.Samplers
namespace SixLabors.ImageSharp.Benchmarks.Processing
{
[Config(typeof(Config.ShortClr))]
[Config(typeof(Config.MultiFramework))]
public class Skew
{
[Benchmark]
public Size DoSkew()
{
using (var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond))
{
image.Mutate(x => x.Skew(20, 10));
using var image = new Image<Rgba32>(Configuration.Default, 400, 400, Color.BlanchedAlmond);
image.Mutate(x => x.Skew(20, 10));
return image.Size();
}
return image.Size();
}
}
}

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

@ -1,45 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SDRectangle = System.Drawing.Rectangle;
using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks
{
[Config(typeof(Config.ShortClr))]
public class Crop : BenchmarkBase
{
[Benchmark(Baseline = true, Description = "System.Drawing Crop")]
public SDSize CropSystemDrawing()
{
using (var source = new Bitmap(800, 800))
using (var destination = new Bitmap(100, 100))
using (var graphics = Graphics.FromImage(destination))
{
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.DrawImage(source, new SDRectangle(0, 0, 100, 100), 0, 0, 100, 100, GraphicsUnit.Pixel);
return destination.Size;
}
}
[Benchmark(Description = "ImageSharp Crop")]
public Size CropResizeCore()
{
using (var image = new Image<Rgba32>(800, 800))
{
image.Mutate(x => x.Crop(100, 100));
return new Size(image.Width, image.Height);
}
}
}
}

91
tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs

@ -0,0 +1,91 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.Colorspaces.Conversion;
using Xunit;
using Xunit.Abstractions;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public class RgbToYCbCrConverterTests
{
public RgbToYCbCrConverterTests(ITestOutputHelper output)
{
this.Output = output;
}
private ITestOutputHelper Output { get; }
[Fact]
public void TestLutConverter()
{
Rgb24[] data = CreateTestData();
var target = RgbToYCbCrConverterLut.Create();
Block8x8F y = default;
Block8x8F cb = default;
Block8x8F cr = default;
target.Convert(data.AsSpan(), ref y, ref cb, ref cr);
Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F));
}
[Fact]
public void TestVectorizedConverter()
{
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.Output.WriteLine("No AVX and/or FMA present, skipping test!");
return;
}
Rgb24[] data = CreateTestData();
Block8x8F y = default;
Block8x8F cb = default;
Block8x8F cr = default;
RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr);
Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F));
}
private static void Verify(ReadOnlySpan<Rgb24> data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, ApproximateColorSpaceComparer comparer)
{
for (int i = 0; i < data.Length; i++)
{
int r = data[i].R;
int g = data[i].G;
int b = data[i].B;
float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
Assert.True(comparer.Equals(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y} == {yResult[i]}, {cb} == {cbResult[i]}, {cr} == {crResult[i]}");
}
}
private static Rgb24[] CreateTestData()
{
var data = new Rgb24[64];
var r = new Random();
var random = new byte[3];
for (int i = 0; i < data.Length; i++)
{
r.NextBytes(random);
data[i] = new Rgb24(random[0], random[1], random[2]);
}
return data;
}
}
}

41
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -39,10 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFileCollection(nameof(NotSupportedImages), PixelTypes.Rgba32)]
public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder));
}
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder));
[Theory]
[InlineData(TestImages.Tiff.RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
@ -91,10 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Uncompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestTiffDecoder(provider);
}
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)]
@ -103,10 +97,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(RgbPaletteDeflate, PixelTypes.Rgba32)]
[WithFile(PaletteUncompressed, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_WithPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestTiffDecoder(provider);
}
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
@ -118,10 +109,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(RgbDeflatePredictor, PixelTypes.Rgba32)]
[WithFile(SmallRgbDeflate, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_DeflateCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestTiffDecoder(provider);
}
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(RgbLzwPredictor, PixelTypes.Rgba32)]
@ -134,10 +122,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(Calliphora_GrayscaleLzw_Predictor, PixelTypes.Rgba32)]
[WithFile(SmallRgbLzw, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_LzwCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestTiffDecoder(provider);
}
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(HuffmanRleAllTermCodes, PixelTypes.Rgba32)]
@ -145,30 +130,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[WithFile(HuffmanRle_basi3p02, PixelTypes.Rgba32)]
[WithFile(Calliphora_HuffmanCompressed, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_HuffmanCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestTiffDecoder(provider);
}
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(CcittFax3AllTermCodes, PixelTypes.Rgba32)]
[WithFile(CcittFax3AllMakeupCodes, PixelTypes.Rgba32)]
[WithFile(Calliphora_Fax3Compressed, PixelTypes.Rgba32)]
[WithFile(Calliphora_Fax3Compressed_WithEolPadding, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Fax3Compressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestTiffDecoder(provider);
}
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Calliphora_RgbPackbits, PixelTypes.Rgba32)]
[WithFile(RgbPackbits, PixelTypes.Rgba32)]
[WithFile(RgbPackbitsMultistrip, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_PackBitsCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
TestTiffDecoder(provider);
}
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]

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

@ -160,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
(s, d) =>
{
Span<TPixel> destPixels = d.GetSpan();
this.Operations.FromVector4Destructive(this.Configuration, (Span<Vector4>)s, destPixels, PixelConversionModifiers.Scale);
this.Operations.FromVector4Destructive(this.Configuration, s, destPixels, PixelConversionModifiers.Scale);
});
}
@ -168,15 +168,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[MemberData(nameof(ArraySizesData))]
public void FromCompandedScaledVector4(int count)
{
void SourceAction(ref Vector4 v)
{
SRgbCompanding.Expand(ref v);
}
void SourceAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
void ExpectedAction(ref Vector4 v)
{
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v) => SRgbCompanding.Compress(ref v);
Vector4[] source = CreateVector4TestData(count, (ref Vector4 v) => SourceAction(ref v));
TPixel[] expected = CreateScaledExpectedPixelData(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -188,7 +182,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale));
PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale),
false);
}
[Theory]
@ -219,7 +214,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(this.Configuration, s, d.GetSpan(), modifiers);
@ -254,7 +250,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
@ -297,7 +294,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
expected,
(s, d) =>
{
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha ? PixelConversionModifiers.Premultiply
PixelConversionModifiers modifiers = this.HasUnassociatedAlpha
? PixelConversionModifiers.Premultiply
: PixelConversionModifiers.None;
this.Operations.FromVector4Destructive(
@ -305,7 +303,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
s,
d.GetSpan(),
modifiers | PixelConversionModifiers.SRgbCompand | PixelConversionModifiers.Scale);
});
},
false);
}
[Theory]
@ -343,7 +342,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
PixelConverterTests.ReferenceImplementations.To<TPixel, TDestPixel>(this.Configuration, source, expected);
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, (ReadOnlySpan<TPixel>)s, d.GetSpan()));
TestOperation(source, expected, (s, d) => this.Operations.To(this.Configuration, s, d.GetSpan()));
}
[Theory]
@ -356,11 +355,11 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
TestOperation(
source,
expected,
(s, d) =>
{
Span<Vector4> destVectors = d.GetSpan();
this.Operations.ToVector4(this.Configuration, (ReadOnlySpan<TPixel>)s, destVectors, PixelConversionModifiers.Scale);
});
(s, d) => this.Operations.ToVector4(
this.Configuration,
s,
d.GetSpan(),
PixelConversionModifiers.Scale));
}
[Theory]
@ -369,13 +368,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v)
{
SRgbCompanding.Expand(ref v);
}
void ExpectedAction(ref Vector4 v) => SRgbCompanding.Expand(ref v);
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -396,13 +391,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
}
void ExpectedAction(ref Vector4 v)
{
Numerics.Premultiply(ref v);
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
TPixel[] source = CreatePixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -419,13 +410,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
}
void ExpectedAction(ref Vector4 v)
{
Numerics.Premultiply(ref v);
}
void ExpectedAction(ref Vector4 v) => Numerics.Premultiply(ref v);
TPixel[] source = CreateScaledPixelTestData(count, (ref Vector4 v) => SourceAction(ref v));
Vector4[] expected = CreateExpectedScaledVector4Data(source, (ref Vector4 v) => ExpectedAction(ref v));
@ -446,8 +433,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
void SourceAction(ref Vector4 v)
{
Numerics.UnPremultiply(ref v);
SRgbCompanding.Compress(ref v);
}
void ExpectedAction(ref Vector4 v)
@ -1006,15 +991,9 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
[Theory]
[MemberData(nameof(ArraySizesData))]
public void PackFromRgbPlanes(int count)
{
SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
=> SimdUtilsTests.TestPackFromRgbPlanes<TPixel>(
count,
(
r,
g,
b,
actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
}
(r, g, b, actual) => PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.Configuration, r, g, b, actual));
public delegate void RefAction<T1>(ref T1 arg1);
@ -1053,11 +1032,12 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
internal static void TestOperation<TSource, TDest>(
TSource[] source,
TDest[] expected,
Action<TSource[], IMemoryOwner<TDest>> action)
Action<TSource[], IMemoryOwner<TDest>> action,
bool preferExactComparison = true)
where TSource : struct
where TDest : struct
{
using (var buffers = new TestBuffers<TSource, TDest>(source, expected))
using (var buffers = new TestBuffers<TSource, TDest>(source, expected, preferExactComparison))
{
action(buffers.SourceBuffer, buffers.ActualDestBuffer);
buffers.Verify();
@ -1071,7 +1051,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
result[i] = v;
@ -1088,7 +1068,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
@ -1106,7 +1086,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
for (int i = 0; i < result.Length; i++)
{
Vector4 v = GetVector(rnd);
Vector4 v = GetScaledVector(rnd);
vectorModifier?.Invoke(ref v);
@ -1129,10 +1109,8 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
return result;
}
internal static Vector4 GetVector(Random rnd)
{
return new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
}
internal static Vector4 GetScaledVector(Random rnd)
=> new Vector4((float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble(), (float)rnd.NextDouble());
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct OctetBytes
@ -1160,11 +1138,14 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
public TDest[] ExpectedDestBuffer { get; }
public TestBuffers(TSource[] source, TDest[] expectedDest)
public bool PreferExactComparison { get; }
public TestBuffers(TSource[] source, TDest[] expectedDest, bool preferExactComparison = true)
{
this.SourceBuffer = source;
this.ExpectedDestBuffer = expectedDest;
this.ActualDestBuffer = Configuration.Default.MemoryAllocator.Allocate<TDest>(expectedDest.Length);
this.PreferExactComparison = preferExactComparison;
}
public void Dispose() => this.ActualDestBuffer.Dispose();
@ -1177,26 +1158,54 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations
{
Span<Vector4> expected = MemoryMarshal.Cast<TDest, Vector4>(this.ExpectedDestBuffer.AsSpan());
Span<Vector4> actual = MemoryMarshal.Cast<TDest, Vector4>(this.ActualDestBuffer.GetSpan());
var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F);
var comparer = new ApproximateFloatComparer(0.001f);
for (int i = 0; i < count; i++)
{
// ReSharper disable PossibleNullReferenceException
Assert.Equal(expected[i], actual[i], comparer);
}
}
else if (!this.PreferExactComparison && typeof(IPixel).IsAssignableFrom(typeof(TDest)) && IsComplexPixel())
{
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> actual = this.ActualDestBuffer.GetSpan();
var comparer = new ApproximateFloatComparer(TestEnvironment.Is64BitProcess ? 0.0001F : 0.001F);
// ReSharper restore PossibleNullReferenceException
for (int i = 0; i < count; i++)
{
Assert.Equal((IPixel)expected[i], (IPixel)actual[i], comparer);
}
}
else
{
Span<TDest> expected = this.ExpectedDestBuffer.AsSpan();
Span<TDest> actual = this.ActualDestBuffer.GetSpan();
for (int i = 0; i < count; i++)
{
Assert.Equal(expected[i], actual[i]);
}
}
}
// TODO: We really need a PixelTypeInfo.BitsPerComponent property!!
private static bool IsComplexPixel()
{
switch (default(TDest))
{
case HalfSingle _:
case HalfVector2 _:
case L16 _:
case La32 _:
case NormalizedShort2 _:
case Rg32 _:
case Short2 _:
return true;
default:
return Unsafe.SizeOf<TDest>() > sizeof(int);
}
}
}
}
}

28
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
testOutputDetails: workingBufferLimitInRows,
appendPixelTypeToFileName: false);
image.CompareToReferenceOutput(
ImageComparer.TolerantPercentage(0.001f),
ImageComparer.TolerantPercentage(0.004f),
provider,
testOutputDetails: workingBufferLimitInRows,
appendPixelTypeToFileName: false);
@ -216,6 +216,32 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
appendSourceFileOrDescription: false);
}
[Theory]
[WithFile(TestImages.Png.Kaboom, DefaultPixelType, false)]
[WithFile(TestImages.Png.Kaboom, DefaultPixelType, true)]
public void Resize_PremultiplyAlpha<TPixel>(TestImageProvider<TPixel> provider, bool premultiplyAlpha)
where TPixel : unmanaged, IPixel<TPixel>
{
string details = premultiplyAlpha ? "On" : "Off";
provider.RunValidatingProcessorTest(
x =>
{
var resizeOptions = new ResizeOptions()
{
Size = x.GetCurrentSize() / 2,
Mode = ResizeMode.Crop,
Sampler = KnownResamplers.Bicubic,
Compand = false,
PremultiplyAlpha = premultiplyAlpha
};
x.Resize(resizeOptions);
},
details,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
[Theory]
[WithFile(TestImages.Gif.Giphy, DefaultPixelType)]
public void Resize_IsAppliedToAllFrames<TPixel>(TestImageProvider<TPixel> provider)

15
tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs

@ -110,6 +110,21 @@ namespace SixLabors.ImageSharp.Tests
}
}
// Test case for issue: https://github.com/SixLabors/ImageSharp/issues/1505
[Theory]
[WithFile(TestImages.Gif.Issues.Issue1505, PixelTypes.Rgba32)]
public void Issue1505<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
{
var octreeQuantizer = new OctreeQuantizer();
IQuantizer<TPixel> quantizer = octreeQuantizer.CreatePixelSpecificQuantizer<TPixel>(Configuration.Default, new QuantizerOptions() { MaxColors = 128 });
ImageFrame<TPixel> frame = image.Frames[0];
quantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds());
}
}
private int GetTransparentIndex<TPixel>(IndexedImageFrame<TPixel> quantized)
where TPixel : unmanaged, IPixel<TPixel>
{

2
tests/ImageSharp.Tests/TestImages.cs

@ -415,6 +415,7 @@ namespace SixLabors.ImageSharp.Tests
public const string BadAppExtLength = "Gif/issues/issue405_badappextlength252.gif";
public const string BadAppExtLength_2 = "Gif/issues/issue405_badappextlength252-2.gif";
public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif";
public const string Issue1505 = "Gif/issues/issue1505_argumentoutofrange.png";
}
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };
@ -521,6 +522,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff";
public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff";
public const string Calliphora_Fax3Compressed = "Tiff/Calliphora_ccitt_fax3.tiff";
public const string Calliphora_Fax3Compressed_WithEolPadding = "Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff";
public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff";
public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff";
public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff";

32
tests/ImageSharp.Tests/TestUtilities/ApproximateFloatComparer.cs

@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
@ -12,6 +13,7 @@ namespace SixLabors.ImageSharp.Tests
internal readonly struct ApproximateFloatComparer :
IEqualityComparer<float>,
IEqualityComparer<Vector2>,
IEqualityComparer<IPixel>,
IEqualityComparer<Vector4>,
IEqualityComparer<ColorMatrix>
{
@ -32,30 +34,42 @@ namespace SixLabors.ImageSharp.Tests
}
/// <inheritdoc/>
public int GetHashCode(float obj) => obj.GetHashCode();
public int GetHashCode(float obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector2 x, Vector2 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
public bool Equals(Vector2 x, Vector2 y)
=> this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y);
/// <inheritdoc/>
public int GetHashCode(Vector2 obj) => obj.GetHashCode();
public int GetHashCode(Vector2 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y) => this.Equals(x.X, y.X) && this.Equals(x.Y, y.Y) && this.Equals(x.Z, y.Z) && this.Equals(x.W, y.W);
public bool Equals(IPixel x, IPixel y)
=> this.Equals(x.ToScaledVector4(), y.ToScaledVector4());
public int GetHashCode(IPixel obj)
=> obj.ToScaledVector4().GetHashCode();
/// <inheritdoc/>
public bool Equals(Vector4 x, Vector4 y)
=> this.Equals(x.X, y.X)
&& this.Equals(x.Y, y.Y)
&& this.Equals(x.Z, y.Z)
&& this.Equals(x.W, y.W);
/// <inheritdoc/>
public int GetHashCode(Vector4 obj) => obj.GetHashCode();
public int GetHashCode(Vector4 obj)
=> obj.GetHashCode();
/// <inheritdoc/>
public bool Equals(ColorMatrix x, ColorMatrix y)
{
return
this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
=> this.Equals(x.M11, y.M11) && this.Equals(x.M12, y.M12) && this.Equals(x.M13, y.M13) && this.Equals(x.M14, y.M14)
&& this.Equals(x.M21, y.M21) && this.Equals(x.M22, y.M22) && this.Equals(x.M23, y.M23) && this.Equals(x.M24, y.M24)
&& this.Equals(x.M31, y.M31) && this.Equals(x.M32, y.M32) && this.Equals(x.M33, y.M33) && this.Equals(x.M34, y.M34)
&& this.Equals(x.M41, y.M41) && this.Equals(x.M42, y.M42) && this.Equals(x.M43, y.M43) && this.Equals(x.M44, y.M44)
&& this.Equals(x.M51, y.M51) && this.Equals(x.M52, y.M52) && this.Equals(x.M53, y.M53) && this.Equals(x.M54, y.M54);
}
/// <inheritdoc/>
public int GetHashCode(ColorMatrix obj) => obj.GetHashCode();

2
tests/Images/External

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

3
tests/Images/Input/Gif/issues/issue1505_argumentoutofrange.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bea0faf09782d0d972e72ad94e53b8ca9c823fd3056fc6a97aba8c43105fcd66
size 102581

2
tests/Images/Input/Tiff/Benchmarks/.gitignore

@ -0,0 +1,2 @@
*.tiff
*.tif

4
tests/Images/Input/Tiff/Benchmarks/gen_big.ps1

@ -7,8 +7,8 @@ $Output_Prefix = ".\big"
& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff"
& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff"
& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff"
# & $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff"
# & $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax.tiff"
& $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff"
& $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax3.tiff"
& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff"
& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff"

4
tests/Images/Input/Tiff/Benchmarks/gen_medium.ps1

@ -7,8 +7,8 @@ $Output_Prefix = ".\medium"
& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff"
& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff"
& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff"
# & $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff"
# & $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax.tiff"
& $Gm_Exe convert $Source_Image -compress Group4 -type Bilevel $Output_Prefix"_bw_Group4.tiff"
& $Gm_Exe convert $Source_Image -compress Fax -type Bilevel $Output_Prefix"_bw_Fax3.tiff"
& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff"
& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff"

3
tests/Images/Input/Tiff/Calliphora_ccitt_fax3_with_eol_padding.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:15e0f4f04699c7253ce422a7741ada192615182da53e9fd86bdf547cd991b290
size 126382
Loading…
Cancel
Save