Browse Source

Merge branch 'main' into icc-color-conversion

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

6
.github/workflows/build-and-test.yml

@ -73,8 +73,10 @@ jobs:
steps:
- name: Install libgdi+, which is required for tests running on ubuntu
if: ${{ matrix.options.os == 'buildjet-4vcpu-ubuntu-2204-arm' }}
run: sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev
if: ${{ contains(matrix.options.os, 'ubuntu') }}
run: |
sudo apt-get update
sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev
- name: Git Config
shell: bash

7
.github/workflows/code-coverage.yml

@ -17,6 +17,13 @@ jobs:
runs-on: ${{matrix.options.os}}
steps:
- name: Install libgdi+, which is required for tests running on ubuntu
if: ${{ contains(matrix.options.os, 'ubuntu') }}
run: |
sudo apt-get update
sudo apt-get -y install libgdiplus libgif-dev libglib2.0-dev libcairo2-dev libtiff-dev libexif-dev
- name: Git Config
shell: bash
run: |

5
Directory.Build.props

@ -21,9 +21,8 @@
<!-- Import the shared global .props file -->
<Import Project="$(MSBuildThisFileDirectory)shared-infrastructure\msbuild\props\SixLabors.Global.props" />
<PropertyGroup Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<!-- Workaround various issues bound to implicit language features. -->
<LangVersion>preview</LangVersion>
<PropertyGroup>
<LangVersion>12.0</LangVersion>
</PropertyGroup>
<!--

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

@ -3,7 +3,6 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -37,22 +36,22 @@ internal sealed class LzwDecoder : IDisposable
/// <summary>
/// The prefix buffer.
/// </summary>
private readonly IMemoryOwner<int> prefix;
private readonly IMemoryOwner<int> prefixOwner;
/// <summary>
/// The suffix buffer.
/// </summary>
private readonly IMemoryOwner<int> suffix;
private readonly IMemoryOwner<int> suffixOwner;
/// <summary>
/// The scratch buffer for reading data blocks.
/// </summary>
private readonly IMemoryOwner<byte> scratchBuffer;
private readonly IMemoryOwner<byte> bufferOwner;
/// <summary>
/// The pixel stack buffer.
/// </summary>
private readonly IMemoryOwner<int> pixelStack;
private readonly IMemoryOwner<int> pixelStackOwner;
private readonly int minCodeSize;
private readonly int clearCode;
private readonly int endCode;
@ -79,11 +78,12 @@ internal sealed class LzwDecoder : IDisposable
public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream, int minCodeSize)
{
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.suffix = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStack = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
this.scratchBuffer = memoryAllocator.Allocate<byte>(byte.MaxValue, AllocationOptions.None);
this.prefixOwner = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.suffixOwner = memoryAllocator.Allocate<int>(MaxStackSize, AllocationOptions.Clean);
this.pixelStackOwner = memoryAllocator.Allocate<int>(MaxStackSize + 1, AllocationOptions.Clean);
this.bufferOwner = memoryAllocator.Allocate<byte>(byte.MaxValue, AllocationOptions.None);
this.minCodeSize = 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.availableCode = this.clearCode + 2;
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
for (this.code = 0; this.code < this.clearCode; this.code++)
// Fill the suffix buffer with the initial values represented by the number of colors.
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>
@ -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
// 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
int clearCode = 1 << minCodeSize;
if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || clearCode > MaxStackSize)
if (minCodeSize < 2 || minCodeSize > MaximumLzwBits || 1 << minCodeSize > MaxStackSize)
{
// Don't attempt to decode the frame indices.
// 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();
ref byte pixelsRowRef = ref MemoryMarshal.GetReference(indices);
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
Span<byte> buffer = this.scratchBuffer.GetSpan();
int x = 0;
int xyz = 0;
while (xyz < indices.Length)
// Get span values from the owners.
Span<int> prefix = this.prefixOwner.GetSpan();
Span<int> suffix = this.suffixOwner.GetSpan();
Span<int> pixelStack = this.pixelStackOwner.GetSpan();
Span<byte> buffer = this.bufferOwner.GetSpan();
// 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 < 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.
if (this.count == 0)
if (count == 0)
{
// Read a new data block.
this.count = this.ReadBlock(buffer);
if (this.count == 0)
count = ReadBlock(stream, buffer);
if (count == 0)
{
break;
}
this.bufferIndex = 0;
bufferIndex = 0;
}
this.data += buffer[this.bufferIndex] << this.bits;
this.bits += 8;
this.bufferIndex++;
this.count--;
data += buffer[bufferIndex] << bits;
bits += 8;
bufferIndex++;
count--;
continue;
}
// Get the next code
this.code = this.data & this.codeMask;
this.data >>= this.codeSize;
this.bits -= this.codeSize;
code = data & codeMask;
data >>= codeSize;
bits -= codeSize;
// Interpret the code
if (this.code > this.availableCode || this.code == this.endCode)
if (code > availableCode || code == endCode)
{
break;
}
if (this.code == this.clearCode)
if (code == clearCode)
{
// Reset the decoder
this.codeSize = this.minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.availableCode = this.clearCode + 2;
this.oldCode = NullCode;
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
continue;
}
if (this.oldCode == NullCode)
if (oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code);
this.oldCode = this.code;
this.first = this.code;
pixelStack[top++] = suffix[code];
oldCode = code;
first = code;
continue;
}
int inCode = this.code;
if (this.code == this.availableCode)
int inCode = code;
if (code == availableCode)
{
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = (byte)this.first;
this.code = this.oldCode;
pixelStack[top++] = first;
code = 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);
this.code = Unsafe.Add(ref prefixRef, (uint)this.code);
pixelStack[top++] = suffix[code];
code = prefix[code];
}
int suffixCode = Unsafe.Add(ref suffixRef, (uint)this.code);
this.first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = suffixCode;
int suffixCode = suffix[code];
first = 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
if (this.availableCode < MaxStackSize)
if (availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, (uint)this.availableCode) = this.oldCode;
Unsafe.Add(ref suffixRef, (uint)this.availableCode) = this.first;
this.availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize)
prefix[availableCode] = oldCode;
suffix[availableCode] = first;
availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{
this.codeSize++;
this.codeMask = (1 << this.codeSize) - 1;
codeSize++;
codeMask = (1 << codeSize) - 1;
}
}
this.oldCode = inCode;
oldCode = inCode;
}
// Pop a pixel off the pixel stack.
this.top--;
top--;
// Clear missing pixels
xyz++;
Unsafe.Add(ref pixelsRowRef, (uint)x++) = (byte)Unsafe.Add(ref pixelStackRef, (uint)this.top);
// Clear missing pixels.
indices[i++] = (byte)pixelStack[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>
@ -246,130 +276,161 @@ internal sealed class LzwDecoder : IDisposable
/// <param name="length">The resulting index table length.</param>
public void SkipIndices(int length)
{
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
Span<byte> buffer = this.scratchBuffer.GetSpan();
int xyz = 0;
while (xyz < length)
// Get span values from the owners.
Span<int> prefix = this.prefixOwner.GetSpan();
Span<int> suffix = this.suffixOwner.GetSpan();
Span<int> pixelStack = this.pixelStackOwner.GetSpan();
Span<byte> buffer = this.bufferOwner.GetSpan();
// 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.
if (this.count == 0)
if (count == 0)
{
// Read a new data block.
this.count = this.ReadBlock(buffer);
if (this.count == 0)
count = ReadBlock(stream, buffer);
if (count == 0)
{
break;
}
this.bufferIndex = 0;
bufferIndex = 0;
}
this.data += buffer[this.bufferIndex] << this.bits;
this.bits += 8;
this.bufferIndex++;
this.count--;
data += buffer[bufferIndex] << bits;
bits += 8;
bufferIndex++;
count--;
continue;
}
// Get the next code
this.code = this.data & this.codeMask;
this.data >>= this.codeSize;
this.bits -= this.codeSize;
code = data & codeMask;
data >>= codeSize;
bits -= codeSize;
// Interpret the code
if (this.code > this.availableCode || this.code == this.endCode)
if (code > availableCode || code == endCode)
{
break;
}
if (this.code == this.clearCode)
if (code == clearCode)
{
// Reset the decoder
this.codeSize = this.minCodeSize + 1;
this.codeMask = (1 << this.codeSize) - 1;
this.availableCode = this.clearCode + 2;
this.oldCode = NullCode;
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
continue;
}
if (this.oldCode == NullCode)
if (oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = Unsafe.Add(ref suffixRef, (uint)this.code);
this.oldCode = this.code;
this.first = this.code;
pixelStack[top++] = suffix[code];
oldCode = code;
first = code;
continue;
}
int inCode = this.code;
if (this.code == this.availableCode)
int inCode = code;
if (code == availableCode)
{
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = (byte)this.first;
this.code = this.oldCode;
pixelStack[top++] = first;
code = 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);
this.code = Unsafe.Add(ref prefixRef, (uint)this.code);
pixelStack[top++] = suffix[code];
code = prefix[code];
}
int suffixCode = Unsafe.Add(ref suffixRef, (uint)this.code);
this.first = suffixCode;
Unsafe.Add(ref pixelStackRef, (uint)this.top++) = suffixCode;
int suffixCode = suffix[code];
first = 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
if (this.availableCode < MaxStackSize)
if (availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, (uint)this.availableCode) = this.oldCode;
Unsafe.Add(ref suffixRef, (uint)this.availableCode) = this.first;
this.availableCode++;
if (this.availableCode == this.codeMask + 1 && this.availableCode < MaxStackSize)
prefix[availableCode] = oldCode;
suffix[availableCode] = first;
availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{
this.codeSize++;
this.codeMask = (1 << this.codeSize) - 1;
codeSize++;
codeMask = (1 << codeSize) - 1;
}
}
this.oldCode = inCode;
oldCode = inCode;
}
// Pop a pixel off the pixel stack.
this.top--;
top--;
// Clear missing pixels
xyz++;
// Skip missing pixels.
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>
/// 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.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">The buffer to store the block in.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
[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)
{
return 0;
}
int count = this.stream.Read(buffer, 0, bufferSize);
int count = stream.Read(buffer, 0, bufferSize);
return count != bufferSize ? 0 : bufferSize;
}
@ -377,9 +438,9 @@ internal sealed class LzwDecoder : IDisposable
/// <inheritdoc />
public void Dispose()
{
this.prefix.Dispose();
this.suffix.Dispose();
this.pixelStack.Dispose();
this.scratchBuffer.Dispose();
this.prefixOwner.Dispose();
this.suffixOwner.Dispose();
this.pixelStackOwner.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}'.");
[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}'.");
[DoesNotReturn]

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

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

2
src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs

@ -604,7 +604,7 @@ internal class Vp8BitWriter : BitWriterBase
uint profile = 0;
int width = this.enc.Width;
int height = this.enc.Height;
byte[] vp8FrameHeader = new byte[WebpConstants.Vp8FrameHeaderSize];
Span<byte> vp8FrameHeader = stackalloc byte[WebpConstants.Vp8FrameHeaderSize];
// Paragraph 9.1.
uint bits = 0 // keyframe (1b)

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);
}
public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes<Rgba32>
{
}
public class FromRgba32Bytes_ToRgba32 : FromRgba32Bytes<Rgba32>;
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)
=> (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]
public void ReadImages()
{
if (this.bmpBytes == null)
{
this.bmpBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
=> this.bmpBytes ??= File.ReadAllBytes(this.TestImageFullPath);
[Params(TestImages.Bmp.Car)]
public string TestImage { get; set; }
@ -32,16 +27,16 @@ public class DecodeBmp
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public SDSize BmpSystemDrawing()
{
using var memoryStream = new MemoryStream(this.bmpBytes);
using var image = SDImage.FromStream(memoryStream);
using MemoryStream memoryStream = new(this.bmpBytes);
using SDImage image = SDImage.FromStream(memoryStream);
return image.Size;
}
[Benchmark(Description = "ImageSharp Bmp")]
public Size BmpImageSharp()
{
using var memoryStream = new MemoryStream(this.bmpBytes);
using var image = Image.Load<Rgba32>(memoryStream);
using MemoryStream memoryStream = new(this.bmpBytes);
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
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))]
public class EncodeBmp
{
private Stream bmpStream;
private FileStream bmpStream;
private SDImage bmpDrawing;
private Image<Rgba32> bmpCore;
@ -40,14 +40,14 @@ public class EncodeBmp
[Benchmark(Baseline = true, Description = "System.Drawing Bmp")]
public void BmpSystemDrawing()
{
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
this.bmpDrawing.Save(memoryStream, ImageFormat.Bmp);
}
[Benchmark(Description = "ImageSharp Bmp")]
public void BmpImageSharp()
{
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
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);
[GlobalSetup]
public void ReadImages()
{
if (this.gifBytes == null)
{
this.gifBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
public void ReadImages() => this.gifBytes ??= File.ReadAllBytes(this.TestImageFullPath);
[Params(TestImages.Gif.Cheers)]
public string TestImage { get; set; }
@ -32,16 +26,16 @@ public class DecodeGif
[Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public SDSize GifSystemDrawing()
{
using var memoryStream = new MemoryStream(this.gifBytes);
using var image = SDImage.FromStream(memoryStream);
using MemoryStream memoryStream = new(this.gifBytes);
using SDImage image = SDImage.FromStream(memoryStream);
return image.Size;
}
[Benchmark(Description = "ImageSharp Gif")]
public Size GifImageSharp()
{
using var memoryStream = new MemoryStream(this.gifBytes);
using var image = Image.Load<Rgba32>(memoryStream);
using MemoryStream memoryStream = new(this.gifBytes);
using Image<Rgba32> image = Image.Load<Rgba32>(memoryStream);
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
{
// System.Drawing needs this.
private Stream bmpStream;
private FileStream bmpStream;
private SDImage bmpDrawing;
private Image<Rgba32> bmpCore;
// 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 })
};
@ -53,14 +53,14 @@ public class EncodeGif
[Benchmark(Baseline = true, Description = "System.Drawing Gif")]
public void GifSystemDrawing()
{
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
this.bmpDrawing.Save(memoryStream, ImageFormat.Gif);
}
[Benchmark(Description = "ImageSharp Gif")]
public void GifImageSharp()
{
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
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) =>
{
// 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 })
};

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
{
private Block8x8 source;
private Block8x8F dest = default;
private Block8x8F destination;
[GlobalSetup]
public void Setup()
@ -30,16 +29,10 @@ public class Block8x8F_LoadFromInt16
}
[Benchmark(Baseline = true)]
public void Scalar()
{
this.dest.LoadFromInt16Scalar(ref this.source);
}
public void Scalar() => this.destination.LoadFromInt16Scalar(ref this.source);
[Benchmark]
public void ExtendedAvx2()
{
this.dest.LoadFromInt16ExtendedAvx2(ref this.source);
}
public void ExtendedAvx2() => this.destination.LoadFromInt16ExtendedAvx2(ref this.source);
// RESULT:
// 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 quant = CreateFromScalar(1);
private Block8x8 result = default;
private Block8x8 result;
[Benchmark]
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)
{
throw new Exception("ptr is unaligned");
throw new InvalidOperationException("ptr is unaligned");
}
this.alignedPtr = (float*)ptr;
@ -67,21 +67,21 @@ public unsafe class Block8x8F_Round
ref Block8x8F b = ref this.block;
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);
row1 = SimdUtils.FastRound(row1);
row1 = row1.FastRound();
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);
row3 = SimdUtils.FastRound(row3);
row3 = row3.FastRound();
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);
row5 = SimdUtils.FastRound(row5);
row5 = row5.FastRound();
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);
row7 = SimdUtils.FastRound(row7);
row7 = row7.FastRound();
}
[Benchmark]
@ -90,21 +90,21 @@ public unsafe class Block8x8F_Round
ref Block8x8F b = ref Unsafe.AsRef<Block8x8F>(this.alignedPtr);
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);
row1 = SimdUtils.FastRound(row1);
row1 = row1.FastRound();
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);
row3 = SimdUtils.FastRound(row3);
row3 = row3.FastRound();
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);
row5 = SimdUtils.FastRound(row5);
row5 = row5.FastRound();
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);
row7 = SimdUtils.FastRound(row7);
row7 = row7.FastRound();
}
[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> row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V3L);
row0 = SimdUtils.FastRound(row0);
row1 = SimdUtils.FastRound(row1);
row2 = SimdUtils.FastRound(row2);
row3 = SimdUtils.FastRound(row3);
row0 = row0.FastRound();
row1 = row1.FastRound();
row2 = row2.FastRound();
row3 = row3.FastRound();
row0 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V4L);
row1 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V5L);
row2 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V6L);
row3 = ref Unsafe.As<Vector4, Vector<float>>(ref b.V7L);
row0 = SimdUtils.FastRound(row0);
row1 = SimdUtils.FastRound(row1);
row2 = SimdUtils.FastRound(row2);
row3 = SimdUtils.FastRound(row3);
row0 = row0.FastRound();
row1 = row1.FastRound();
row2 = row2.FastRound();
row3 = row3.FastRound();
}
[Benchmark]
@ -174,7 +174,7 @@ public unsafe class Block8x8F_Round
}
[Benchmark]
public unsafe void Sse41_V2()
public void Sse41_V2()
{
ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block);
p = Sse41.RoundToNearestInteger(p);
@ -214,7 +214,7 @@ public unsafe class Block8x8F_Round
}
[Benchmark]
public unsafe void Sse41_V3()
public void Sse41_V3()
{
ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block);
p = Sse41.RoundToNearestInteger(p);
@ -228,7 +228,7 @@ public unsafe class Block8x8F_Round
}
[Benchmark]
public unsafe void Sse41_V4()
public void Sse41_V4()
{
ref Vector128<float> p = ref Unsafe.As<Block8x8F, Vector128<float>>(ref this.block);
nuint offset = (uint)sizeof(Vector128<float>);
@ -271,7 +271,7 @@ public unsafe class Block8x8F_Round
}
[Benchmark]
public unsafe void Sse41_V5_Unaligned()
public void Sse41_V5_Unaligned()
{
float* p = this.alignedPtr + 1;
@ -356,7 +356,7 @@ public unsafe class Block8x8F_Round
}
[Benchmark]
public unsafe void Sse41_V5_Aligned()
public void Sse41_V5_Aligned()
{
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")]
public SDSize JpegSystemDrawing()
{
using var memoryStream = new MemoryStream(this.jpegBytes);
using var image = System.Drawing.Image.FromStream(memoryStream);
using MemoryStream memoryStream = new(this.jpegBytes);
using System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream);
return image.Size;
}
[Benchmark(Description = "JpegDecoderCore.ParseStream")]
public void ParseStream()
{
using var memoryStream = new MemoryStream(this.jpegBytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, memoryStream);
var options = new JpegDecoderOptions() { GeneralOptions = new() { SkipMetadata = true } };
using MemoryStream memoryStream = new(this.jpegBytes);
using BufferedReadStream bufferedStream = new(Configuration.Default, memoryStream);
JpegDecoderOptions options = new() { GeneralOptions = new() { SkipMetadata = true } };
using var decoder = new JpegDecoderCore(options);
var spectralConverter = new NoopSpectralConverter();
using JpegDecoderCore decoder = new(options);
NoopSpectralConverter spectralConverter = new();
decoder.ParseStream(bufferedStream, spectralConverter, cancellationToken: default);
}
@ -48,7 +48,7 @@ public class DecodeJpegParseStreamOnly
// Nor we need to allocate final pixel buffer
// 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
private class NoopSpectralConverter : SpectralConverter
private sealed class NoopSpectralConverter : SpectralConverter
{
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
{
protected override IEnumerable<string> InputImageSubfoldersOrFiles
=> new[]
{
=>
[
TestImages.Jpeg.BenchmarkSuite.Jpeg400_SmallMonochrome,
TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr,
TestImages.Jpeg.BenchmarkSuite.Lake_Small444YCbCr,
TestImages.Jpeg.BenchmarkSuite.MissingFF00ProgressiveBedroom159_MidSize420YCbCr,
TestImages.Jpeg.BenchmarkSuite.ExifGetString750Transform_Huge420YCbCr,
};
];
[Params(InputImageCategory.AllImages)]
public override InputImageCategory InputCategory { get; set; }
[Benchmark]
public void ImageSharp()
=> this.ForEachStream(ms => Image.Load<Rgba32>(ms));
=> this.ForEachStream(Image.Load<Rgba32>);
[Benchmark(Baseline = true)]
public void SystemDrawing()

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

@ -35,26 +35,21 @@ public class DecodeJpeg_ImageSpecific
[GlobalSetup]
public void ReadImages()
{
if (this.jpegBytes == null)
{
this.jpegBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
=> this.jpegBytes ??= File.ReadAllBytes(this.TestImageFullPath);
[Benchmark(Baseline = true)]
public SDSize SystemDrawing()
{
using var memoryStream = new MemoryStream(this.jpegBytes);
using var image = SDImage.FromStream(memoryStream);
using MemoryStream memoryStream = new(this.jpegBytes);
using SDImage image = SDImage.FromStream(memoryStream);
return image.Size;
}
[Benchmark]
public Size ImageSharp()
{
using var memoryStream = new MemoryStream(this.jpegBytes);
using var image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream);
using MemoryStream memoryStream = new(this.jpegBytes);
using Image image = Image.Load(new DecoderOptions() { SkipMetadata = true }, memoryStream);
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
private const string TestImage = TestImages.Jpeg.Baseline.Calliphora;
// Change/add parameters for extra benchmarks
[Params(75, 90, 100)]
public int Quality;
private MemoryStream destinationStream;
// ImageSharp
@ -33,6 +28,10 @@ public class EncodeJpegComparison
// SkiaSharp
private SKBitmap imageSkiaSharp;
// Change/add parameters for extra benchmarks
[Params(75, 90, 100)]
public int Quality { get; set; }
[GlobalSetup(Target = nameof(BenchmarkImageSharp))]
public void SetupImageSharp()
{

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

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

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

@ -11,11 +11,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
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>
/// 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 IEnumerable<string> SearchPatterns => new[] { "*.*" };
protected virtual IEnumerable<string> SearchPatterns => ["*.*"];
/// <summary>
/// Gets the file names containing these strings are substrings are not processed by the benchmark.
/// </summary>
protected virtual IEnumerable<string> ExcludeSubstringsInFileNames => new[] { "badeof", "BadEof", "CriticalEOF" };
protected virtual IEnumerable<string> ExcludeSubstringsInFileNames => ["badeof", "BadEof", "CriticalEOF"];
/// <summary>
/// Gets folders containing files OR files to be processed by the benchmark.
@ -70,7 +70,7 @@ public abstract class MultiImageBenchmarkBase
InputImageCategory.AllImages => input,
InputImageCategory.SmallImagesOnly => 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
@ -86,7 +86,7 @@ public abstract class MultiImageBenchmarkBase
{
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);
@ -103,13 +103,13 @@ public abstract class MultiImageBenchmarkBase
continue;
}
string[] excludeStrings = this.ExcludeSubstringsInFileNames.Select(s => s.ToLower()).ToArray();
string[] excludeStrings = this.ExcludeSubstringsInFileNames.ToArray();
string[] allFiles =
this.SearchPatterns.SelectMany(
f =>
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)
{
@ -126,7 +126,7 @@ public abstract class MultiImageBenchmarkBase
{
foreach (KeyValuePair<string, byte[]> kv in this.FileNames2Bytes)
{
using var memoryStream = new MemoryStream(kv.Value);
using MemoryStream memoryStream = new(kv.Value);
try
{
object obj = operation(memoryStream);
@ -150,7 +150,7 @@ public abstract class MultiImageBenchmarkBase
byte[] bytes = kv.Value;
string fn = kv.Key;
using (var ms1 = new MemoryStream(bytes))
using (MemoryStream ms1 = new(bytes))
{
this.FileNamesToImageSharpImages[fn] = Image.Load<Rgba32>(ms1);
}
@ -191,7 +191,7 @@ public abstract class MultiImageBenchmarkBase
protected void ForEachImageSharpImage(Func<Image<Rgba32>, MemoryStream, object> operation)
{
using var workStream = new MemoryStream();
using MemoryStream workStream = new();
this.ForEachImageSharpImage(
img =>
{
@ -222,7 +222,7 @@ public abstract class MultiImageBenchmarkBase
protected void ForEachSystemDrawingImage(Func<Bitmap, MemoryStream, object> operation)
{
using var workStream = new MemoryStream();
using MemoryStream workStream = new();
this.ForEachSystemDrawingImage(
img =>
{

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

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs;
public class EncodeIndexedPng
{
// System.Drawing needs this.
private Stream bmpStream;
private FileStream bmpStream;
private Image<Rgba32> bmpCore;
[GlobalSetup]
@ -43,48 +43,48 @@ public class EncodeIndexedPng
[Benchmark(Baseline = true, Description = "ImageSharp Octree Png")]
public void PngCoreOctree()
{
using var memoryStream = new MemoryStream();
var options = new PngEncoder { Quantizer = KnownQuantizers.Octree };
using MemoryStream memoryStream = new();
PngEncoder options = new() { 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 }) };
using MemoryStream memoryStream = new();
PngEncoder options = new() { 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 };
using MemoryStream memoryStream = new();
PngEncoder options = new() { 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 }) };
using MemoryStream memoryStream = new();
PngEncoder options = new() { 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 };
using MemoryStream memoryStream = new();
PngEncoder options = new() { 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 }), ColorType = PngColorType.Palette };
using MemoryStream memoryStream = new();
PngEncoder options = new() { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette };
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
{
// System.Drawing needs this.
private Stream bmpStream;
private FileStream bmpStream;
private SDImage bmpDrawing;
private Image<Rgba32> bmpCore;
@ -46,15 +46,15 @@ public class EncodePng
[Benchmark(Baseline = true, Description = "System.Drawing Png")]
public void PngSystemDrawing()
{
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
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 };
using MemoryStream memoryStream = new();
PngEncoder encoder = new() { FilterMethod = PngFilterMethod.None };
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 };
using MagickImage image = new(new MemoryStream(this.data), settings);
return (int)image.Width;
return image.Width;
}
[Benchmark(Description = "ImageSharp Tga")]
@ -48,7 +48,7 @@ public class DecodeTga
return image.Width;
}
private class PfimAllocator : IImageAllocator
private sealed class PfimAllocator : IImageAllocator
{
private int rented;
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")]
public void MagickTga()
{
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
}
[Benchmark(Description = "ImageSharp Tga")]
public void ImageSharpTga()
{
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
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))]
public class EncodeTiff
{
private Stream stream;
private FileStream stream;
private SDImage drawing;
private Image<Rgba32> core;
@ -60,12 +60,12 @@ public class EncodeTiff
public void SystemDrawing()
{
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)) }
};
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
this.drawing.Save(memoryStream, codec, parameters);
}
@ -77,8 +77,8 @@ public class EncodeTiff
TiffPhotometricInterpretation.WhiteIsZero :
TiffPhotometricInterpretation.Rgb;
var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation };
using var memoryStream = new MemoryStream();
TiffEncoder encoder = new() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation };
using MemoryStream memoryStream = new();
this.core.SaveAsTiff(memoryStream, encoder);
}
@ -98,33 +98,15 @@ public class EncodeTiff
}
private static EncoderValue Cast(TiffCompression compression)
{
switch (compression)
=> compression switch
{
case TiffCompression.None:
return EncoderValue.CompressionNone;
case TiffCompression.CcittGroup3Fax:
return EncoderValue.CompressionCCITT3;
case TiffCompression.Ccitt1D:
return EncoderValue.CompressionRle;
case TiffCompression.Lzw:
return EncoderValue.CompressionLZW;
default:
throw new NotSupportedException(compression.ToString());
}
}
TiffCompression.None => EncoderValue.CompressionNone,
TiffCompression.CcittGroup3Fax => EncoderValue.CompressionCCITT3,
TiffCompression.Ccitt1D => EncoderValue.CompressionRle,
TiffCompression.Lzw => EncoderValue.CompressionLZW,
_ => throw new NotSupportedException(compression.ToString()),
};
public static bool IsOneBitCompression(TiffCompression compression)
{
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
=> compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax;
}

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

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

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

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

6
tests/ImageSharp.Benchmarks/Config.cs

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

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

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

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

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

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

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

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

@ -11,7 +11,7 @@ public class StructCasting
[Benchmark(Baseline = true)]
public short ExplicitCast()
{
int x = 5 * 2;
const int x = 5 * 2;
return (short)x;
}
@ -25,6 +25,7 @@ public class StructCasting
[Benchmark]
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)]
public void Standard()
{
float v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
float v = this.TestValue;
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)]
public void Standard()
{
uint v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
uint v = this.TestValue;
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)]
public void Standard()
{
int v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
int v = this.TestValue;
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)]
public void Standard()
{
short v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
short v = this.TestValue;
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)]
public void Standard()
{
uint v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
uint v = this.TestValue;
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)]
public void Standard()
{
int v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
int v = this.TestValue;
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)]
public void Standard()
{
short v = this.testValue;
for (int i = 0; i < this.input.Length; i++)
short v = this.TestValue;
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>
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)]
public int InputSize { get; set; }
protected Vector<T> TestVector { get; set; }
[GlobalSetup]
public virtual void Setup()
{
this.input = new T[this.InputSize];
this.result = new T[this.InputSize];
this.testValue = this.GetTestValue();
this.testVector = this.GetTestVector();
this.Input = new T[this.InputSize];
this.Result = new T[this.InputSize];
this.TestValue = this.GetTestValue();
this.TestVector = this.GetTestVector();
}
public abstract class Multiply : SIMDBenchmarkBase<T>
@ -39,13 +39,13 @@ public abstract class SIMDBenchmarkBase<T>
[Benchmark]
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]);
a = a * v;
Unsafe.As<T, Vector<T>>(ref this.result[i]) = a;
Vector<T> a = Unsafe.As<T, Vector<T>>(ref this.Input[i]);
a *= v;
Unsafe.As<T, Vector<T>>(ref this.Result[i]) = a;
}
}
}
@ -55,13 +55,13 @@ public abstract class SIMDBenchmarkBase<T>
[Benchmark]
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]);
a = a / v;
Unsafe.As<T, Vector<T>>(ref this.result[i]) = a;
Vector<T> a = Unsafe.As<T, Vector<T>>(ref this.Input[i]);
a /= v;
Unsafe.As<T, Vector<T>>(ref this.Result[i]) = a;
}
}
}

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

@ -15,9 +15,25 @@
</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-->
<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>
<Choose>

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

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

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

@ -17,9 +17,9 @@ 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);
using Bitmap source = new(800, 800);
using Bitmap destination = new(100, 100);
using Graphics graphics = Graphics.FromImage(destination);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
@ -32,7 +32,7 @@ public class Crop
[Benchmark(Description = "ImageSharp Crop")]
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));
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>
where TPixel : unmanaged, IPixel<TPixel>
{
private byte[] bytes = null;
private byte[] bytes;
private Image<TPixel> sourceImage;
@ -35,7 +35,7 @@ public abstract class Resize<TPixel>
this.sourceImage = Image.Load<TPixel>(this.bytes);
var ms1 = new MemoryStream(this.bytes);
MemoryStream ms1 = new(this.bytes);
this.sourceBitmap = SDImage.FromStream(ms1);
this.DestSize = this.sourceBitmap.Width / 2;
}
@ -52,21 +52,19 @@ public abstract class Resize<TPixel>
[Benchmark(Baseline = true)]
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.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize);
}
return destination.Width;
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(this.sourceBitmap, 0, 0, this.DestSize, this.DestSize);
}
return destination.Width;
}
[Benchmark(Description = "ImageSharp, MaxDegreeOfParallelism = 1")]
@ -87,10 +85,8 @@ public abstract class Resize<TPixel>
{
this.Configuration.MaxDegreeOfParallelism = maxDegreeOfParallelism;
using (Image<TPixel> clone = this.sourceImage.Clone(this.ExecuteResizeOperation))
{
return clone.Width;
}
using Image<TPixel> clone = this.sourceImage.Clone(this.ExecuteResizeOperation);
return clone.Width;
}
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.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

@ -547,6 +547,8 @@ public static class TestImages
public const string Issue2450_B = "Gif/issues/issue_2450_2.gif";
public const string Issue2198 = "Gif/issues/issue_2198.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 =

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