Browse Source

Merge branch 'SixLabors:main' into feat/ani

pull/2899/head
Poker 1 year ago
committed by GitHub
parent
commit
49cf55c020
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 325
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  2. 2
      src/ImageSharp/Formats/Png/PngThrowHelper.cs
  3. 31
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 4
      tests/ImageSharp.Benchmarks/Bulk/FromRgba32Bytes.cs
  5. 2
      tests/ImageSharp.Benchmarks/Bulk/Vector4Factory.cs
  6. 15
      tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs
  7. 6
      tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs
  8. 16
      tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs
  9. 8
      tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs
  10. 2
      tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs
  11. 13
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs
  12. 2
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs
  13. 60
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs
  14. 16
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpegParseStreamOnly.cs
  15. 8
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_Aggregate.cs
  16. 15
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg_ImageSpecific.cs
  17. 9
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs
  18. 10
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegFeatures.cs
  19. 26
      tests/ImageSharp.Benchmarks/Codecs/MultiImageBenchmarkBase.cs
  20. 26
      tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs
  21. 8
      tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs
  22. 4
      tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs
  23. 4
      tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs
  24. 44
      tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs
  25. 4
      tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs
  26. 2
      tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs
  27. 6
      tests/ImageSharp.Benchmarks/Config.cs
  28. 2
      tests/ImageSharp.Benchmarks/General/GetSetPixel.cs
  29. 8
      tests/ImageSharp.Benchmarks/General/IO/BufferedStreams.cs
  30. 18
      tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs
  31. 5
      tests/ImageSharp.Benchmarks/General/StructCasting.cs
  32. 24
      tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs
  33. 18
      tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs
  34. 44
      tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs
  35. 20
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  36. 2
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs
  37. 8
      tests/ImageSharp.Benchmarks/Processing/Crop.cs
  38. 34
      tests/ImageSharp.Benchmarks/Processing/Resize.cs
  39. 12
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  40. 2
      tests/ImageSharp.Tests/TestImages.cs
  41. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_A.gif/00.png
  42. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png
  43. 3
      tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png
  44. 3
      tests/Images/Input/Gif/issues/issue_2859_A.gif
  45. 3
      tests/Images/Input/Gif/issues/issue_2859_B.gif

325
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -3,7 +3,6 @@
using System.Buffers; using System.Buffers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -37,22 +36,22 @@ internal sealed class LzwDecoder : IDisposable
/// <summary> /// <summary>
/// The prefix buffer. /// The prefix buffer.
/// </summary> /// </summary>
private readonly IMemoryOwner<int> prefix; private readonly IMemoryOwner<int> prefixOwner;
/// <summary> /// <summary>
/// The suffix buffer. /// The suffix buffer.
/// </summary> /// </summary>
private readonly IMemoryOwner<int> suffix; private readonly IMemoryOwner<int> suffixOwner;
/// <summary> /// <summary>
/// The scratch buffer for reading data blocks. /// The scratch buffer for reading data blocks.
/// </summary> /// </summary>
private readonly IMemoryOwner<byte> scratchBuffer; private readonly IMemoryOwner<byte> bufferOwner;
/// <summary> /// <summary>
/// The pixel stack buffer. /// The pixel stack buffer.
/// </summary> /// </summary>
private readonly IMemoryOwner<int> pixelStack; private readonly IMemoryOwner<int> pixelStackOwner;
private readonly int minCodeSize; private readonly int minCodeSize;
private readonly int clearCode; private readonly int clearCode;
private readonly int endCode; private readonly int endCode;
@ -79,11 +78,12 @@ internal sealed class LzwDecoder : IDisposable
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream, int minCodeSize) public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream, int minCodeSize)
{ {
this.stream = stream ?? throw new ArgumentNullException(nameof(stream)); this.stream = stream ?? throw new ArgumentNullException(nameof(stream));
Guard.IsTrue(IsValidMinCodeSize(minCodeSize), nameof(minCodeSize), "Invalid minimum code size.");
this.prefix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean); this.prefixOwner = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.suffix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean); this.suffixOwner = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean); this.pixelStackOwner = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
this.scratchBuffer = memoryAllocator.Allocate<byte>(byte.MaxValue, AllocationOptions.None); this.bufferOwner = memoryAllocator.Allocate<byte>(byte.MaxValue, AllocationOptions.None);
this.minCodeSize = minCodeSize; this.minCodeSize = minCodeSize;
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize // Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
@ -93,11 +93,15 @@ internal sealed class LzwDecoder : IDisposable
this.endCode = this.clearCode + 1; this.endCode = this.clearCode + 1;
this.availableCode = this.clearCode + 2; this.availableCode = this.clearCode + 2;
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); // Fill the suffix buffer with the initial values represented by the number of colors.
for (this.code = 0; this.code < this.clearCode; this.code++) Span<int> suffix = this.suffixOwner.GetSpan()[..this.clearCode];
int i;
for (i = 0; i < suffix.Length; i++)
{ {
Unsafe.Add(ref suffixRef, (uint)this.code) = (byte)this.code; suffix[i] = i;
} }
this.code = i;
} }
/// <summary> /// <summary>
@ -112,8 +116,7 @@ internal sealed class LzwDecoder : IDisposable
// It is possible to specify a larger LZW minimum code size than the palette length in bits // It is possible to specify a larger LZW minimum code size than the palette length in bits
// which may leave a gap in the codes where no colors are assigned. // which may leave a gap in the codes where no colors are assigned.
// http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression // http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression
int clearCode = 1 << minCodeSize; if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || 1 << minCodeSize > MaxStackSize)
if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || clearCode > MaxStackSize)
{ {
// Don't attempt to decode the frame indices. // Don't attempt to decode the frame indices.
// Theoretically we could determine a min code size from the length of the provided // Theoretically we could determine a min code size from the length of the provided
@ -132,112 +135,139 @@ internal sealed class LzwDecoder : IDisposable
{ {
indices.Clear(); indices.Clear();
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(indices); // Get span values from the owners.
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan()); Span<int> prefix = this.prefixOwner.GetSpan();
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); Span<int> suffix = this.suffixOwner.GetSpan();
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan()); Span<int> pixelStack = this.pixelStackOwner.GetSpan();
Span<byte> buffer = this.scratchBuffer.GetSpan(); Span<byte> buffer = this.bufferOwner.GetSpan();
int x = 0; // Cache frequently accessed instance fields into locals.
int xyz = 0; // This helps avoid repeated field loads inside the tight loop.
while (xyz < indices.Length) BufferedReadStream stream = this.stream;
int top = this.top;
int bits = this.bits;
int codeSize = this.codeSize;
int codeMask = this.codeMask;
int minCodeSize = this.minCodeSize;
int availableCode = this.availableCode;
int oldCode = this.oldCode;
int first = this.first;
int data = this.data;
int count = this.count;
int bufferIndex = this.bufferIndex;
int code = this.code;
int clearCode = this.clearCode;
int endCode = this.endCode;
int i = 0;
while (i < indices.Length)
{ {
if (this.top == 0) if (top == 0)
{ {
if (this.bits < this.codeSize) if (bits < codeSize)
{ {
// Load bytes until there are enough bits for a code. // Load bytes until there are enough bits for a code.
if (this.count == 0) if (count == 0)
{ {
// Read a new data block. // Read a new data block.
this.count = this.ReadBlock(buffer); count = ReadBlock(stream, buffer);
if (this.count == 0) if (count == 0)
{ {
break; break;
} }
this.bufferIndex = 0; bufferIndex = 0;
} }
this.data += buffer[this.bufferIndex] << this.bits; data += buffer[bufferIndex] << bits;
bits += 8;
this.bits += 8; bufferIndex++;
this.bufferIndex++; count--;
this.count--;
continue; continue;
} }
// Get the next code // Get the next code
this.code = this.data & this.codeMask; code = data & codeMask;
this.data >>= this.codeSize; data >>= codeSize;
this.bits -= this.codeSize; bits -= codeSize;
// Interpret the code // Interpret the code
if (this.code > this.availableCode || this.code == this.endCode) if (code > availableCode || code == endCode)
{ {
break; break;
} }
if (this.code == this.clearCode) if (code == clearCode)
{ {
// Reset the decoder // Reset the decoder
this.codeSize = this.minCodeSize + 1; codeSize = minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1; codeMask = (1 << codeSize) - 1;
this.availableCode = this.clearCode + 2; availableCode = clearCode + 2;
this.oldCode = NullCode; oldCode = NullCode;
continue; continue;
} }
if (this.oldCode == NullCode) if (oldCode == NullCode)
{ {
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code); pixelStack[top++] = suffix[code];
this.oldCode = this.code; oldCode = code;
this.first = this.code; first = code;
continue; continue;
} }
int inCode = this.code; int inCode = code;
if (this.code == this.availableCode) if (code == availableCode)
{ {
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = (byte)this.first; pixelStack[top++] = first;
code = oldCode;
this.code = this.oldCode;
} }
while (this.code > this.clearCode) while (code > clearCode && top < MaxStackSize)
{ {
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code); pixelStack[top++] = suffix[code];
this.code = Unsafe.Add(ref prefixRef, (uint)this.code); code = prefix[code];
} }
int suffixCode = Unsafe.Add(ref suffixRef, (uint)this.code); int suffixCode = suffix[code];
this.first = suffixCode; first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = suffixCode; pixelStack[top++] = suffixCode;
// Fix for Gifs that have "deferred clear code" as per here : // Fix for GIFs that have "deferred clear code" as per:
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918 // https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (this.availableCode < MaxStackSize) if (availableCode < MaxStackSize)
{ {
Unsafe.Add(ref prefixRef, (uint)this.availableCode) = this.oldCode; prefix[availableCode] = oldCode;
Unsafe.Add(ref suffixRef, (uint)this.availableCode) = this.first; suffix[availableCode] = first;
this.availableCode++; availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize) if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{ {
this.codeSize++; codeSize++;
this.codeMask = (1 << this.codeSize) - 1; codeMask = (1 << codeSize) - 1;
} }
} }
this.oldCode = inCode; oldCode = inCode;
} }
// Pop a pixel off the pixel stack. // Pop a pixel off the pixel stack.
this.top--; top--;
// Clear missing pixels // Clear missing pixels.
xyz++; indices[i++] = (byte)pixelStack[top];
Unsafe.Add(ref pixelsRowRef, (uint)x++) = (byte)Unsafe.Add(ref pixelStackRef, (uint)this.top);
} }
// Write back the local values to the instance fields.
this.top = top;
this.bits = bits;
this.codeSize = codeSize;
this.codeMask = codeMask;
this.availableCode = availableCode;
this.oldCode = oldCode;
this.first = first;
this.data = data;
this.count = count;
this.bufferIndex = bufferIndex;
this.code = code;
} }
/// <summary> /// <summary>
@ -246,130 +276,161 @@ internal sealed class LzwDecoder : IDisposable
/// <param name="length">The resulting index table length.</param> /// <param name="length">The resulting index table length.</param>
public void SkipIndices(int length) public void SkipIndices(int length)
{ {
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan()); // Get span values from the owners.
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan()); Span<int> prefix = this.prefixOwner.GetSpan();
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan()); Span<int> suffix = this.suffixOwner.GetSpan();
Span<byte> buffer = this.scratchBuffer.GetSpan(); Span<int> pixelStack = this.pixelStackOwner.GetSpan();
Span<byte> buffer = this.bufferOwner.GetSpan();
int xyz = 0;
while (xyz < length) // Cache frequently accessed instance fields into locals.
// This helps avoid repeated field loads inside the tight loop.
BufferedReadStream stream = this.stream;
int top = this.top;
int bits = this.bits;
int codeSize = this.codeSize;
int codeMask = this.codeMask;
int minCodeSize = this.minCodeSize;
int availableCode = this.availableCode;
int oldCode = this.oldCode;
int first = this.first;
int data = this.data;
int count = this.count;
int bufferIndex = this.bufferIndex;
int code = this.code;
int clearCode = this.clearCode;
int endCode = this.endCode;
int i = 0;
while (i < length)
{ {
if (this.top == 0) if (top == 0)
{ {
if (this.bits < this.codeSize) if (bits < codeSize)
{ {
// Load bytes until there are enough bits for a code. // Load bytes until there are enough bits for a code.
if (this.count == 0) if (count == 0)
{ {
// Read a new data block. // Read a new data block.
this.count = this.ReadBlock(buffer); count = ReadBlock(stream, buffer);
if (this.count == 0) if (count == 0)
{ {
break; break;
} }
this.bufferIndex = 0; bufferIndex = 0;
} }
this.data += buffer[this.bufferIndex] << this.bits; data += buffer[bufferIndex] << bits;
bits += 8;
this.bits += 8; bufferIndex++;
this.bufferIndex++; count--;
this.count--;
continue; continue;
} }
// Get the next code // Get the next code
this.code = this.data & this.codeMask; code = data & codeMask;
this.data >>= this.codeSize; data >>= codeSize;
this.bits -= this.codeSize; bits -= codeSize;
// Interpret the code // Interpret the code
if (this.code > this.availableCode || this.code == this.endCode) if (code > availableCode || code == endCode)
{ {
break; break;
} }
if (this.code == this.clearCode) if (code == clearCode)
{ {
// Reset the decoder // Reset the decoder
this.codeSize = this.minCodeSize + 1; codeSize = minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1; codeMask = (1 << codeSize) - 1;
this.availableCode = this.clearCode + 2; availableCode = clearCode + 2;
this.oldCode = NullCode; oldCode = NullCode;
continue; continue;
} }
if (this.oldCode == NullCode) if (oldCode == NullCode)
{ {
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code); pixelStack[top++] = suffix[code];
this.oldCode = this.code; oldCode = code;
this.first = this.code; first = code;
continue; continue;
} }
int inCode = this.code; int inCode = code;
if (this.code == this.availableCode) if (code == availableCode)
{ {
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = (byte)this.first; pixelStack[top++] = first;
code = oldCode;
this.code = this.oldCode;
} }
while (this.code > this.clearCode) while (code > clearCode && top < MaxStackSize)
{ {
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code); pixelStack[top++] = suffix[code];
this.code = Unsafe.Add(ref prefixRef, (uint)this.code); code = prefix[code];
} }
int suffixCode = Unsafe.Add(ref suffixRef, (uint)this.code); int suffixCode = suffix[code];
this.first = suffixCode; first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = suffixCode; pixelStack[top++] = suffixCode;
// Fix for Gifs that have "deferred clear code" as per here : // Fix for GIFs that have "deferred clear code" as per:
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918 // https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (this.availableCode < MaxStackSize) if (availableCode < MaxStackSize)
{ {
Unsafe.Add(ref prefixRef, (uint)this.availableCode) = this.oldCode; prefix[availableCode] = oldCode;
Unsafe.Add(ref suffixRef, (uint)this.availableCode) = this.first; suffix[availableCode] = first;
this.availableCode++; availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize) if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{ {
this.codeSize++; codeSize++;
this.codeMask = (1 << this.codeSize) - 1; codeMask = (1 << codeSize) - 1;
} }
} }
this.oldCode = inCode; oldCode = inCode;
} }
// Pop a pixel off the pixel stack. // Pop a pixel off the pixel stack.
this.top--; top--;
// Clear missing pixels // Skip missing pixels.
xyz++; i++;
} }
// Write back the local values to the instance fields.
this.top = top;
this.bits = bits;
this.codeSize = codeSize;
this.codeMask = codeMask;
this.availableCode = availableCode;
this.oldCode = oldCode;
this.first = first;
this.data = data;
this.count = count;
this.bufferIndex = bufferIndex;
this.code = code;
} }
/// <summary> /// <summary>
/// Reads the next data block from the stream. A data block begins with a byte, /// Reads the next data block from the stream. A data block begins with a byte,
/// which defines the size of the block, followed by the block itself. /// which defines the size of the block, followed by the block itself.
/// </summary> /// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to store the block in.</param> /// <param name="buffer">The buffer to store the block in.</param>
/// <returns> /// <returns>
/// The <see cref="int"/>. /// The <see cref="int"/>.
/// </returns> /// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadBlock(Span<byte> buffer) private static int ReadBlock(BufferedReadStream stream, Span<byte> buffer)
{ {
int bufferSize = this.stream.ReadByte(); int bufferSize = stream.ReadByte();
if (bufferSize < 1) if (bufferSize < 1)
{ {
return 0; return 0;
} }
int count = this.stream.Read(buffer, 0, bufferSize); int count = stream.Read(buffer, 0, bufferSize);
return count != bufferSize ? 0 : bufferSize; return count != bufferSize ? 0 : bufferSize;
} }
@ -377,9 +438,9 @@ internal sealed class LzwDecoder : IDisposable
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {
this.prefix.Dispose(); this.prefixOwner.Dispose();
this.suffix.Dispose(); this.suffixOwner.Dispose();
this.pixelStack.Dispose(); this.pixelStackOwner.Dispose();
this.scratchBuffer.Dispose(); this.bufferOwner.Dispose();
} }
} }

2
src/ImageSharp/Formats/Png/PngThrowHelper.cs

@ -44,7 +44,7 @@ internal static class PngThrowHelper
=> throw new NotSupportedException($"Invalid {name}. {message}. Was '{value}'."); => throw new NotSupportedException($"Invalid {name}. {message}. Was '{value}'.");
[DoesNotReturn] [DoesNotReturn]
public static void ThrowInvalidParameter(object value1, object value2, string message, [CallerArgumentExpression(nameof(value1))] string name1 = "", [CallerArgumentExpression(nameof(value1))] string name2 = "") public static void ThrowInvalidParameter(object value1, object value2, string message, [CallerArgumentExpression(nameof(value1))] string name1 = "", [CallerArgumentExpression(nameof(value2))] string name2 = "")
=> throw new NotSupportedException($"Invalid {name1} or {name2}. {message}. Was '{value1}' and '{value2}'."); => throw new NotSupportedException($"Invalid {name1} or {name2}. {message}. Was '{value1}' and '{value2}'.");
[DoesNotReturn] [DoesNotReturn]

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

@ -648,7 +648,7 @@ internal class TiffDecoderCore : ImageDecoderCore
} }
/// <summary> /// <summary>
/// Decodes the image data for Tiff's which arrange the pixel data in tiles and the chunky configuration. /// Decodes the image data for TIFFs which arrange the pixel data in tiles and the chunky configuration.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The image frame to decode into.</param> /// <param name="frame">The image frame to decode into.</param>
@ -674,14 +674,10 @@ internal class TiffDecoderCore : ImageDecoderCore
int width = pixels.Width; int width = pixels.Width;
int height = pixels.Height; int height = pixels.Height;
int bitsPerPixel = this.BitsPerPixel; int bitsPerPixel = this.BitsPerPixel;
int bytesPerRow = RoundUpToMultipleOfEight(width * bitsPerPixel);
int bytesPerTileRow = RoundUpToMultipleOfEight(tileWidth * bitsPerPixel); int bytesPerTileRow = RoundUpToMultipleOfEight(tileWidth * bitsPerPixel);
int uncompressedTilesSize = bytesPerTileRow * tileLength;
using IMemoryOwner<byte> tileBuffer = this.memoryAllocator.Allocate<byte>(uncompressedTilesSize, AllocationOptions.Clean); using IMemoryOwner<byte> tileBuffer = this.memoryAllocator.Allocate<byte>(bytesPerTileRow * tileLength, AllocationOptions.Clean);
using IMemoryOwner<byte> uncompressedPixelBuffer = this.memoryAllocator.Allocate<byte>(tilesDown * tileLength * bytesPerRow, AllocationOptions.Clean);
Span<byte> tileBufferSpan = tileBuffer.GetSpan(); Span<byte> tileBufferSpan = tileBuffer.GetSpan();
Span<byte> uncompressedPixelBufferSpan = uncompressedPixelBuffer.GetSpan();
using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel); using TiffBaseDecompressor decompressor = this.CreateDecompressor<TPixel>(frame.Width, bitsPerPixel);
TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>(); TiffBaseColorDecoder<TPixel> colorDecoder = this.CreateChunkyColorDecoder<TPixel>();
@ -689,13 +685,15 @@ internal class TiffDecoderCore : ImageDecoderCore
int tileIndex = 0; int tileIndex = 0;
for (int tileY = 0; tileY < tilesDown; tileY++) for (int tileY = 0; tileY < tilesDown; tileY++)
{ {
int remainingPixelsInRow = width; int rowStartY = tileY * tileLength;
int rowEndY = Math.Min(rowStartY + tileLength, height);
for (int tileX = 0; tileX < tilesAcross; tileX++) for (int tileX = 0; tileX < tilesAcross; tileX++)
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
int uncompressedPixelBufferOffset = tileY * tileLength * bytesPerRow;
bool isLastHorizontalTile = tileX == tilesAcross - 1; bool isLastHorizontalTile = tileX == tilesAcross - 1;
int remainingPixelsInRow = width - (tileX * tileWidth);
decompressor.Decompress( decompressor.Decompress(
this.inputStream, this.inputStream,
@ -706,22 +704,21 @@ internal class TiffDecoderCore : ImageDecoderCore
cancellationToken); cancellationToken);
int tileBufferOffset = 0; int tileBufferOffset = 0;
uncompressedPixelBufferOffset += bytesPerTileRow * tileX;
int bytesToCopy = isLastHorizontalTile ? RoundUpToMultipleOfEight(bitsPerPixel * remainingPixelsInRow) : bytesPerTileRow; int bytesToCopy = isLastHorizontalTile ? RoundUpToMultipleOfEight(bitsPerPixel * remainingPixelsInRow) : bytesPerTileRow;
for (int y = 0; y < tileLength; y++) int rowWidth = Math.Min(tileWidth, remainingPixelsInRow);
int left = tileX * tileWidth;
for (int y = rowStartY; y < rowEndY; y++)
{ {
Span<byte> uncompressedPixelRow = uncompressedPixelBufferSpan.Slice(uncompressedPixelBufferOffset, bytesToCopy); // Decode the tile row directly into the pixel buffer.
tileBufferSpan.Slice(tileBufferOffset, bytesToCopy).CopyTo(uncompressedPixelRow); ReadOnlySpan<byte> tileRowSpan = tileBufferSpan.Slice(tileBufferOffset, bytesToCopy);
colorDecoder.Decode(tileRowSpan, pixels, left, y, rowWidth, 1);
tileBufferOffset += bytesPerTileRow; tileBufferOffset += bytesPerTileRow;
uncompressedPixelBufferOffset += bytesPerRow;
} }
remainingPixelsInRow -= tileWidth;
tileIndex++; tileIndex++;
} }
} }
colorDecoder.Decode(uncompressedPixelBufferSpan, pixels, 0, 0, width, height);
} }
private TiffBaseColorDecoder<TPixel> CreateChunkyColorDecoder<TPixel>() private TiffBaseColorDecoder<TPixel> CreateChunkyColorDecoder<TPixel>()

4
tests/ImageSharp.Benchmarks/Bulk/FromRgba32Bytes.cs

@ -62,9 +62,7 @@ public abstract class FromRgba32Bytes<TPixel>
=> PixelOperations<TPixel>.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count); => PixelOperations<TPixel>.Instance.FromRgba32Bytes(this.configuration, this.source.GetSpan(), this.destination.GetSpan(), this.Count);
} }
public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes<Rgba32> public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes<Rgba32>;
{
}
public class FromRgba32Bytes_ToBgra32 : FromRgba32Bytes<Bgra32> public class FromRgba32Bytes_ToBgra32 : FromRgba32Bytes<Bgra32>
{ {

2
tests/ImageSharp.Benchmarks/Bulk/Vector4Factory.cs

@ -30,5 +30,5 @@ internal static class Vector4Factory
} }
private static float GetRandomFloat(Random rnd, float minVal, float maxVal) private static float GetRandomFloat(Random rnd, float minVal, float maxVal)
=> (float)rnd.NextDouble() * (maxVal - minVal) + minVal; => ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal;
} }

15
tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs

@ -19,12 +19,7 @@ public class DecodeBmp
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages()
{ => this.bmpBytes ??= File.ReadAllBytes(this.TestImageFullPath);
if (this.bmpBytes == null)
{
this.bmpBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
[Params(TestImages.Bmp.Car)] [Params(TestImages.Bmp.Car)]
public string TestImage { get; set; } public string TestImage { get; set; }
@ -32,16 +27,16 @@ public class DecodeBmp
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")] [Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public SDSize BmpSystemDrawing() public SDSize BmpSystemDrawing()
{ {
using var memoryStream = new MemoryStream(this.bmpBytes); using MemoryStream memoryStream = new(this.bmpBytes);
using var image = SDImage.FromStream(memoryStream); using SDImage image = SDImage.FromStream(memoryStream);
return image.Size; return image.Size;
} }
[Benchmark(Description = "ImageSharp Bmp")] [Benchmark(Description = "ImageSharp Bmp")]
public Size BmpImageSharp() public Size BmpImageSharp()
{ {
using var memoryStream = new MemoryStream(this.bmpBytes); using MemoryStream memoryStream = new(this.bmpBytes);
using var image = Image.Load<Rgba32>(memoryStream); using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
return new Size(image.Width, image.Height); return new Size(image.Width, image.Height);
} }
} }

6
tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
[Config(typeof(Config.Short))] [Config(typeof(Config.Short))]
public class EncodeBmp public class EncodeBmp
{ {
private Stream bmpStream; private FileStream bmpStream;
private SDImage bmpDrawing; private SDImage bmpDrawing;
private Image<Rgba32> bmpCore; private Image<Rgba32> bmpCore;
@ -40,14 +40,14 @@ public class EncodeBmp
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")] [Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public void BmpSystemDrawing() public void BmpSystemDrawing()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp); this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp);
} }
[Benchmark(Description = "ImageSharp Bmp")] [Benchmark(Description = "ImageSharp Bmp")]
public void BmpImageSharp() public void BmpImageSharp()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.bmpCore.SaveAsBmp(memoryStream); this.bmpCore.SaveAsBmp(memoryStream);
} }
} }

16
tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs

@ -18,13 +18,7 @@ public class DecodeGif
=> Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage); => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages() => this.gifBytes ??= File.ReadAllBytes(this.TestImageFullPath);
{
if (this.gifBytes == null)
{
this.gifBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
[Params(TestImages.Gif.Cheers)] [Params(TestImages.Gif.Cheers)]
public string TestImage { get; set; } public string TestImage { get; set; }
@ -32,16 +26,16 @@ public class DecodeGif
[Benchmark(Baseline = true, Description = "System.Drawing Gif")] [Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public SDSize GifSystemDrawing() public SDSize GifSystemDrawing()
{ {
using var memoryStream = new MemoryStream(this.gifBytes); using MemoryStream memoryStream = new(this.gifBytes);
using var image = SDImage.FromStream(memoryStream); using SDImage image = SDImage.FromStream(memoryStream);
return image.Size; return image.Size;
} }
[Benchmark(Description = "ImageSharp Gif")] [Benchmark(Description = "ImageSharp Gif")]
public Size GifImageSharp() public Size GifImageSharp()
{ {
using var memoryStream = new MemoryStream(this.gifBytes); using MemoryStream memoryStream = new(this.gifBytes);
using var image = Image.Load<Rgba32>(memoryStream); using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
return new Size(image.Width, image.Height); return new Size(image.Width, image.Height);
} }
} }

8
tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs

@ -16,12 +16,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
public class EncodeGif public class EncodeGif
{ {
// System.Drawing needs this. // System.Drawing needs this.
private Stream bmpStream; private FileStream bmpStream;
private SDImage bmpDrawing; private SDImage bmpDrawing;
private Image<Rgba32> bmpCore; private Image<Rgba32> bmpCore;
// Try to get as close to System.Drawing's output as possible // Try to get as close to System.Drawing's output as possible
private readonly GifEncoder encoder = new GifEncoder private readonly GifEncoder encoder = new()
{ {
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 })
}; };
@ -53,14 +53,14 @@ public class EncodeGif
[Benchmark(Baseline = true, Description = "System.Drawing Gif")] [Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public void GifSystemDrawing() public void GifSystemDrawing()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.bmpDrawing.Save(memoryStream, ImageFormat.Gif); this.bmpDrawing.Save(memoryStream, ImageFormat.Gif);
} }
[Benchmark(Description = "ImageSharp Gif")] [Benchmark(Description = "ImageSharp Gif")]
public void GifImageSharp() public void GifImageSharp()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.bmpCore.SaveAsGif(memoryStream, this.encoder); this.bmpCore.SaveAsGif(memoryStream, this.encoder);
} }
} }

2
tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs

@ -22,7 +22,7 @@ public class EncodeGifMultiple : MultiImageBenchmarkBase.WithImagesPreloaded
=> this.ForEachImageSharpImage((img, ms) => => this.ForEachImageSharpImage((img, ms) =>
{ {
// Try to get as close to System.Drawing's output as possible // Try to get as close to System.Drawing's output as possible
var options = new GifEncoder GifEncoder options = new()
{ {
Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 }) Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = KnownDitherings.Bayer4x4 })
}; };

13
tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_LoadFromInt16.cs

@ -12,8 +12,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg.BlockOperations;
public class Block8x8F_LoadFromInt16 public class Block8x8F_LoadFromInt16
{ {
private Block8x8 source; private Block8x8 source;
private Block8x8F destination;
private Block8x8F dest = default;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
@ -30,16 +29,10 @@ public class Block8x8F_LoadFromInt16
} }
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Scalar() public void Scalar() => this.destination.LoadFromInt16Scalar(ref this.source);
{
this.dest.LoadFromInt16Scalar(ref this.source);
}
[Benchmark] [Benchmark]
public void ExtendedAvx2() public void ExtendedAvx2() => this.destination.LoadFromInt16ExtendedAvx2(ref this.source);
{
this.dest.LoadFromInt16ExtendedAvx2(ref this.source);
}
// RESULT: // RESULT:
// Method | Mean | Error | StdDev | Scaled | // Method | Mean | Error | StdDev | Scaled |

2
tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Quantize.cs

@ -11,7 +11,7 @@ public class Block8x8F_Quantize
{ {
private Block8x8F block = CreateFromScalar(1); private Block8x8F block = CreateFromScalar(1);
private Block8x8F quant = CreateFromScalar(1); private Block8x8F quant = CreateFromScalar(1);
private Block8x8 result = default; private Block8x8 result;
[Benchmark] [Benchmark]
public short Quantize() public short Quantize()

60
tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Round.cs

@ -36,7 +36,7 @@ public unsafe class Block8x8F_Round
if (ptr % 16 != 0) if (ptr % 16 != 0)
{ {
throw new Exception("ptr is unaligned"); throw new InvalidOperationException("ptr is unaligned");
} }
this.alignedPtr = (float*)ptr; this.alignedPtr = (float*)ptr;
@ -67,21 +67,21 @@ public unsafe class Block8x8F_Round
ref Block8x8F b = ref this.block; ref Block8x8F b = ref this.block;
ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V0L); ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V0L);
row0 = SimdUtils.FastRound(row0); row0 = row0.FastRound();
ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V1L); ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V1L);
row1 = SimdUtils.FastRound(row1); row1 = row1.FastRound();
ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V2L); ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V2L);
row2 = SimdUtils.FastRound(row2); row2 = row2.FastRound();
ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L); ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L);
row3 = SimdUtils.FastRound(row3); row3 = row3.FastRound();
ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L); ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L);
row4 = SimdUtils.FastRound(row4); row4 = row4.FastRound();
ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L); ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L);
row5 = SimdUtils.FastRound(row5); row5 = row5.FastRound();
ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L); ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L);
row6 = SimdUtils.FastRound(row6); row6 = row6.FastRound();
ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L); ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L);
row7 = SimdUtils.FastRound(row7); row7 = row7.FastRound();
} }
[Benchmark] [Benchmark]
@ -90,21 +90,21 @@ public unsafe class Block8x8F_Round
ref Block8x8F b = ref Unsafe.AsRef<Block8x8F>(this.alignedPtr); ref Block8x8F b = ref Unsafe.AsRef<Block8x8F>(this.alignedPtr);
ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V0L); ref Vector<float> row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V0L);
row0 = SimdUtils.FastRound(row0); row0 = row0.FastRound();
ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V1L); ref Vector<float> row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V1L);
row1 = SimdUtils.FastRound(row1); row1 = row1.FastRound();
ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V2L); ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V2L);
row2 = SimdUtils.FastRound(row2); row2 = row2.FastRound();
ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L); ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L);
row3 = SimdUtils.FastRound(row3); row3 = row3.FastRound();
ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L); ref Vector<float> row4 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L);
row4 = SimdUtils.FastRound(row4); row4 = row4.FastRound();
ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L); ref Vector<float> row5 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L);
row5 = SimdUtils.FastRound(row5); row5 = row5.FastRound();
ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L); ref Vector<float> row6 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L);
row6 = SimdUtils.FastRound(row6); row6 = row6.FastRound();
ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L); ref Vector<float> row7 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L);
row7 = SimdUtils.FastRound(row7); row7 = row7.FastRound();
} }
[Benchmark] [Benchmark]
@ -117,20 +117,20 @@ public unsafe class Block8x8F_Round
ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V2L); ref Vector<float> row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V2L);
ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L); ref Vector<float> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L);
row0 = SimdUtils.FastRound(row0); row0 = row0.FastRound();
row1 = SimdUtils.FastRound(row1); row1 = row1.FastRound();
row2 = SimdUtils.FastRound(row2); row2 = row2.FastRound();
row3 = SimdUtils.FastRound(row3); row3 = row3.FastRound();
row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L); row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L);
row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L); row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L);
row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L); row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L);
row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L); row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L);
row0 = SimdUtils.FastRound(row0); row0 = row0.FastRound();
row1 = SimdUtils.FastRound(row1); row1 = row1.FastRound();
row2 = SimdUtils.FastRound(row2); row2 = row2.FastRound();
row3 = SimdUtils.FastRound(row3); row3 = row3.FastRound();
} }
[Benchmark] [Benchmark]
@ -174,7 +174,7 @@ public unsafe class Block8x8F_Round
} }
[Benchmark] [Benchmark]
public unsafe void Sse41_V2() public void Sse41_V2()
{ {
ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block); ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block);
p = Sse41.RoundToNearestInteger(p); p = Sse41.RoundToNearestInteger(p);
@ -214,7 +214,7 @@ public unsafe class Block8x8F_Round
} }
[Benchmark] [Benchmark]
public unsafe void Sse41_V3() public void Sse41_V3()
{ {
ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block); ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block);
p = Sse41.RoundToNearestInteger(p); p = Sse41.RoundToNearestInteger(p);
@ -228,7 +228,7 @@ public unsafe class Block8x8F_Round
} }
[Benchmark] [Benchmark]
public unsafe void Sse41_V4() public void Sse41_V4()
{ {
ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block); ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block);
nuint offset = (uint)sizeof(Vector128<float>); nuint offset = (uint)sizeof(Vector128<float>);
@ -271,7 +271,7 @@ public unsafe class Block8x8F_Round
} }
[Benchmark] [Benchmark]
public unsafe void Sse41_V5_Unaligned() public void Sse41_V5_Unaligned()
{ {
float* p = this.alignedPtr + 1; float* p = this.alignedPtr + 1;
@ -356,7 +356,7 @@ public unsafe class Block8x8F_Round
} }
[Benchmark] [Benchmark]
public unsafe void Sse41_V5_Aligned() public void Sse41_V5_Aligned()
{ {
float* p = this.alignedPtr; float* p = this.alignedPtr;

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

@ -27,20 +27,20 @@ public class DecodeJpegParseStreamOnly
[Benchmark(Baseline = true, Description = "System.Drawing FULL")] [Benchmark(Baseline = true, Description = "System.Drawing FULL")]
public SDSize JpegSystemDrawing() public SDSize JpegSystemDrawing()
{ {
using var memoryStream = new MemoryStream(this.jpegBytes); using MemoryStream memoryStream = new(this.jpegBytes);
using var image = System.Drawing.Image.FromStream(memoryStream); using System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream);
return image.Size; return image.Size;
} }
[Benchmark(Description = "JpegDecoderCore.ParseStream")] [Benchmark(Description = "JpegDecoderCore.ParseStream")]
public void ParseStream() public void ParseStream()
{ {
using var memoryStream = new MemoryStream(this.jpegBytes); using MemoryStream memoryStream = new(this.jpegBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream); using BufferedReadStream bufferedStream = new(Configuration.Default, memoryStream);
var options = new JpegDecoderOptions() { GeneralOptions = new() { SkipMetadata = true } }; JpegDecoderOptions options = new() { GeneralOptions = new() { SkipMetadata = true } };
using var decoder = new JpegDecoderCore(options); using JpegDecoderCore decoder = new(options);
var spectralConverter = new NoopSpectralConverter(); NoopSpectralConverter spectralConverter = new();
decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default); decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default);
} }
@ -48,7 +48,7 @@ public class DecodeJpegParseStreamOnly
// Nor we need to allocate final pixel buffer // Nor we need to allocate final pixel buffer
// Note: this still introduces virtual method call overhead for baseline interleaved images // Note: this still introduces virtual method call overhead for baseline interleaved images
// There's no way to eliminate it as spectral conversion is built into the scan decoding loop for memory footprint reduction // There's no way to eliminate it as spectral conversion is built into the scan decoding loop for memory footprint reduction
private class NoopSpectralConverter : SpectralConverter private sealed class NoopSpectralConverter : SpectralConverter
{ {
public override void ConvertStrideBaseline() public override void ConvertStrideBaseline()
{ {

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

@ -17,21 +17,21 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg;
public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase public class DecodeJpeg_Aggregate : MultiImageBenchmarkBase
{ {
protected override IEnumerable<string> InputImageSubfoldersOrFiles protected override IEnumerable<string> InputImageSubfoldersOrFiles
=> new[] =>
{ [
TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome, TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome,
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr, TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr,
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr, TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr, TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr,
TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr, TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr,
}; ];
[Params(InputImageCategory.AllImages)] [Params(InputImageCategory.AllImages)]
public override InputImageCategory InputCategory { get; set; } public override InputImageCategory InputCategory { get; set; }
[Benchmark] [Benchmark]
public void ImageSharp() public void ImageSharp()
=> this.ForEachStream(ms => Image.Load<Rgba32>(ms)); => this.ForEachStream(Image.Load<Rgba32>);
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void SystemDrawing() public void SystemDrawing()

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

@ -35,26 +35,21 @@ public class DecodeJpeg_ImageSpecific
[GlobalSetup] [GlobalSetup]
public void ReadImages() public void ReadImages()
{ => this.jpegBytes ??= File.ReadAllBytes(this.TestImageFullPath);
if (this.jpegBytes == null)
{
this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public SDSize SystemDrawing() public SDSize SystemDrawing()
{ {
using var memoryStream = new MemoryStream(this.jpegBytes); using MemoryStream memoryStream = new(this.jpegBytes);
using var image = SDImage.FromStream(memoryStream); using SDImage image = SDImage.FromStream(memoryStream);
return image.Size; return image.Size;
} }
[Benchmark] [Benchmark]
public Size ImageSharp() public Size ImageSharp()
{ {
using var memoryStream = new MemoryStream(this.jpegBytes); using MemoryStream memoryStream = new(this.jpegBytes);
using var image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream); using Image image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream);
return new Size(image.Width, image.Height); return new Size(image.Width, image.Height);
} }

9
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpegComparison.cs

@ -19,11 +19,6 @@ public class EncodeJpegComparison
{ {
// Big enough, 4:4:4 chroma sampling // Big enough, 4:4:4 chroma sampling
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
// Change/add parameters for extra benchmarks
[Params(75, 90, 100)]
public int Quality;
private MemoryStream destinationStream; private MemoryStream destinationStream;
// ImageSharp // ImageSharp
@ -33,6 +28,10 @@ public class EncodeJpegComparison
// SkiaSharp // SkiaSharp
private SKBitmap imageSkiaSharp; private SKBitmap imageSkiaSharp;
// Change/add parameters for extra benchmarks
[Params(75, 90, 100)]
public int Quality { get; set; }
[GlobalSetup(Target = nameof(BenchmarkImageSharp))] [GlobalSetup(Target = nameof(BenchmarkImageSharp))]
public void SetupImageSharp() public void SetupImageSharp()
{ {

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

@ -20,19 +20,19 @@ public class EncodeJpegFeatures
// No metadata // No metadata
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora; private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
public static IEnumerable<JpegColorType> ColorSpaceValues => new[] public static IEnumerable<JpegColorType> ColorSpaceValues =>
{ [
JpegColorType.Luminance, JpegColorType.Luminance,
JpegColorType.Rgb, JpegColorType.Rgb,
JpegColorType.YCbCrRatio420, JpegColorType.YCbCrRatio420,
JpegColorType.YCbCrRatio444, JpegColorType.YCbCrRatio444,
}; ];
[Params(75, 90, 100)] [Params(75, 90, 100)]
public int Quality; public int Quality { get; set; }
[ParamsSource(nameof(ColorSpaceValues), Priority = -100)] [ParamsSource(nameof(ColorSpaceValues), Priority = -100)]
public JpegColorType TargetColorSpace; public JpegColorType TargetColorSpace { get; set; }
private Image<Rgb24> bmpCore; private Image<Rgb24> bmpCore;
private JpegEncoder encoder; private JpegEncoder encoder;

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

@ -11,11 +11,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
public abstract class MultiImageBenchmarkBase public abstract class MultiImageBenchmarkBase
{ {
protected Dictionary<string, byte[]> FileNamesToBytes { get; set; } = new Dictionary<string, byte[]>(); protected Dictionary<string, byte[]> FileNamesToBytes { get; set; } = [];
protected Dictionary<string, Image<Rgba32>> FileNamesToImageSharpImages { get; set; } = new Dictionary<string, Image<Rgba32>>(); protected Dictionary<string, Image<Rgba32>> FileNamesToImageSharpImages { get; set; } = [];
protected Dictionary<string, Bitmap> FileNamesToSystemDrawingImages { get; set; } = new Dictionary<string, Bitmap>(); protected Dictionary<string, Bitmap> FileNamesToSystemDrawingImages { get; set; } = [];
/// <summary> /// <summary>
/// The values of this enum separate input files into categories. /// The values of this enum separate input files into categories.
@ -43,12 +43,12 @@ public abstract class MultiImageBenchmarkBase
protected virtual string BaseFolder => TestEnvironment.InputImagesDirectoryFullPath; protected virtual string BaseFolder => TestEnvironment.InputImagesDirectoryFullPath;
protected virtual IEnumerable<string> SearchPatterns => new[] { "*.*" }; protected virtual IEnumerable<string> SearchPatterns => ["*.*"];
/// <summary> /// <summary>
/// Gets the file names containing these strings are substrings are not processed by the benchmark. /// Gets the file names containing these strings are substrings are not processed by the benchmark.
/// </summary> /// </summary>
protected virtual IEnumerable<string> ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" }; protected virtual IEnumerable<string> ExcludeSubstringsInFileNames => ["badeof", "BadEof", "CriticalEOF"];
/// <summary> /// <summary>
/// Gets folders containing files OR files to be processed by the benchmark. /// Gets folders containing files OR files to be processed by the benchmark.
@ -70,7 +70,7 @@ public abstract class MultiImageBenchmarkBase
InputImageCategory.AllImages => input, InputImageCategory.AllImages => input,
InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)), InputImageCategory.SmallImagesOnly => input.Where(kv => checkIfSmall(kv.Value)),
InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)), InputImageCategory.LargeImagesOnly => input.Where(kv => !checkIfSmall(kv.Value)),
_ => throw new ArgumentOutOfRangeException(), _ => throw new ArgumentOutOfRangeException(nameof(input), "Invalid input category")
}; };
protected IEnumerable<KeyValuePair<string, byte[]>> FileNames2Bytes protected IEnumerable<KeyValuePair<string, byte[]>> FileNames2Bytes
@ -86,7 +86,7 @@ public abstract class MultiImageBenchmarkBase
{ {
if (!Vector.IsHardwareAccelerated) if (!Vector.IsHardwareAccelerated)
{ {
throw new Exception("Vector.IsHardwareAccelerated == false! Check your build settings!"); throw new InvalidOperationException("Vector.IsHardwareAccelerated == false! Check your build settings!");
} }
// Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated); // Console.WriteLine("Vector.IsHardwareAccelerated: " + Vector.IsHardwareAccelerated);
@ -103,13 +103,13 @@ public abstract class MultiImageBenchmarkBase
continue; continue;
} }
string[] excludeStrings = this.ExcludeSubstringsInFileNames.Select(s => s.ToLower()).ToArray(); string[] excludeStrings = this.ExcludeSubstringsInFileNames.ToArray();
string[] allFiles = string[] allFiles =
this.SearchPatterns.SelectMany( this.SearchPatterns.SelectMany(
f => f =>
Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) Directory.EnumerateFiles(path, f, SearchOption.AllDirectories)
.Where(fn => !excludeStrings.Any(excludeStr => fn.ToLower().Contains(excludeStr)))).ToArray(); .Where(fn => !excludeStrings.Any(excludeStr => fn.Contains(excludeStr, StringComparison.OrdinalIgnoreCase)))).ToArray();
foreach (string fn in allFiles) foreach (string fn in allFiles)
{ {
@ -126,7 +126,7 @@ public abstract class MultiImageBenchmarkBase
{ {
foreach (KeyValuePair<string, byte[]> kv in this.FileNames2Bytes) foreach (KeyValuePair<string, byte[]> kv in this.FileNames2Bytes)
{ {
using var memoryStream = new MemoryStream(kv.Value); using MemoryStream memoryStream = new(kv.Value);
try try
{ {
object obj = operation(memoryStream); object obj = operation(memoryStream);
@ -150,7 +150,7 @@ public abstract class MultiImageBenchmarkBase
byte[] bytes = kv.Value; byte[] bytes = kv.Value;
string fn = kv.Key; string fn = kv.Key;
using (var ms1 = new MemoryStream(bytes)) using (MemoryStream ms1 = new(bytes))
{ {
this.FileNamesToImageSharpImages[fn] = Image.Load<Rgba32>(ms1); this.FileNamesToImageSharpImages[fn] = Image.Load<Rgba32>(ms1);
} }
@ -191,7 +191,7 @@ public abstract class MultiImageBenchmarkBase
protected void ForEachImageSharpImage(Func<Image<Rgba32>, MemoryStream, object> operation) protected void ForEachImageSharpImage(Func<Image<Rgba32>, MemoryStream, object> operation)
{ {
using var workStream = new MemoryStream(); using MemoryStream workStream = new();
this.ForEachImageSharpImage( this.ForEachImageSharpImage(
img => img =>
{ {
@ -222,7 +222,7 @@ public abstract class MultiImageBenchmarkBase
protected void ForEachSystemDrawingImage(Func<Bitmap, MemoryStream, object> operation) protected void ForEachSystemDrawingImage(Func<Bitmap, MemoryStream, object> operation)
{ {
using var workStream = new MemoryStream(); using MemoryStream workStream = new();
this.ForEachSystemDrawingImage( this.ForEachSystemDrawingImage(
img => img =>
{ {

26
tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
public class EncodeIndexedPng public class EncodeIndexedPng
{ {
// System.Drawing needs this. // System.Drawing needs this.
private Stream bmpStream; private FileStream bmpStream;
private Image<Rgba32> bmpCore; private Image<Rgba32> bmpCore;
[GlobalSetup] [GlobalSetup]
@ -43,48 +43,48 @@ public class EncodeIndexedPng
[Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")]
public void PngCoreOctree() public void PngCoreOctree()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
var options = new PngEncoder { Quantizer = KnownQuantizers.Octree }; PngEncoder options = new() { Quantizer = KnownQuantizers.Octree };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
[Benchmark(Description = "ImageSharp Octree NoDither Png")] [Benchmark(Description = "ImageSharp Octree NoDither Png")]
public void PngCoreOctreeNoDither() public void PngCoreOctreeNoDither()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
var options = new PngEncoder { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) }; PngEncoder options = new() { Quantizer = new OctreeQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
[Benchmark(Description = "ImageSharp Palette Png")] [Benchmark(Description = "ImageSharp Palette Png")]
public void PngCorePalette() public void PngCorePalette()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
var options = new PngEncoder { Quantizer = KnownQuantizers.WebSafe }; PngEncoder options = new() { Quantizer = KnownQuantizers.WebSafe };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
[Benchmark(Description = "ImageSharp Palette NoDither Png")] [Benchmark(Description = "ImageSharp Palette NoDither Png")]
public void PngCorePaletteNoDither() public void PngCorePaletteNoDither()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
var options = new PngEncoder { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) }; PngEncoder options = new() { Quantizer = new WebSafePaletteQuantizer(new QuantizerOptions { Dither = null }) };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
[Benchmark(Description = "ImageSharp Wu Png")] [Benchmark(Description = "ImageSharp Wu Png")]
public void PngCoreWu() public void PngCoreWu()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
var options = new PngEncoder { Quantizer = KnownQuantizers.Wu }; PngEncoder options = new() { Quantizer = KnownQuantizers.Wu };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
[Benchmark(Description = "ImageSharp Wu NoDither Png")] [Benchmark(Description = "ImageSharp Wu NoDither Png")]
public void PngCoreWuNoDither() public void PngCoreWuNoDither()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette }; PngEncoder options = new() { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette };
this.bmpCore.SaveAsPng(memoryStream, options); this.bmpCore.SaveAsPng(memoryStream, options);
} }
} }

8
tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
public class EncodePng public class EncodePng
{ {
// System.Drawing needs this. // System.Drawing needs this.
private Stream bmpStream; private FileStream bmpStream;
private SDImage bmpDrawing; private SDImage bmpDrawing;
private Image<Rgba32> bmpCore; private Image<Rgba32> bmpCore;
@ -46,15 +46,15 @@ public class EncodePng
[Benchmark(Baseline = true, Description = "System.Drawing Png")] [Benchmark(Baseline = true, Description = "System.Drawing Png")]
public void PngSystemDrawing() public void PngSystemDrawing()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.bmpDrawing.Save(memoryStream, ImageFormat.Png); this.bmpDrawing.Save(memoryStream, ImageFormat.Png);
} }
[Benchmark(Description = "ImageSharp Png")] [Benchmark(Description = "ImageSharp Png")]
public void PngCore() public void PngCore()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None }; PngEncoder encoder = new() { FilterMethod = PngFilterMethod.None };
this.bmpCore.SaveAsPng(memoryStream, encoder); this.bmpCore.SaveAsPng(memoryStream, encoder);
} }
} }

4
tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs

@ -31,7 +31,7 @@ public class DecodeTga
{ {
MagickReadSettings settings = new() { Format = MagickFormat.Tga }; MagickReadSettings settings = new() { Format = MagickFormat.Tga };
using MagickImage image = new(new MemoryStream(this.data), settings); using MagickImage image = new(new MemoryStream(this.data), settings);
return (int)image.Width; return image.Width;
} }
[Benchmark(Description = "ImageSharp Tga")] [Benchmark(Description = "ImageSharp Tga")]
@ -48,7 +48,7 @@ public class DecodeTga
return image.Width; return image.Width;
} }
private class PfimAllocator : IImageAllocator private sealed class PfimAllocator : IImageAllocator
{ {
private int rented; private int rented;
private readonly ArrayPool<byte> shared = ArrayPool<byte>.Shared; private readonly ArrayPool<byte> shared = ArrayPool<byte>.Shared;

4
tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs

@ -41,14 +41,14 @@ public class EncodeTga
[Benchmark(Baseline = true, Description = "Magick Tga")] [Benchmark(Baseline = true, Description = "Magick Tga")]
public void MagickTga() public void MagickTga()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.tgaMagick.Write(memoryStream, MagickFormat.Tga); this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
} }
[Benchmark(Description = "ImageSharp Tga")] [Benchmark(Description = "ImageSharp Tga")]
public void ImageSharpTga() public void ImageSharpTga()
{ {
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.tga.SaveAsTga(memoryStream); this.tga.SaveAsTga(memoryStream);
} }
} }

44
tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
[Config(typeof(Config.Short))] [Config(typeof(Config.Short))]
public class EncodeTiff public class EncodeTiff
{ {
private Stream stream; private FileStream stream;
private SDImage drawing; private SDImage drawing;
private Image<Rgba32> core; private Image<Rgba32> core;
@ -60,12 +60,12 @@ public class EncodeTiff
public void SystemDrawing() public void SystemDrawing()
{ {
ImageCodecInfo codec = FindCodecForType("image/tiff"); ImageCodecInfo codec = FindCodecForType("image/tiff");
using var parameters = new EncoderParameters(1) using EncoderParameters parameters = new(1)
{ {
Param = { [0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)) } Param = { [0] = new EncoderParameter(Encoder.Compression, (long)Cast(this.Compression)) }
}; };
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.drawing.Save(memoryStream, codec, parameters); this.drawing.Save(memoryStream, codec, parameters);
} }
@ -77,8 +77,8 @@ public class EncodeTiff
TiffPhotometricInterpretation.WhiteIsZero : TiffPhotometricInterpretation.WhiteIsZero :
TiffPhotometricInterpretation.Rgb; TiffPhotometricInterpretation.Rgb;
var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation }; TiffEncoder encoder = new() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation };
using var memoryStream = new MemoryStream(); using MemoryStream memoryStream = new();
this.core.SaveAsTiff(memoryStream, encoder); this.core.SaveAsTiff(memoryStream, encoder);
} }
@ -98,33 +98,15 @@ public class EncodeTiff
} }
private static EncoderValue Cast(TiffCompression compression) private static EncoderValue Cast(TiffCompression compression)
{ => compression switch
switch (compression)
{ {
case TiffCompression.None: TiffCompression.None => EncoderValue.CompressionNone,
return EncoderValue.CompressionNone; TiffCompression.CcittGroup3Fax => EncoderValue.CompressionCCITT3,
TiffCompression.Ccitt1D => EncoderValue.CompressionRle,
case TiffCompression.CcittGroup3Fax: TiffCompression.Lzw => EncoderValue.CompressionLZW,
return EncoderValue.CompressionCCITT3; _ => throw new NotSupportedException(compression.ToString()),
};
case TiffCompression.Ccitt1D:
return EncoderValue.CompressionRle;
case TiffCompression.Lzw:
return EncoderValue.CompressionLZW;
default:
throw new NotSupportedException(compression.ToString());
}
}
public static bool IsOneBitCompression(TiffCompression compression) public static bool IsOneBitCompression(TiffCompression compression)
{ => compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax;
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
} }

4
tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs

@ -47,7 +47,7 @@ public class DecodeWebp
MagickReadSettings settings = new() { Format = MagickFormat.WebP }; MagickReadSettings settings = new() { Format = MagickFormat.WebP };
using MemoryStream memoryStream = new(this.webpLossyBytes); using MemoryStream memoryStream = new(this.webpLossyBytes);
using MagickImage image = new(memoryStream, settings); using MagickImage image = new(memoryStream, settings);
return (int)image.Width; return image.Width;
} }
[Benchmark(Description = "ImageSharp Lossy Webp")] [Benchmark(Description = "ImageSharp Lossy Webp")]
@ -65,7 +65,7 @@ public class DecodeWebp
{ Format = MagickFormat.WebP }; { Format = MagickFormat.WebP };
using MemoryStream memoryStream = new(this.webpLossyBytes); using MemoryStream memoryStream = new(this.webpLossyBytes);
using MagickImage image = new(memoryStream, settings); using MagickImage image = new(memoryStream, settings);
return (int)image.Width; return image.Width;
} }
[Benchmark(Description = "ImageSharp Lossless Webp")] [Benchmark(Description = "ImageSharp Lossless Webp")]

2
tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs

@ -14,9 +14,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
[MarkdownExporter] [MarkdownExporter]
[HtmlExporter] [HtmlExporter]
[Config(typeof(Config.Short))] [Config(typeof(Config.Short))]
#pragma warning disable CA1001 // Types that own disposable fields should be disposable
public class EncodeWebp public class EncodeWebp
#pragma warning restore CA1001 // Types that own disposable fields should be disposable
{ {
private MagickImage webpMagick; private MagickImage webpMagick;
private Image<Rgba32> webp; private Image<Rgba32> webp;

6
tests/ImageSharp.Benchmarks/Config.cs

@ -32,7 +32,7 @@ public partial class Config : ManualConfig
public class Standard : Config public class Standard : Config
{ {
public Standard() => this.AddJob( public Standard() => this.AddJob(
Job.Default.WithRuntime(CoreRuntime.Core80).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); Job.Default.WithRuntime(CoreRuntime.Core80).WithArguments([new MsBuildArgument("/p:DebugType=portable")]));
} }
public class Short : Config public class Short : Config
@ -42,12 +42,10 @@ public partial class Config : ManualConfig
.WithLaunchCount(1) .WithLaunchCount(1)
.WithWarmupCount(3) .WithWarmupCount(3)
.WithIterationCount(3) .WithIterationCount(3)
.WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") })); .WithArguments([new MsBuildArgument("/p:DebugType=portable")]));
} }
#if OS_WINDOWS #if OS_WINDOWS
#pragma warning disable CA1416 // Validate platform compatibility
private bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); private bool IsElevated => new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
#pragma warning restore CA1416 // Validate platform compatibility
#endif #endif
} }

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
#if OS_WINDOWS
using System.Drawing; using System.Drawing;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -25,3 +26,4 @@ public class GetSetPixel
return image[200, 200]; return image[200, 200];
} }
} }
#endif

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

@ -78,7 +78,7 @@ public class BufferedStreams
public int StandardStreamRead() public int StandardStreamRead()
{ {
int r = 0; int r = 0;
Stream stream = this.stream1; MemoryStream stream = this.stream1;
byte[] b = this.chunk1; byte[] b = this.chunk1;
for (int i = 0; i < stream.Length / 2; i++) for (int i = 0; i < stream.Length / 2; i++)
@ -138,7 +138,7 @@ public class BufferedStreams
public int StandardStreamReadByte() public int StandardStreamReadByte()
{ {
int r = 0; int r = 0;
Stream stream = this.stream2; MemoryStream stream = this.stream2;
for (int i = 0; i < stream.Length; i++) for (int i = 0; i < stream.Length; i++)
{ {
@ -205,8 +205,8 @@ public class BufferedStreams
private static byte[] CreateTestBytes() private static byte[] CreateTestBytes()
{ {
var buffer = new byte[Configuration.Default.StreamProcessingBufferSize * 3]; byte[] buffer = new byte[Configuration.Default.StreamProcessingBufferSize * 3];
var random = new Random(); Random random = new();
random.NextBytes(buffer); random.NextBytes(buffer);
return buffer; return buffer;

18
tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_ConvertFromRgba32.cs

@ -184,14 +184,16 @@ public class PixelConversion_ConvertFromRgba32_Permuted_RgbaToArgb : PixelConver
} }
} }
[Benchmark] // Commenting this out because for some reason MSBuild is showing error CS0029: Cannot implicitly convert type 'System.ReadOnlySpan<byte>' to 'System.Span<byte>'
public void PixelConverter_Rgba32_ToArgb32() // when trying to build via BenchmarkDotnet. (╯‵□′)╯︵┻━┻
{ // [Benchmark]
Span<byte> source = MemoryMarshal.Cast<Rgba32, byte>(this.PermutedRunnerRgbaToArgb.Source); // public void PixelConverter_Rgba32_ToArgb32()
Span<byte> dest = MemoryMarshal.Cast<TestArgb, byte>(this.PermutedRunnerRgbaToArgb.Destination); // {
// ReadOnlySpan<byte> source = MemoryMarshal.Cast<Rgba32, byte>(this.PermutedRunnerRgbaToArgb.Source);
PixelConverter.FromRgba32.ToArgb32(source, dest); // Span<byte> destination = MemoryMarshal.Cast<TestArgb, byte>(this.PermutedRunnerRgbaToArgb.Destination);
} //
// PixelConverter.FromRgba32.ToArgb32(source, destination);
// }
/* /*
BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3) BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3007/23H2/2023Update/SunValley3)

5
tests/ImageSharp.Benchmarks/General/StructCasting.cs

@ -11,7 +11,7 @@ public class StructCasting
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public short ExplicitCast() public short ExplicitCast()
{ {
int x = 5 * 2; const int x = 5 * 2;
return (short)x; return (short)x;
} }
@ -25,6 +25,7 @@ public class StructCasting
[Benchmark] [Benchmark]
public short UnsafeCastRef() public short UnsafeCastRef()
{ {
return Unsafe.As<int, short>(ref Unsafe.AsRef(5 * 2)); int x = 5 * 2;
return Unsafe.As<int, short>(ref Unsafe.AsRef(ref x));
} }
} }

24
tests/ImageSharp.Benchmarks/General/Vectorization/Divide.cs

@ -15,10 +15,10 @@ public class DivFloat : SIMDBenchmarkBase<float>.Divide
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Standard() public void Standard()
{ {
float v = this.testValue; float v = this.TestValue;
for (int i = 0; i < this.input.Length; i++) for (int i = 0; i < this.Input.Length; i++)
{ {
this.result[i] = this.input[i] / v; this.Result[i] = this.Input[i] / v;
} }
} }
} }
@ -30,10 +30,10 @@ public class Divide : SIMDBenchmarkBase<uint>.Divide
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Standard() public void Standard()
{ {
uint v = this.testValue; uint v = this.TestValue;
for (int i = 0; i < this.input.Length; i++) for (int i = 0; i < this.Input.Length; i++)
{ {
this.result[i] = this.input[i] / v; this.Result[i] = this.Input[i] / v;
} }
} }
} }
@ -45,10 +45,10 @@ public class DivInt32 : SIMDBenchmarkBase<int>.Divide
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Standard() public void Standard()
{ {
int v = this.testValue; int v = this.TestValue;
for (int i = 0; i < this.input.Length; i++) for (int i = 0; i < this.Input.Length; i++)
{ {
this.result[i] = this.input[i] / v; this.Result[i] = this.Input[i] / v;
} }
} }
} }
@ -62,10 +62,10 @@ public class DivInt16 : SIMDBenchmarkBase<short>.Divide
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Standard() public void Standard()
{ {
short v = this.testValue; short v = this.TestValue;
for (int i = 0; i < this.input.Length; i++) for (int i = 0; i < this.Input.Length; i++)
{ {
this.result[i] = (short)(this.input[i] / v); this.Result[i] = (short)(this.Input[i] / v);
} }
} }
} }

18
tests/ImageSharp.Benchmarks/General/Vectorization/Multiply.cs

@ -15,10 +15,10 @@ public class MulUInt32 : SIMDBenchmarkBase<uint>.Multiply
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Standard() public void Standard()
{ {
uint v = this.testValue; uint v = this.TestValue;
for (int i = 0; i < this.input.Length; i++) for (int i = 0; i < this.Input.Length; i++)
{ {
this.result[i] = this.input[i] * v; this.Result[i] = this.Input[i] * v;
} }
} }
} }
@ -28,10 +28,10 @@ public class MulInt32 : SIMDBenchmarkBase<int>.Multiply
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Standard() public void Standard()
{ {
int v = this.testValue; int v = this.TestValue;
for (int i = 0; i < this.input.Length; i++) for (int i = 0; i < this.Input.Length; i++)
{ {
this.result[i] = this.input[i] * v; this.Result[i] = this.Input[i] * v;
} }
} }
} }
@ -45,10 +45,10 @@ public class MulInt16 : SIMDBenchmarkBase<short>.Multiply
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public void Standard() public void Standard()
{ {
short v = this.testValue; short v = this.TestValue;
for (int i = 0; i < this.input.Length; i++) for (int i = 0; i < this.Input.Length; i++)
{ {
this.result[i] = (short)(this.input[i] * v); this.Result[i] = (short)(this.Input[i] * v);
} }
} }
} }

44
tests/ImageSharp.Benchmarks/General/Vectorization/SIMDBenchmarkBase.cs

@ -10,28 +10,28 @@ namespace ImageSharp.Benchmarks.General.Vectorization;
public abstract class SIMDBenchmarkBase<T> public abstract class SIMDBenchmarkBase<T>
where T : struct where T : struct
{ {
protected T[] input; protected virtual T GetTestValue() => default;
protected T[] result; protected virtual Vector<T> GetTestVector() => new(this.GetTestValue());
protected T testValue; [Params(32)]
public int InputSize { get; set; }
protected Vector<T> testVector; protected T[] Input { get; set; }
protected virtual T GetTestValue() => default; protected T[] Result { get; set; }
protected virtual Vector<T> GetTestVector() => new Vector<T>(this.GetTestValue()); protected T TestValue { get; set; }
[Params(32)] protected Vector<T> TestVector { get; set; }
public int InputSize { get; set; }
[GlobalSetup] [GlobalSetup]
public virtual void Setup() public virtual void Setup()
{ {
this.input = new T[this.InputSize]; this.Input = new T[this.InputSize];
this.result = new T[this.InputSize]; this.Result = new T[this.InputSize];
this.testValue = this.GetTestValue(); this.TestValue = this.GetTestValue();
this.testVector = this.GetTestVector(); this.TestVector = this.GetTestVector();
} }
public abstract class Multiply : SIMDBenchmarkBase<T> public abstract class Multiply : SIMDBenchmarkBase<T>
@ -39,13 +39,13 @@ public abstract class SIMDBenchmarkBase<T>
[Benchmark] [Benchmark]
public void Simd() public void Simd()
{ {
Vector<T> v = this.testVector; Vector<T> v = this.TestVector;
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count) for (int i = 0; i < this.Input.Length; i += Vector<uint>.Count)
{ {
Vector<T> a = Unsafe.As<T, Vector<T>>(ref this.input[i]); Vector<T> a = Unsafe.As<T, Vector<T>>(ref this.Input[i]);
a = a * v; a *= v;
Unsafe.As<T, Vector<T>>(ref this.result[i]) = a; Unsafe.As<T, Vector<T>>(ref this.Result[i]) = a;
} }
} }
} }
@ -55,13 +55,13 @@ public abstract class SIMDBenchmarkBase<T>
[Benchmark] [Benchmark]
public void Simd() public void Simd()
{ {
Vector<T> v = this.testVector; Vector<T> v = this.TestVector;
for (int i = 0; i < this.input.Length; i += Vector<uint>.Count) for (int i = 0; i < this.Input.Length; i += Vector<uint>.Count)
{ {
Vector<T> a = Unsafe.As<T, Vector<T>>(ref this.input[i]); Vector<T> a = Unsafe.As<T, Vector<T>>(ref this.Input[i]);
a = a / v; a /= v;
Unsafe.As<T, Vector<T>>(ref this.result[i]) = a; Unsafe.As<T, Vector<T>>(ref this.Result[i]) = a;
} }
} }
} }

20
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -15,9 +15,25 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!--BenchmarkDotNet cannot run static benchmarks--> <!--
BenchmarkDotNet requires a certain structure to the code,
as such, some of these rules cannot be implemented.
-->
<!--Mark members as static--> <!--Mark members as static-->
<NoWarn>CA1822</NoWarn> <!--Validate platform compatibility-->
<!--Types that own disposable fields should be disposable-->
<!--Cannot implicitly convert type 'System.ReadOnlySpan<byte>' to 'System.Span<byte>'-->
<NoWarn>CA1822;CA1416;CA1001;CS0029;CA1861;CA2201</NoWarn>
<!--<NoWarn>CA1001</NoWarn>-->
<!--Validate platform compatibility-->
<!--<NoWarn>CA1416</NoWarn>-->
<!--Types that own disposable fields should be disposable-->
<!--<NoWarn>CA1001</NoWarn>-->
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>

2
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs

@ -40,7 +40,7 @@ public class LoadResizeSaveStressRunner
public double TotalProcessedMegapixels { get; private set; } public double TotalProcessedMegapixels { get; private set; }
public Size LastProcessedImageSize { get; private set; } public ImageSharpSize LastProcessedImageSize { get; private set; }
private string outputDirectory; private string outputDirectory;

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

@ -17,9 +17,9 @@ public class Crop
[Benchmark(Baseline = true, Description = "System.Drawing Crop")] [Benchmark(Baseline = true, Description = "System.Drawing Crop")]
public SDSize CropSystemDrawing() public SDSize CropSystemDrawing()
{ {
using var source = new Bitmap(800, 800); using Bitmap source = new(800, 800);
using var destination = new Bitmap(100, 100); using Bitmap destination = new(100, 100);
using var graphics = Graphics.FromImage(destination); using Graphics graphics = Graphics.FromImage(destination);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
@ -32,7 +32,7 @@ public class Crop
[Benchmark(Description = "ImageSharp Crop")] [Benchmark(Description = "ImageSharp Crop")]
public Size CropImageSharp() public Size CropImageSharp()
{ {
using var image = new Image<Rgba32>(800, 800); using Image<Rgba32> image = new(800, 800);
image.Mutate(x => x.Crop(100, 100)); image.Mutate(x => x.Crop(100, 100));
return new Size(image.Width, image.Height); return new Size(image.Width, image.Height);
} }

34
tests/ImageSharp.Benchmarks/Processing/Resize.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Benchmarks;
public abstract class Resize<TPixel> public abstract class Resize<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private byte[] bytes = null; private byte[] bytes;
private Image<TPixel> sourceImage; private Image<TPixel> sourceImage;
@ -35,7 +35,7 @@ public abstract class Resize<TPixel>
this.sourceImage = Image.Load<TPixel>(this.bytes); this.sourceImage = Image.Load<TPixel>(this.bytes);
var ms1 = new MemoryStream(this.bytes); MemoryStream ms1 = new(this.bytes);
this.sourceBitmap = SDImage.FromStream(ms1); this.sourceBitmap = SDImage.FromStream(ms1);
this.DestSize = this.sourceBitmap.Width / 2; this.DestSize = this.sourceBitmap.Width / 2;
} }
@ -52,21 +52,19 @@ public abstract class Resize<TPixel>
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public int SystemDrawing() public int SystemDrawing()
{ {
using (var destination = new Bitmap(this.DestSize, this.DestSize)) using Bitmap destination = new(this.DestSize, this.DestSize);
using (Graphics g = Graphics.FromImage(destination))
{ {
using (var g = Graphics.FromImage(destination)) g.CompositingMode = CompositingMode.SourceCopy;
{ g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.CompositingMode = CompositingMode.SourceCopy; g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic; g.CompositingQuality = CompositingQuality.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality; g.SmoothingMode = SmoothingMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality; g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize);
g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize);
}
return destination.Width;
} }
return destination.Width;
} }
[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")] [Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")]
@ -87,10 +85,8 @@ public abstract class Resize<TPixel>
{ {
this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism; this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism;
using (Image<TPixel> clone = this.sourceImage.Clone(this.ExecuteResizeOperation)) using Image<TPixel> clone = this.sourceImage.Clone(this.ExecuteResizeOperation);
{ return clone.Width;
return clone.Width;
}
} }
protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx); protected abstract void ExecuteResizeOperation(IImageProcessingContext ctx);

12
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -334,4 +334,16 @@ public class GifDecoderTests
image.DebugSaveMultiFrame(provider); image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
} }
// https://github.com/SixLabors/ImageSharp/issues/2859
[Theory]
[WithFile(TestImages.Gif.Issues.Issue2859_A, PixelTypes.Rgba32)]
[WithFile(TestImages.Gif.Issues.Issue2859_B, PixelTypes.Rgba32)]
public void Issue2859_LZWPixelStackOverflow<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage();
image.DebugSaveMultiFrame(provider);
image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact);
}
} }

2
tests/ImageSharp.Tests/TestImages.cs

@ -536,6 +536,8 @@ public static class TestImages
public const string Issue2450_B = "Gif/issues/issue_2450_2.gif"; public const string Issue2450_B = "Gif/issues/issue_2450_2.gif";
public const string Issue2198 = "Gif/issues/issue_2198.gif"; public const string Issue2198 = "Gif/issues/issue_2198.gif";
public const string Issue2758 = "Gif/issues/issue_2758.gif"; public const string Issue2758 = "Gif/issues/issue_2758.gif";
public const string Issue2859_A = "Gif/issues/issue_2859_A.gif";
public const string Issue2859_B = "Gif/issues/issue_2859_B.gif";
} }
public static readonly string[] Animated = public static readonly string[] Animated =

3
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_A.gif/00.png

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

3
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/00.png

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

3
tests/Images/External/ReferenceOutput/GifDecoderTests/Issue2859_LZWPixelStackOverflow_Rgba32_issue_2859_B.gif/01.png

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

3
tests/Images/Input/Gif/issues/issue_2859_A.gif

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

3
tests/Images/Input/Gif/issues/issue_2859_B.gif

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