diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a450aebf4..435c629bc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -19,6 +19,31 @@ jobs: isARM: - ${{ contains(github.event.pull_request.labels.*.name, 'arch:arm32') || contains(github.event.pull_request.labels.*.name, 'arch:arm64') }} options: + - os: ubuntu-latest + framework: net9.0 + sdk: 9.0.x + sdk-preview: true + runtime: -x64 + codecov: false + - os: macos-13 # macos-latest runs on arm64 runners where libgdiplus is unavailable + framework: net9.0 + sdk: 9.0.x + sdk-preview: true + runtime: -x64 + codecov: false + - os: windows-latest + framework: net9.0 + sdk: 9.0.x + sdk-preview: true + runtime: -x64 + codecov: false + - os: buildjet-4vcpu-ubuntu-2204-arm + framework: net9.0 + sdk: 9.0.x + sdk-preview: true + runtime: -x64 + codecov: false + - os: ubuntu-latest framework: net8.0 sdk: 8.0.x @@ -100,7 +125,7 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 8.0.x + 9.0.x - name: DotNet Build if: ${{ matrix.options.sdk-preview != true }} diff --git a/src/ImageSharp.ruleset b/src/ImageSharp.ruleset index b60989020..dee0393cd 100644 --- a/src/ImageSharp.ruleset +++ b/src/ImageSharp.ruleset @@ -1,4 +1,7 @@  + + + \ No newline at end of file diff --git a/src/ImageSharp/Formats/ImageEncoder.cs b/src/ImageSharp/Formats/ImageEncoder.cs index fdaa5c35d..a37a32717 100644 --- a/src/ImageSharp/Formats/ImageEncoder.cs +++ b/src/ImageSharp/Formats/ImageEncoder.cs @@ -51,7 +51,7 @@ public abstract class ImageEncoder : IImageEncoder else { using ChunkedMemoryStream ms = new(configuration.MemoryAllocator); - this.Encode(image, stream, cancellationToken); + this.Encode(image, ms, cancellationToken); ms.Position = 0; ms.CopyTo(stream, configuration.StreamProcessingBufferSize); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 2320fe179..707baa1a8 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -16,7 +16,6 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg; @@ -1473,7 +1472,7 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData this.Frame.ComponentOrder[i / 2] = (byte)componentIndex; - IJpegComponent component = this.Frame.Components[componentIndex]; + JpegComponent component = this.Frame.Components[componentIndex]; // 1 byte: Huffman table selectors. // 4 bits - dc diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index eccd9ede8..a9e63a3d0 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -183,7 +183,7 @@ internal class AlphaDecoder : IDisposable else { this.LosslessDecoder.DecodeImageData(this.Vp8LDec, this.Vp8LDec.Pixels.Memory.Span); - this.ExtractAlphaRows(this.Vp8LDec); + this.ExtractAlphaRows(this.Vp8LDec, this.Width); } } @@ -257,14 +257,15 @@ internal class AlphaDecoder : IDisposable /// Once the image-stream is decoded into ARGB color values, the transparency information will be extracted from the green channel of the ARGB quadruplet. /// /// The VP8L decoder. - private void ExtractAlphaRows(Vp8LDecoder dec) + /// The image width. + private void ExtractAlphaRows(Vp8LDecoder dec, int width) { int numRowsToProcess = dec.Height; - int width = dec.Width; Span input = dec.Pixels.Memory.Span; Span output = this.Alpha.Memory.Span; // Extract alpha (which is stored in the green plane). + // the final width (!= dec->width_) int pixelCount = width * numRowsToProcess; WebpLosslessDecoder.ApplyInverseTransforms(dec, input, this.memoryAllocator); ExtractGreen(input, output, pixelCount); diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 024adb7c2..5287f0b75 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -269,7 +269,11 @@ internal static unsafe class LosslessUtils /// /// The transform data contains color table size and the entries in the color table. /// The pixel data to apply the reverse transform on. - public static void ColorIndexInverseTransform(Vp8LTransform transform, Span pixelData) + /// The resulting pixel data with the reversed transformation data. + public static void ColorIndexInverseTransform( + Vp8LTransform transform, + Span pixelData, + Span outputSpan) { int bitsPerPixel = 8 >> transform.Bits; int width = transform.XSize; @@ -282,7 +286,6 @@ internal static unsafe class LosslessUtils int countMask = pixelsPerByte - 1; int bitMask = (1 << bitsPerPixel) - 1; - uint[] decodedPixelData = new uint[width * height]; int pixelDataPos = 0; for (int y = 0; y < height; y++) { @@ -298,12 +301,12 @@ internal static unsafe class LosslessUtils packedPixels = GetArgbIndex(pixelData[pixelDataPos++]); } - decodedPixelData[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; + outputSpan[decodedPixels++] = colorMap[(int)(packedPixels & bitMask)]; packedPixels >>= bitsPerPixel; } } - decodedPixelData.AsSpan().CopyTo(pixelData); + outputSpan.CopyTo(pixelData); } else { diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index e4c2a7ddf..6de3ae749 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -684,6 +684,7 @@ internal sealed class WebpLosslessDecoder List transforms = decoder.Transforms; for (int i = transforms.Count - 1; i >= 0; i--) { + // TODO: Review these 1D allocations. They could conceivably exceed limits. Vp8LTransform transform = transforms[i]; switch (transform.TransformType) { @@ -701,7 +702,11 @@ internal sealed class WebpLosslessDecoder LosslessUtils.ColorSpaceInverseTransform(transform, pixelData); break; case Vp8LTransformType.ColorIndexingTransform: - LosslessUtils.ColorIndexInverseTransform(transform, pixelData); + using (IMemoryOwner output = memoryAllocator.Allocate(transform.XSize * transform.YSize, AllocationOptions.Clean)) + { + LosslessUtils.ColorIndexInverseTransform(transform, pixelData, output.GetSpan()); + } + break; } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 82f00e876..c645816d4 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -667,12 +667,12 @@ internal static unsafe class Vp8Encoding // V block. dst = dst[8..]; - if (top != default) + if (!top.IsEmpty) { top = top[8..]; } - if (left != default) + if (!left.IsEmpty) { left = left[16..]; } @@ -701,7 +701,7 @@ internal static unsafe class Vp8Encoding private static void VerticalPred(Span dst, Span top, int size) { - if (top != default) + if (!top.IsEmpty) { for (int j = 0; j < size; j++) { @@ -716,7 +716,7 @@ internal static unsafe class Vp8Encoding public static void HorizontalPred(Span dst, Span left, int size) { - if (left != default) + if (!left.IsEmpty) { left = left[1..]; // in the reference implementation, left starts at - 1. for (int j = 0; j < size; j++) @@ -732,9 +732,9 @@ internal static unsafe class Vp8Encoding public static void TrueMotion(Span dst, Span left, Span top, int size) { - if (left != default) + if (!left.IsEmpty) { - if (top != default) + if (!top.IsEmpty) { Span clip = Clip1.AsSpan(255 - left[0]); // left [0] instead of left[-1], original left starts at -1 for (int y = 0; y < size; y++) @@ -759,7 +759,7 @@ internal static unsafe class Vp8Encoding // is equivalent to VE prediction where you just copy the top samples. // Note that if top samples are not available, the default value is // then 129, and not 127 as in the VerticalPred case. - if (top != default) + if (!top.IsEmpty) { VerticalPred(dst, top, size); } @@ -774,14 +774,14 @@ internal static unsafe class Vp8Encoding { int dc = 0; int j; - if (top != default) + if (!top.IsEmpty) { for (j = 0; j < size; j++) { dc += top[j]; } - if (left != default) + if (!left.IsEmpty) { // top and left present. left = left[1..]; // in the reference implementation, left starts at -1. @@ -798,7 +798,7 @@ internal static unsafe class Vp8Encoding dc = (dc + round) >> shift; } - else if (left != default) + else if (!left.IsEmpty) { // left but no top. left = left[1..]; // in the reference implementation, left starts at -1. diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index f8e664ed0..40146c6af 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -48,7 +48,7 @@ internal static class YuvConversion uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); - if (bottomY != default) + if (!bottomY.IsEmpty) { uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); @@ -69,7 +69,7 @@ internal static class YuvConversion YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((xMul2 - 1) * xStep)..]); YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst[((xMul2 - 0) * xStep)..]); - if (bottomY != default) + if (!bottomY.IsEmpty) { uv0 = (diag03 + luv) >> 1; uv1 = (diag12 + uv) >> 1; @@ -85,7 +85,7 @@ internal static class YuvConversion { uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst[((len - 1) * xStep)..]); - if (bottomY != default) + if (!bottomY.IsEmpty) { uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst[((len - 1) * xStep)..]); @@ -120,7 +120,7 @@ internal static class YuvConversion int u0t = (topU[0] + uDiag) >> 1; int v0t = (topV[0] + vDiag) >> 1; YuvToBgr(topY[0], u0t, v0t, topDst); - if (bottomY != default) + if (!bottomY.IsEmpty) { int u0b = (curU[0] + uDiag) >> 1; int v0b = (curV[0] + vDiag) >> 1; @@ -134,7 +134,7 @@ internal static class YuvConversion ref byte topVRef = ref MemoryMarshal.GetReference(topV); ref byte curURef = ref MemoryMarshal.GetReference(curU); ref byte curVRef = ref MemoryMarshal.GetReference(curV); - if (bottomY != default) + if (!bottomY.IsEmpty) { for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) { @@ -160,12 +160,12 @@ internal static class YuvConversion Span tmpTopDst = ru[(4 * 32)..]; Span tmpBottomDst = tmpTopDst[(4 * 32)..]; Span tmpTop = tmpBottomDst[(4 * 32)..]; - Span tmpBottom = (bottomY == default) ? null : tmpTop[32..]; + Span tmpBottom = bottomY.IsEmpty ? null : tmpTop[32..]; UpSampleLastBlock(topU[uvPos..], curU[uvPos..], leftOver, ru); UpSampleLastBlock(topV[uvPos..], curV[uvPos..], leftOver, rv); topY[pos..len].CopyTo(tmpTop); - if (bottomY != default) + if (!bottomY.IsEmpty) { bottomY[pos..len].CopyTo(tmpBottom); ConvertYuvToBgrWithBottomYSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); @@ -176,7 +176,7 @@ internal static class YuvConversion } tmpTopDst[..((len - pos) * xStep)].CopyTo(topDst[(pos * xStep)..]); - if (bottomY != default) + if (!bottomY.IsEmpty) { tmpBottomDst[..((len - pos) * xStep)].CopyTo(bottomDst[(pos * xStep)..]); } diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index 253454814..760d1d334 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.IO; @@ -14,42 +15,19 @@ namespace SixLabors.ImageSharp.IO; /// internal sealed class ChunkedMemoryStream : Stream { - // The memory allocator. - private readonly MemoryAllocator allocator; - - // Data - private MemoryChunk? memoryChunk; - - // The total number of allocated chunks - private int chunkCount; - - // The length of the largest contiguous buffer that can be handled by the allocator. - private readonly int allocatorCapacity; - - // Has the stream been disposed. + private readonly MemoryChunkBuffer memoryChunkBuffer; + private long length; + private long position; + private int bufferIndex; + private int chunkIndex; private bool isDisposed; - // Current chunk to write to - private MemoryChunk? writeChunk; - - // Offset into chunk to write to - private int writeOffset; - - // Current chunk to read from - private MemoryChunk? readChunk; - - // Offset into chunk to read from - private int readOffset; - /// /// Initializes a new instance of the class. /// /// The memory allocator. public ChunkedMemoryStream(MemoryAllocator allocator) - { - this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); - this.allocator = allocator; - } + => this.memoryChunkBuffer = new(allocator); /// public override bool CanRead => !this.isDisposed; @@ -66,25 +44,7 @@ internal sealed class ChunkedMemoryStream : Stream get { this.EnsureNotDisposed(); - - int length = 0; - MemoryChunk? chunk = this.memoryChunk; - while (chunk != null) - { - MemoryChunk? next = chunk.Next; - if (next != null) - { - length += chunk.Length; - } - else - { - length += this.writeOffset; - } - - chunk = next; - } - - return length; + return this.length; } } @@ -94,93 +54,35 @@ internal sealed class ChunkedMemoryStream : Stream get { this.EnsureNotDisposed(); - - if (this.readChunk is null) - { - return 0; - } - - int pos = 0; - MemoryChunk? chunk = this.memoryChunk; - while (chunk != this.readChunk && chunk is not null) - { - pos += chunk.Length; - chunk = chunk.Next; - } - - pos += this.readOffset; - - return pos; + return this.position; } set { this.EnsureNotDisposed(); - - if (value < 0) - { - ThrowArgumentOutOfRange(nameof(value)); - } - - // Back up current position in case new position is out of range - MemoryChunk? backupReadChunk = this.readChunk; - int backupReadOffset = this.readOffset; - - this.readChunk = null; - this.readOffset = 0; - - int leftUntilAtPos = (int)value; - MemoryChunk? chunk = this.memoryChunk; - while (chunk != null) - { - if ((leftUntilAtPos < chunk.Length) - || ((leftUntilAtPos == chunk.Length) - && (chunk.Next is null))) - { - // The desired position is in this chunk - this.readChunk = chunk; - this.readOffset = leftUntilAtPos; - break; - } - - leftUntilAtPos -= chunk.Length; - chunk = chunk.Next; - } - - if (this.readChunk is null) - { - // Position is out of range - this.readChunk = backupReadChunk; - this.readOffset = backupReadOffset; - } + this.SetPosition(value); } } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void Flush() + { + } + + /// public override long Seek(long offset, SeekOrigin origin) { this.EnsureNotDisposed(); - switch (origin) + this.Position = origin switch { - case SeekOrigin.Begin: - this.Position = offset; - break; - - case SeekOrigin.Current: - this.Position += offset; - break; - - case SeekOrigin.End: - this.Position = this.Length + offset; - break; - default: - ThrowInvalidSeek(); - break; - } + SeekOrigin.Begin => (int)offset, + SeekOrigin.Current => (int)(this.Position + offset), + SeekOrigin.End => (int)(this.Length + offset), + _ => throw new ArgumentOutOfRangeException(nameof(offset)), + }; - return this.Position; + return this.position; } /// @@ -188,39 +90,13 @@ internal sealed class ChunkedMemoryStream : Stream => throw new NotSupportedException(); /// - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - try - { - this.isDisposed = true; - if (disposing) - { - ReleaseMemoryChunks(this.memoryChunk); - } - - this.memoryChunk = null; - this.writeChunk = null; - this.readChunk = null; - this.chunkCount = 0; - } - finally - { - base.Dispose(disposing); - } - } - - /// - public override void Flush() + public override int ReadByte() { + Unsafe.SkipInit(out byte b); + return this.Read(MemoryMarshal.CreateSpan(ref b, 1)) == 1 ? b : -1; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int Read(byte[] buffer, int offset, int count) { Guard.NotNull(buffer, nameof(buffer)); @@ -230,111 +106,70 @@ internal sealed class ChunkedMemoryStream : Stream const string bufferMessage = "Offset subtracted from the buffer length is less than count."; Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); - return this.ReadImpl(buffer.AsSpan(offset, count)); + return this.Read(buffer.AsSpan(offset, count)); } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int Read(Span buffer) => this.ReadImpl(buffer); - - private int ReadImpl(Span buffer) + public override int Read(Span buffer) { this.EnsureNotDisposed(); - if (this.readChunk is null) - { - if (this.memoryChunk is null) - { - return 0; - } + int offset = 0; + int count = buffer.Length; - this.readChunk = this.memoryChunk; - this.readOffset = 0; + long remaining = this.length - this.position; + if (remaining <= 0) + { + // Already at the end of the stream, nothing to read + return 0; } - IMemoryOwner chunkBuffer = this.readChunk.Buffer; - int chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) + if (remaining > count) { - chunkSize = this.writeOffset; + remaining = count; } + // 'remaining' can be less than the provided buffer length. + int bytesToRead = (int)remaining; int bytesRead = 0; - int offset = 0; - int count = buffer.Length; - while (count > 0) + while (bytesToRead > 0 && this.bufferIndex != this.memoryChunkBuffer.Length) { - if (this.readOffset == chunkSize) + bool moveToNextChunk = false; + MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex]; + int n = bytesToRead; + int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex; + if (n >= remainingBytesInCurrentChunk) { - // Exit if no more chunks are currently available - if (this.readChunk.Next is null) - { - break; - } - - this.readChunk = this.readChunk.Next; - this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer; - chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) - { - chunkSize = this.writeOffset; - } + n = remainingBytesInCurrentChunk; + moveToNextChunk = true; } - int readCount = Math.Min(count, chunkSize - this.readOffset); - chunkBuffer.Slice(this.readOffset, readCount).CopyTo(buffer[offset..]); - offset += readCount; - count -= readCount; - this.readOffset += readCount; - bytesRead += readCount; - } - - return bytesRead; - } - - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override int ReadByte() - { - this.EnsureNotDisposed(); + // Read n bytes from the current chunk + chunk.Buffer.Memory.Span.Slice(this.chunkIndex, n).CopyTo(buffer.Slice(offset, n)); + bytesToRead -= n; + offset += n; + bytesRead += n; - if (this.readChunk is null) - { - if (this.memoryChunk is null) + if (moveToNextChunk) { - return 0; + this.chunkIndex = 0; + this.bufferIndex++; } - - this.readChunk = this.memoryChunk; - this.readOffset = 0; - } - - IMemoryOwner chunkBuffer = this.readChunk.Buffer; - int chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) - { - chunkSize = this.writeOffset; - } - - if (this.readOffset == chunkSize) - { - // Exit if no more chunks are currently available - if (this.readChunk.Next is null) + else { - return -1; + this.chunkIndex += n; } - - this.readChunk = this.readChunk.Next; - this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer; } - return chunkBuffer.GetSpan()[this.readOffset++]; + this.position += bytesRead; + return bytesRead; } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override void WriteByte(byte value) + => this.Write(MemoryMarshal.CreateSpan(ref value, 1)); + + /// public override void Write(byte[] buffer, int offset, int count) { Guard.NotNull(buffer, nameof(buffer)); @@ -344,157 +179,198 @@ internal sealed class ChunkedMemoryStream : Stream const string bufferMessage = "Offset subtracted from the buffer length is less than count."; Guard.IsFalse(buffer.Length - offset < count, nameof(buffer), bufferMessage); - this.WriteImpl(buffer.AsSpan(offset, count)); + this.Write(buffer.AsSpan(offset, count)); } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override void Write(ReadOnlySpan buffer) => this.WriteImpl(buffer); - - private void WriteImpl(ReadOnlySpan buffer) + public override void Write(ReadOnlySpan buffer) { this.EnsureNotDisposed(); - if (this.memoryChunk is null) + int offset = 0; + int count = buffer.Length; + + long remaining = this.memoryChunkBuffer.Length - this.position; + + // Ensure we have enough capacity to write the data. + while (remaining < count) { - this.memoryChunk = this.AllocateMemoryChunk(); - this.writeChunk = this.memoryChunk; - this.writeOffset = 0; + this.memoryChunkBuffer.Expand(); + remaining = this.memoryChunkBuffer.Length - this.position; } - Guard.NotNull(this.writeChunk); - - Span chunkBuffer = this.writeChunk.Buffer.GetSpan(); - int chunkSize = this.writeChunk.Length; - int count = buffer.Length; - int offset = 0; - while (count > 0) + int bytesToWrite = count; + int bytesWritten = 0; + while (bytesToWrite > 0 && this.bufferIndex != this.memoryChunkBuffer.Length) { - if (this.writeOffset == chunkSize) + bool moveToNextChunk = false; + MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex]; + int n = bytesToWrite; + int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex; + if (n >= remainingBytesInCurrentChunk) { - // Allocate a new chunk if the current one is full - this.writeChunk.Next = this.AllocateMemoryChunk(); - this.writeChunk = this.writeChunk.Next; - this.writeOffset = 0; - chunkBuffer = this.writeChunk.Buffer.GetSpan(); - chunkSize = this.writeChunk.Length; + n = remainingBytesInCurrentChunk; + moveToNextChunk = true; } - int copyCount = Math.Min(count, chunkSize - this.writeOffset); - buffer.Slice(offset, copyCount).CopyTo(chunkBuffer[this.writeOffset..]); + // Write n bytes to the current chunk + buffer.Slice(offset, n).CopyTo(chunk.Buffer.Slice(this.chunkIndex, n)); + bytesToWrite -= n; + offset += n; + bytesWritten += n; - offset += copyCount; - count -= copyCount; - this.writeOffset += copyCount; + if (moveToNextChunk) + { + this.chunkIndex = 0; + this.bufferIndex++; + } + else + { + this.chunkIndex += n; + } } + + this.position += bytesWritten; + this.length += bytesWritten; } - /// - public override void WriteByte(byte value) + /// + /// Writes the entire contents of this memory stream to another stream. + /// + /// The stream to write this memory stream to. + /// is . + /// The current or target stream is closed. + public void WriteTo(Stream stream) { + Guard.NotNull(stream, nameof(stream)); this.EnsureNotDisposed(); - if (this.memoryChunk is null) + this.Position = 0; + + long remaining = this.length - this.position; + if (remaining <= 0) { - this.memoryChunk = this.AllocateMemoryChunk(); - this.writeChunk = this.memoryChunk; - this.writeOffset = 0; + // Already at the end of the stream, nothing to read + return; } - Guard.NotNull(this.writeChunk); + int bytesToRead = (int)remaining; + int bytesRead = 0; + while (bytesToRead > 0 && this.bufferIndex != this.memoryChunkBuffer.Length) + { + bool moveToNextChunk = false; + MemoryChunk chunk = this.memoryChunkBuffer[this.bufferIndex]; + int n = bytesToRead; + int remainingBytesInCurrentChunk = chunk.Length - this.chunkIndex; + if (n >= remainingBytesInCurrentChunk) + { + n = remainingBytesInCurrentChunk; + moveToNextChunk = true; + } - IMemoryOwner chunkBuffer = this.writeChunk.Buffer; - int chunkSize = this.writeChunk.Length; + // Read n bytes from the current chunk + stream.Write(chunk.Buffer.Memory.Span.Slice(this.chunkIndex, n)); + bytesToRead -= n; + bytesRead += n; - if (this.writeOffset == chunkSize) - { - // Allocate a new chunk if the current one is full - this.writeChunk.Next = this.AllocateMemoryChunk(); - this.writeChunk = this.writeChunk.Next; - this.writeOffset = 0; - chunkBuffer = this.writeChunk.Buffer; + if (moveToNextChunk) + { + this.chunkIndex = 0; + this.bufferIndex++; + } + else + { + this.chunkIndex += n; + } } - chunkBuffer.GetSpan()[this.writeOffset++] = value; + this.position += bytesRead; } /// - /// Copy entire buffer into an array. + /// Writes the stream contents to a byte array, regardless of the property. /// - /// The . + /// A new . public byte[] ToArray() { - int length = (int)this.Length; // This will throw if stream is closed - byte[] copy = new byte[this.Length]; - - MemoryChunk? backupReadChunk = this.readChunk; - int backupReadOffset = this.readOffset; - - this.readChunk = this.memoryChunk; - this.readOffset = 0; - this.Read(copy, 0, length); - - this.readChunk = backupReadChunk; - this.readOffset = backupReadOffset; + this.EnsureNotDisposed(); + long position = this.position; + byte[] copy = new byte[this.length]; + this.Position = 0; + _ = this.Read(copy, 0, copy.Length); + this.Position = position; return copy; } - /// - /// Write remainder of this stream to another stream. - /// - /// The stream to write to. - public void WriteTo(Stream stream) + /// + protected override void Dispose(bool disposing) { - this.EnsureNotDisposed(); - - Guard.NotNull(stream, nameof(stream)); + if (this.isDisposed) + { + return; + } - if (this.readChunk is null) + try { - if (this.memoryChunk is null) + this.isDisposed = true; + if (disposing) { - return; + this.memoryChunkBuffer.Dispose(); } - this.readChunk = this.memoryChunk; - this.readOffset = 0; + this.bufferIndex = 0; + this.chunkIndex = 0; + this.position = 0; + this.length = 0; + } + finally + { + base.Dispose(disposing); + } + } + + private void SetPosition(long value) + { + long newPosition = value; + if (newPosition < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); } - IMemoryOwner chunkBuffer = this.readChunk.Buffer; - int chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) + this.position = newPosition; + + // Find the current chunk & current chunk index + int currentChunkIndex = 0; + long offset = newPosition; + + // If the new position is greater than the length of the stream, set the position to the end of the stream + if (offset > 0 && offset >= this.memoryChunkBuffer.Length) { - chunkSize = this.writeOffset; + this.bufferIndex = this.memoryChunkBuffer.ChunkCount - 1; + this.chunkIndex = this.memoryChunkBuffer[this.bufferIndex].Length - 1; + return; } - // Following code mirrors Read() logic (readChunk/readOffset should - // point just past last byte of last chunk when done) - // loop until end of chunks is found - while (true) + // Loop through the current chunks, as we increment the chunk index, we subtract the length of the chunk + // from the offset. Once the offset is less than the length of the chunk, we have found the correct chunk. + while (offset != 0) { - if (this.readOffset == chunkSize) + int chunkLength = this.memoryChunkBuffer[currentChunkIndex].Length; + if (offset < chunkLength) { - // Exit if no more chunks are currently available - if (this.readChunk.Next is null) - { - break; - } - - this.readChunk = this.readChunk.Next; - this.readOffset = 0; - chunkBuffer = this.readChunk.Buffer; - chunkSize = this.readChunk.Length; - if (this.readChunk.Next is null) - { - chunkSize = this.writeOffset; - } + // Found the correct chunk and the corresponding index + break; } - int writeCount = chunkSize - this.readOffset; - stream.Write(chunkBuffer.GetSpan(), this.readOffset, writeCount); - this.readOffset = chunkSize; + offset -= chunkLength; + currentChunkIndex++; } + + this.bufferIndex = currentChunkIndex; + + // Safe to cast here as we know the offset is less than the chunk length. + this.chunkIndex = (int)offset; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -507,48 +383,66 @@ internal sealed class ChunkedMemoryStream : Stream } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowDisposed() => throw new ObjectDisposedException(null, "The stream is closed."); + private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(ChunkedMemoryStream), "The stream is closed."); - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowArgumentOutOfRange(string value) => throw new ArgumentOutOfRangeException(value); + private sealed class MemoryChunkBuffer : IDisposable + { + private readonly List memoryChunks = new(); + private readonly MemoryAllocator allocator; + private readonly int allocatorCapacity; + private bool isDisposed; - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowInvalidSeek() => throw new ArgumentException("Invalid seek origin."); + public MemoryChunkBuffer(MemoryAllocator allocator) + { + this.allocatorCapacity = allocator.GetBufferCapacityInBytes(); + this.allocator = allocator; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private MemoryChunk AllocateMemoryChunk() - { - // Tweak our buffer sizes to take the minimum of the provided buffer sizes - // or the allocator buffer capacity which provides us with the largest - // available contiguous buffer size. - IMemoryOwner buffer = this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.chunkCount++))); + public int ChunkCount => this.memoryChunks.Count; - return new MemoryChunk(buffer) + public long Length { get; private set; } + + public MemoryChunk this[int index] => this.memoryChunks[index]; + + public void Expand() { - Next = null, - Length = buffer.Length() - }; - } + IMemoryOwner buffer = + this.allocator.Allocate(Math.Min(this.allocatorCapacity, GetChunkSize(this.ChunkCount))); - private static void ReleaseMemoryChunks(MemoryChunk? chunk) - { - while (chunk != null) + MemoryChunk chunk = new(buffer) + { + Length = buffer.Length() + }; + + this.memoryChunks.Add(chunk); + this.Length += chunk.Length; + } + + public void Dispose() { - chunk.Dispose(); - chunk = chunk.Next; + if (!this.isDisposed) + { + foreach (MemoryChunk chunk in this.memoryChunks) + { + chunk.Dispose(); + } + + this.memoryChunks.Clear(); + this.Length = 0; + this.isDisposed = true; + } } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int GetChunkSize(int i) - { - // Increment chunks sizes with moderate speed, but without using too many buffers from the same ArrayPool bucket of the default MemoryAllocator. - // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720 -#pragma warning disable IDE1006 // Naming Styles - const int _128K = 1 << 17; - const int _4M = 1 << 22; - return i < 16 ? _128K * (1 << (int)((uint)i / 4)) : _4M; -#pragma warning restore IDE1006 // Naming Styles + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetChunkSize(int i) + { + // Increment chunks sizes with moderate speed, but without using too many buffers from the + // same ArrayPool bucket of the default MemoryAllocator. + // https://github.com/SixLabors/ImageSharp/pull/2006#issuecomment-1066244720 + const int b128K = 1 << 17; + const int b4M = 1 << 22; + return i < 16 ? b128K * (1 << (int)((uint)i / 4)) : b4M; + } } private sealed class MemoryChunk : IDisposable @@ -559,27 +453,15 @@ internal sealed class ChunkedMemoryStream : Stream public IMemoryOwner Buffer { get; } - public MemoryChunk? Next { get; set; } - public int Length { get; init; } - private void Dispose(bool disposing) + public void Dispose() { if (!this.isDisposed) { - if (disposing) - { - this.Buffer.Dispose(); - } - + this.Buffer.Dispose(); this.isDisposed = true; } } - - public void Dispose() - { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 7f58c6ecd..d2ee0f906 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -128,21 +128,18 @@ public abstract partial class Image // Does the given stream contain enough data to fit in the header for the format // and does that data match the format specification? // Individual formats should still check since they are public. - IImageFormat? format = null; foreach (IImageFormatDetector formatDetector in configuration.ImageFormatsManager.FormatDetectors) { if (formatDetector.HeaderSize <= headersBuffer.Length && formatDetector.TryDetectFormat(headersBuffer, out IImageFormat? attemptFormat)) { - format = attemptFormat; + return attemptFormat; } } - if (format is null) - { - ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); - } + ImageFormatManager.ThrowInvalidDecoder(configuration.ImageFormatsManager); - return format; + // Need to write this otherwise compiler is not happy + return null; } /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index d3c403471..0d36340bf 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -13,6 +13,7 @@ Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET Debug;Release + true @@ -29,14 +30,12 @@ - net8.0 - true + net8.0;net9.0 net8.0 - true diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 02403923d..7ec791838 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -160,7 +160,7 @@ public sealed class Image : Image /// /// Gets the root frame. /// - private IPixelSource PixelSourceUnsafe => this.frames.RootFrameUnsafe; + private ImageFrame PixelSourceUnsafe => this.frames.RootFrameUnsafe; /// /// Gets or sets the pixel at the specified position. @@ -324,7 +324,7 @@ public sealed class Image : Image } /// - /// Clones the current image + /// Clones the current image. /// /// Returns a new image with all the same metadata as the original. public Image Clone() => this.Clone(this.Configuration); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 1d2dca870..cf4a421b4 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -241,7 +241,7 @@ internal sealed class ExifWriter return true; } - private static uint GetLength(IList values) + private static uint GetLength(List values) { if (values.Count == 0) { diff --git a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs index 0d50b9809..ddfc62515 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/DataReader/IccDataReader.TagDataEntry.cs @@ -144,7 +144,7 @@ internal sealed partial class IccDataReader ushort channelCount = this.ReadUInt16(); var colorant = (IccColorantEncoding)this.ReadUInt16(); - if (Enum.IsDefined(typeof(IccColorantEncoding), colorant) && colorant != IccColorantEncoding.Unknown) + if (Enum.IsDefined(colorant) && colorant != IccColorantEncoding.Unknown) { // The type is known and so are the values (they are constant) // channelCount should always be 3 but it doesn't really matter if it's not diff --git a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs index be7350bc4..ac78318f2 100644 --- a/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/ICC/IccProfile.cs @@ -155,9 +155,9 @@ public sealed class IccProfile : IDeepCloneable } return arrayValid && - Enum.IsDefined(typeof(IccColorSpaceType), this.Header.DataColorSpace) && - Enum.IsDefined(typeof(IccColorSpaceType), this.Header.ProfileConnectionSpace) && - Enum.IsDefined(typeof(IccRenderingIntent), this.Header.RenderingIntent) && + Enum.IsDefined(this.Header.DataColorSpace) && + Enum.IsDefined(this.Header.ProfileConnectionSpace) && + Enum.IsDefined(this.Header.RenderingIntent) && this.Header.Size is >= minSize and < maxSize; } diff --git a/src/ImageSharp/Processing/AffineTransformBuilder.cs b/src/ImageSharp/Processing/AffineTransformBuilder.cs index 4ac9546f3..6d1e8aaa5 100644 --- a/src/ImageSharp/Processing/AffineTransformBuilder.cs +++ b/src/ImageSharp/Processing/AffineTransformBuilder.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing; /// public class AffineTransformBuilder { - private readonly List> transformMatrixFactories = new(); + private readonly List> transformMatrixFactories = []; /// /// Initializes a new instance of the class. @@ -301,7 +301,8 @@ public class AffineTransformBuilder /// /// The source image size. /// The . - public Matrix3x2 BuildMatrix(Size sourceSize) => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); + public Matrix3x2 BuildMatrix(Size sourceSize) + => this.BuildMatrix(new Rectangle(Point.Empty, sourceSize)); /// /// Returns the combined transform matrix for a given source rectangle. @@ -345,18 +346,8 @@ public class AffineTransformBuilder /// The . public Size GetTransformedSize(Rectangle sourceRectangle) { - Size size = sourceRectangle.Size; - - // Translate the origin matrix to cater for source rectangle offsets. - Matrix3x2 matrix = Matrix3x2.CreateTranslation(-sourceRectangle.Location); - - foreach (Func factory in this.transformMatrixFactories) - { - matrix *= factory(size); - CheckDegenerate(matrix); - } - - return TransformUtils.GetTransformedSize(matrix, size, this.TransformSpace); + Matrix3x2 matrix = this.BuildMatrix(sourceRectangle); + return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); } private static void CheckDegenerate(Matrix3x2 matrix) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs index a680393c8..565a5746d 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Parameters/BokehBlurKernelDataProvider.cs @@ -21,12 +21,12 @@ internal static class BokehBlurKernelDataProvider /// /// Gets the kernel scales to adjust the component values in each kernel /// - private static IReadOnlyList KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; + private static float[] KernelScales { get; } = new[] { 1.4f, 1.2f, 1.2f, 1.2f, 1.2f, 1.2f }; /// /// Gets the available bokeh blur kernel parameters /// - private static IReadOnlyList KernelComponents { get; } = new[] + private static Vector4[][] KernelComponents { get; } = new[] { // 1 component new[] { new Vector4(0.862325f, 1.624835f, 0.767583f, 1.862321f) }, @@ -112,7 +112,7 @@ internal static class BokehBlurKernelDataProvider private static (Vector4[] Parameters, float Scale) GetParameters(int componentsCount) { // Prepare the kernel components - int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Count)); + int index = Math.Max(0, Math.Min(componentsCount - 1, KernelComponents.Length)); return (KernelComponents[index], KernelScales[index]); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index c5c2a778e..888d51320 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -61,12 +61,12 @@ internal class AffineTransformProcessor : TransformProcessor, IR if (matrix.Equals(Matrix3x2.Identity)) { // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); for (int y = 0; y < sourceBuffer.Height; y++) { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); } return; @@ -77,7 +77,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR if (sampler is NearestNeighborResampler) { - var nnOperation = new NNAffineOperation( + NNAffineOperation nnOperation = new( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, @@ -91,7 +91,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR return; } - var operation = new AffineOperation( + AffineOperation operation = new( configuration, source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), @@ -128,17 +128,17 @@ internal class AffineTransformProcessor : TransformProcessor, IR [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span destRow = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - for (int x = 0; x < destRow.Length; x++) + for (int x = 0; x < destinationRowSpan.Length; x++) { - var point = Vector2.Transform(new Vector2(x, y), this.matrix); + Vector2 point = Vector2.Transform(new Vector2(x, y), this.matrix); int px = (int)MathF.Round(point.X); int py = (int)MathF.Round(point.Y); if (this.bounds.Contains(px, py)) { - destRow[x] = this.source.GetElementUnsafe(px, py); + destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); } } } @@ -195,16 +195,16 @@ internal class AffineTransformProcessor : TransformProcessor, IR for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, - rowSpan, + destinationRowSpan, span, PixelConversionModifiers.Scale); for (int x = 0; x < span.Length; x++) { - var point = Vector2.Transform(new Vector2(x, y), matrix); + Vector2 point = Vector2.Transform(new Vector2(x, y), matrix); float pY = point.Y; float pX = point.X; @@ -221,13 +221,14 @@ internal class AffineTransformProcessor : TransformProcessor, IR Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { + Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); float yWeight = sampler.GetValue(yK - pY); for (int xK = left; xK <= right; xK++) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = sourceRowSpan[xK].ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -240,7 +241,7 @@ internal class AffineTransformProcessor : TransformProcessor, IR PixelOperations.Instance.FromVector4Destructive( this.configuration, span, - rowSpan, + destinationRowSpan, PixelConversionModifiers.Scale); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs new file mode 100644 index 000000000..1190de435 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/GaussianEliminationSolver.cs @@ -0,0 +1,86 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; + +/// +/// Represents a solver for systems of linear equations using the Gaussian Elimination method. +/// This class applies Gaussian Elimination to transform the matrix into row echelon form and then performs back substitution to find the solution vector. +/// This implementation is based on: +/// +internal static class GaussianEliminationSolver +{ + /// + /// Solves the system of linear equations represented by the given matrix and result vector using Gaussian Elimination. + /// + /// The square matrix representing the coefficients of the linear equations. + /// The vector representing the constants on the right-hand side of the linear equations. + /// Thrown if the matrix is singular and cannot be solved. + /// + /// The matrix passed to this method must be a square matrix. + /// If the matrix is singular (i.e., has no unique solution), an will be thrown. + /// + public static void Solve(double[][] matrix, double[] result) + { + TransformToRowEchelonForm(matrix, result); + ApplyBackSubstitution(matrix, result); + } + + private static void TransformToRowEchelonForm(double[][] matrix, double[] result) + { + int colCount = matrix.Length; + int rowCount = matrix[0].Length; + int pivotRow = 0; + for (int pivotCol = 0; pivotCol < colCount; pivotCol++) + { + double maxValue = double.Abs(matrix[pivotRow][pivotCol]); + int maxIndex = pivotRow; + for (int r = pivotRow + 1; r < rowCount; r++) + { + double value = double.Abs(matrix[r][pivotCol]); + if (value > maxValue) + { + maxIndex = r; + maxValue = value; + } + } + + if (matrix[maxIndex][pivotCol] == 0) + { + throw new NotSupportedException("Matrix is singular and cannot be solve"); + } + + (matrix[pivotRow], matrix[maxIndex]) = (matrix[maxIndex], matrix[pivotRow]); + (result[pivotRow], result[maxIndex]) = (result[maxIndex], result[pivotRow]); + + for (int r = pivotRow + 1; r < rowCount; r++) + { + double fraction = matrix[r][pivotCol] / matrix[pivotRow][pivotCol]; + for (int c = pivotCol + 1; c < colCount; c++) + { + matrix[r][c] -= matrix[pivotRow][c] * fraction; + } + + result[r] -= result[pivotRow] * fraction; + matrix[r][pivotCol] = 0; + } + + pivotRow++; + } + } + + private static void ApplyBackSubstitution(double[][] matrix, double[] result) + { + int rowCount = matrix[0].Length; + + for (int row = rowCount - 1; row >= 0; row--) + { + result[row] /= matrix[row][row]; + + for (int r = 0; r < row; r++) + { + result[r] -= result[row] * matrix[r][row]; + } + } + } +} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index b741dc4ee..068f69ceb 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -61,12 +61,12 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (matrix.Equals(Matrix4x4.Identity)) { // The clone will be blank here copy all the pixel data over - var interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); + Rectangle interest = Rectangle.Intersect(this.SourceRectangle, destination.Bounds()); Buffer2DRegion sourceBuffer = source.PixelBuffer.GetRegion(interest); - Buffer2DRegion destbuffer = destination.PixelBuffer.GetRegion(interest); + Buffer2DRegion destinationBuffer = destination.PixelBuffer.GetRegion(interest); for (int y = 0; y < sourceBuffer.Height; y++) { - sourceBuffer.DangerousGetRowSpan(y).CopyTo(destbuffer.DangerousGetRowSpan(y)); + sourceBuffer.DangerousGetRowSpan(y).CopyTo(destinationBuffer.DangerousGetRowSpan(y)); } return; @@ -77,7 +77,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (sampler is NearestNeighborResampler) { - var nnOperation = new NNProjectiveOperation( + NNProjectiveOperation nnOperation = new( source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), destination.PixelBuffer, @@ -91,7 +91,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor return; } - var operation = new ProjectiveOperation( + ProjectiveOperation operation = new( configuration, source.PixelBuffer, Rectangle.Intersect(this.SourceRectangle, source.Bounds()), @@ -128,9 +128,9 @@ internal class ProjectiveTransformProcessor : TransformProcessor [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span destRow = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); - for (int x = 0; x < destRow.Length; x++) + for (int x = 0; x < destinationRowSpan.Length; x++) { Vector2 point = TransformUtils.ProjectiveTransform2D(x, y, this.matrix); int px = (int)MathF.Round(point.X); @@ -138,7 +138,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor if (this.bounds.Contains(px, py)) { - destRow[x] = this.source.GetElementUnsafe(px, py); + destinationRowSpan[x] = this.source.GetElementUnsafe(px, py); } } } @@ -195,10 +195,10 @@ internal class ProjectiveTransformProcessor : TransformProcessor for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.DangerousGetRowSpan(y); + Span destinationRowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, - rowSpan, + destinationRowSpan, span, PixelConversionModifiers.Scale); @@ -221,13 +221,14 @@ internal class ProjectiveTransformProcessor : TransformProcessor Vector4 sum = Vector4.Zero; for (int yK = top; yK <= bottom; yK++) { + Span sourceRowSpan = this.source.DangerousGetRowSpan(yK); float yWeight = sampler.GetValue(yK - pY); for (int xK = left; xK <= right; xK++) { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = sourceRowSpan[xK].ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -240,7 +241,7 @@ internal class ProjectiveTransformProcessor : TransformProcessor PixelOperations.Instance.FromVector4Destructive( this.configuration, span, - rowSpan, + destinationRowSpan, PixelConversionModifiers.Scale); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs index 62ea5e830..47b3250b8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/TransformUtils.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; namespace SixLabors.ImageSharp.Processing.Processors.Transforms; @@ -278,6 +279,91 @@ internal static class TransformUtils return matrix; } + /// + /// Computes the projection matrix for a quad distortion transformation. + /// + /// The source rectangle. + /// The top-left point of the distorted quad. + /// The top-right point of the distorted quad. + /// The bottom-right point of the distorted quad. + /// The bottom-left point of the distorted quad. + /// The to use when creating the matrix. + /// The computed projection matrix for the quad distortion. + /// + /// This method is based on the algorithm described in the following article: + /// + /// + public static Matrix4x4 CreateQuadDistortionMatrix( + Rectangle rectangle, + PointF topLeft, + PointF topRight, + PointF bottomRight, + PointF bottomLeft, + TransformSpace transformSpace) + { + PointF p1 = new(rectangle.X, rectangle.Y); + PointF p2 = new(rectangle.X + rectangle.Width, rectangle.Y); + PointF p3 = new(rectangle.X + rectangle.Width, rectangle.Y + rectangle.Height); + PointF p4 = new(rectangle.X, rectangle.Y + rectangle.Height); + + PointF q1 = topLeft; + PointF q2 = topRight; + PointF q3 = bottomRight; + PointF q4 = bottomLeft; + + double[][] matrixData = + [ + [p1.X, p1.Y, 1, 0, 0, 0, -p1.X * q1.X, -p1.Y * q1.X], + [0, 0, 0, p1.X, p1.Y, 1, -p1.X * q1.Y, -p1.Y * q1.Y], + [p2.X, p2.Y, 1, 0, 0, 0, -p2.X * q2.X, -p2.Y * q2.X], + [0, 0, 0, p2.X, p2.Y, 1, -p2.X * q2.Y, -p2.Y * q2.Y], + [p3.X, p3.Y, 1, 0, 0, 0, -p3.X * q3.X, -p3.Y * q3.X], + [0, 0, 0, p3.X, p3.Y, 1, -p3.X * q3.Y, -p3.Y * q3.Y], + [p4.X, p4.Y, 1, 0, 0, 0, -p4.X * q4.X, -p4.Y * q4.X], + [0, 0, 0, p4.X, p4.Y, 1, -p4.X * q4.Y, -p4.Y * q4.Y], + ]; + + double[] b = + [ + q1.X, + q1.Y, + q2.X, + q2.Y, + q3.X, + q3.Y, + q4.X, + q4.Y, + ]; + + GaussianEliminationSolver.Solve(matrixData, b); + +#pragma warning disable SA1117 + Matrix4x4 projectionMatrix = new( + (float)b[0], (float)b[3], 0, (float)b[6], + (float)b[1], (float)b[4], 0, (float)b[7], + 0, 0, 1, 0, + (float)b[2], (float)b[5], 0, 1); +#pragma warning restore SA1117 + + // Check if the matrix involves only affine transformations by inspecting the relevant components. + // We want to use pixel space for calculations only if the transformation is purely 2D and does not include + // any perspective effects, non-standard scaling, or unusual translations that could distort the image. + if (transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(projectionMatrix)) + { + if (projectionMatrix.M41 != 0) + { + projectionMatrix.M41--; + } + + if (projectionMatrix.M42 != 0) + { + projectionMatrix.M42--; + } + } + + return projectionMatrix; + } + /// /// Returns the size relative to the source for the given transformation matrix. /// @@ -293,15 +379,16 @@ internal static class TransformUtils /// /// The transformation matrix. /// The source size. + /// The used when generating the matrix. /// /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size GetTransformedSize(Matrix4x4 matrix, Size size) + public static Size GetTransformedSize(Matrix4x4 matrix, Size size, TransformSpace transformSpace) { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - if (matrix.Equals(default) || matrix.Equals(Matrix4x4.Identity)) + if (matrix.IsIdentity || matrix.Equals(default)) { return size; } @@ -309,27 +396,7 @@ internal static class TransformUtils // Check if the matrix involves only affine transformations by inspecting the relevant components. // We want to use pixel space for calculations only if the transformation is purely 2D and does not include // any perspective effects, non-standard scaling, or unusual translations that could distort the image. - // The conditions are as follows: - bool usePixelSpace = - - // 1. Ensure there's no perspective distortion: - // M34 corresponds to the perspective component. For a purely 2D affine transformation, this should be 0. - (matrix.M34 == 0) && - - // 2. Ensure standard affine transformation without any unusual depth or perspective scaling: - // M44 should be 1 for a standard affine transformation. If M44 is not 1, it indicates non-standard depth - // scaling or perspective, which suggests a more complex transformation. - (matrix.M44 == 1) && - - // 3. Ensure no unusual translation in the x-direction: - // M14 represents translation in the x-direction that might be part of a more complex transformation. - // For standard affine transformations, M14 should be 0. - (matrix.M14 == 0) && - - // 4. Ensure no unusual translation in the y-direction: - // M24 represents translation in the y-direction that might be part of a more complex transformation. - // For standard affine transformations, M24 should be 0. - (matrix.M24 == 0); + bool usePixelSpace = transformSpace == TransformSpace.Pixel && IsAffineRotationOrSkew(matrix); // Define an offset size to translate between pixel space and coordinate space. // When using pixel space, apply a scaling sensitive offset to translate to discrete pixel coordinates. @@ -376,7 +443,7 @@ internal static class TransformUtils { Guard.IsTrue(size.Width > 0 && size.Height > 0, nameof(size), "Source size dimensions cannot be 0!"); - if (matrix.Equals(default) || matrix.Equals(Matrix3x2.Identity)) + if (matrix.IsIdentity || matrix.Equals(default)) { return size; } @@ -412,7 +479,7 @@ internal static class TransformUtils /// private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix3x2 matrix, out Rectangle bounds) { - if (rectangle.Equals(default) || Matrix3x2.Identity.Equals(matrix)) + if (matrix.IsIdentity || rectangle.Equals(default)) { bounds = default; return false; @@ -439,7 +506,7 @@ internal static class TransformUtils [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool TryGetTransformedRectangle(RectangleF rectangle, Matrix4x4 matrix, out Rectangle bounds) { - if (rectangle.Equals(default) || Matrix4x4.Identity.Equals(matrix)) + if (matrix.IsIdentity || rectangle.Equals(default)) { bounds = default; return false; @@ -492,4 +559,44 @@ internal static class TransformUtils (int)Math.Ceiling(right), (int)Math.Ceiling(bottom)); } + + private static bool IsAffineRotationOrSkew(Matrix4x4 matrix) + { + const float epsilon = 1e-6f; + + // Check if the matrix is affine (last column should be [0, 0, 0, 1]) + if (Math.Abs(matrix.M14) > epsilon || + Math.Abs(matrix.M24) > epsilon || + Math.Abs(matrix.M34) > epsilon || + Math.Abs(matrix.M44 - 1f) > epsilon) + { + return false; + } + + // Translation component (M41, m42) are allowed, others are not. + if (Math.Abs(matrix.M43) > epsilon) + { + return false; + } + + // Extract the linear (rotation and skew) part of the matrix + // Upper-left 3x3 matrix + float m11 = matrix.M11, m12 = matrix.M12, m13 = matrix.M13; + float m21 = matrix.M21, m22 = matrix.M22, m23 = matrix.M23; + float m31 = matrix.M31, m32 = matrix.M32, m33 = matrix.M33; + + // Compute the determinant of the linear part + float determinant = (m11 * ((m22 * m33) - (m23 * m32))) - + (m12 * ((m21 * m33) - (m23 * m31))) + + (m13 * ((m21 * m32) - (m22 * m31))); + + // Check if the determinant is approximately ±1 (no scaling) + if (Math.Abs(Math.Abs(determinant) - 1f) > epsilon) + { + return false; + } + + // All checks passed; the matrix represents rotation and/or skew (with possible translation) + return true; + } } diff --git a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs index 9027ee726..82b897ea5 100644 --- a/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs +++ b/src/ImageSharp/Processing/ProjectiveTransformBuilder.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Processing; /// public class ProjectiveTransformBuilder { - private readonly List> transformMatrixFactories = new(); + private readonly List> transformMatrixFactories = []; /// /// Initializes a new instance of the class. @@ -279,6 +279,30 @@ public class ProjectiveTransformBuilder public ProjectiveTransformBuilder AppendTranslation(Vector2 position) => this.AppendMatrix(Matrix4x4.CreateTranslation(new Vector3(position, 0))); + /// + /// Prepends a quad distortion matrix using the specified corner points. + /// + /// The top-left corner point of the distorted quad. + /// The top-right corner point of the distorted quad. + /// The bottom-right corner point of the distorted quad. + /// The bottom-left corner point of the distorted quad. + /// The . + public ProjectiveTransformBuilder PrependQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) + => this.Prepend(size => TransformUtils.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); + + /// + /// Appends a quad distortion matrix using the specified corner points. + /// + /// The top-left corner point of the distorted quad. + /// The top-right corner point of the distorted quad. + /// The bottom-right corner point of the distorted quad. + /// The bottom-left corner point of the distorted quad. + /// The . + public ProjectiveTransformBuilder AppendQuadDistortion(PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) + => this.Append(size => TransformUtils.CreateQuadDistortionMatrix( + new Rectangle(Point.Empty, size), topLeft, topRight, bottomRight, bottomLeft, this.TransformSpace)); + /// /// Prepends a raw matrix. /// @@ -361,18 +385,8 @@ public class ProjectiveTransformBuilder /// The . public Size GetTransformedSize(Rectangle sourceRectangle) { - Size size = sourceRectangle.Size; - - // Translate the origin matrix to cater for source rectangle offsets. - Matrix4x4 matrix = Matrix4x4.CreateTranslation(new Vector3(-sourceRectangle.Location, 0)); - - foreach (Func factory in this.transformMatrixFactories) - { - matrix *= factory(size); - CheckDegenerate(matrix); - } - - return TransformUtils.GetTransformedSize(matrix, size); + Matrix4x4 matrix = this.BuildMatrix(sourceRectangle); + return TransformUtils.GetTransformedSize(matrix, sourceRectangle.Size, this.TransformSpace); } private static void CheckDegenerate(Matrix4x4 matrix) diff --git a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs index 1b6663e70..8728dd671 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Pad3Shuffle4Channel.cs @@ -47,37 +47,37 @@ public class Pad3Shuffle4Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // |------------------------- |------------------- |-------------------------------------------------- |------ |------------:|----------:|----------:|------------:|------:|--------:|------:|------:|------:|----------:| -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 120.64 ns | 7.190 ns | 21.200 ns | 114.26 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 120.64 ns | 7.190 ns | 21.200 ns | 114.26 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4 | 2. AVX | Empty | 96 | 23.63 ns | 0.175 ns | 0.155 ns | 23.65 ns | 0.15 | 0.01 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 25.25 ns | 0.356 ns | 0.298 ns | 25.27 ns | 0.17 | 0.01 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 96 | 25.25 ns | 0.356 ns | 0.298 ns | 25.27 ns | 0.17 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 14.80 ns | 0.358 ns | 1.032 ns | 14.64 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 14.80 ns | 0.358 ns | 1.032 ns | 14.64 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 96 | 24.84 ns | 0.376 ns | 0.333 ns | 24.74 ns | 1.57 | 0.06 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 96 | 24.58 ns | 0.471 ns | 0.704 ns | 24.38 ns | 1.60 | 0.09 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 96 | 24.58 ns | 0.471 ns | 0.704 ns | 24.38 ns | 1.60 | 0.09 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 258.92 ns | 4.873 ns | 4.069 ns | 257.95 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 258.92 ns | 4.873 ns | 4.069 ns | 257.95 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4 | 2. AVX | Empty | 384 | 41.41 ns | 0.859 ns | 1.204 ns | 41.33 ns | 0.16 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 40.74 ns | 0.848 ns | 0.793 ns | 40.48 ns | 0.16 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 384 | 40.74 ns | 0.848 ns | 0.793 ns | 40.48 ns | 0.16 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 74.50 ns | 0.490 ns | 0.383 ns | 74.49 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 74.50 ns | 0.490 ns | 0.383 ns | 74.49 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 384 | 40.74 ns | 0.624 ns | 0.584 ns | 40.72 ns | 0.55 | 0.01 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 384 | 38.28 ns | 0.534 ns | 0.417 ns | 38.22 ns | 0.51 | 0.01 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 384 | 38.28 ns | 0.534 ns | 0.417 ns | 38.22 ns | 0.51 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 503.91 ns | 6.466 ns | 6.048 ns | 501.58 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 503.91 ns | 6.466 ns | 6.048 ns | 501.58 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4 | 2. AVX | Empty | 768 | 62.86 ns | 0.332 ns | 0.277 ns | 62.80 ns | 0.12 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 64.59 ns | 0.469 ns | 0.415 ns | 64.62 ns | 0.13 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 768 | 64.59 ns | 0.469 ns | 0.415 ns | 64.62 ns | 0.13 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 110.51 ns | 0.592 ns | 0.554 ns | 110.33 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 110.51 ns | 0.592 ns | 0.554 ns | 110.33 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 768 | 64.72 ns | 1.306 ns | 1.090 ns | 64.51 ns | 0.59 | 0.01 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 768 | 62.11 ns | 0.816 ns | 0.682 ns | 61.98 ns | 0.56 | 0.01 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 768 | 62.11 ns | 0.816 ns | 0.682 ns | 61.98 ns | 0.56 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 1,005.84 ns | 13.176 ns | 12.325 ns | 1,004.70 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 1,005.84 ns | 13.176 ns | 12.325 ns | 1,004.70 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4 | 2. AVX | Empty | 1536 | 110.05 ns | 0.256 ns | 0.214 ns | 110.04 ns | 0.11 | 0.00 | - | - | - | - | -// | Pad3Shuffle4 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.545 ns | 0.483 ns | 110.09 ns | 0.11 | 0.00 | - | - | - | - | +// | Pad3Shuffle4 | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 110.23 ns | 0.545 ns | 0.483 ns | 110.09 ns | 0.11 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 220.37 ns | 1.601 ns | 1.419 ns | 220.13 ns | 1.00 | 0.00 | - | - | - | - | // | Pad3Shuffle4FastFallback | 2. AVX | Empty | 1536 | 111.54 ns | 2.173 ns | 2.901 ns | 111.27 ns | 0.51 | 0.01 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 110.23 ns | 0.456 ns | 0.427 ns | 110.25 ns | 0.50 | 0.00 | - | - | - | - | // 2023-02-21 // ########## @@ -94,34 +94,34 @@ public class Pad3Shuffle4Channel // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | // |------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:| -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 57.45 ns | 0.126 ns | 0.118 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 96 | 14.70 ns | 0.105 ns | 0.098 ns | 0.26 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 57.45 ns | 0.126 ns | 0.118 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 96 | 14.70 ns | 0.105 ns | 0.098 ns | 0.26 | - | - | - | - | // | Pad3Shuffle4 | 3. AVX | Empty | 96 | 14.63 ns | 0.070 ns | 0.062 ns | 0.25 | - | - | - | - | // | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 12.08 ns | 0.028 ns | 0.025 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 96 | 14.04 ns | 0.050 ns | 0.044 ns | 1.16 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 12.08 ns | 0.028 ns | 0.025 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 96 | 14.04 ns | 0.050 ns | 0.044 ns | 1.16 | - | - | - | - | // | Pad3Shuffle4FastFallback | 3. AVX | Empty | 96 | 13.90 ns | 0.086 ns | 0.080 ns | 1.15 | - | - | - | - | // | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 202.67 ns | 2.010 ns | 1.678 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 384 | 25.54 ns | 0.060 ns | 0.053 ns | 0.13 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 202.67 ns | 2.010 ns | 1.678 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 384 | 25.54 ns | 0.060 ns | 0.053 ns | 0.13 | - | - | - | - | // | Pad3Shuffle4 | 3. AVX | Empty | 384 | 25.72 ns | 0.139 ns | 0.130 ns | 0.13 | - | - | - | - | // | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 60.35 ns | 0.080 ns | 0.071 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 384 | 25.18 ns | 0.388 ns | 0.324 ns | 0.42 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 60.35 ns | 0.080 ns | 0.071 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 384 | 25.18 ns | 0.388 ns | 0.324 ns | 0.42 | - | - | - | - | // | Pad3Shuffle4FastFallback | 3. AVX | Empty | 384 | 26.21 ns | 0.067 ns | 0.059 ns | 0.43 | - | - | - | - | // | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 393.88 ns | 1.353 ns | 1.199 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 768 | 39.44 ns | 0.230 ns | 0.204 ns | 0.10 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 393.88 ns | 1.353 ns | 1.199 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 768 | 39.44 ns | 0.230 ns | 0.204 ns | 0.10 | - | - | - | - | // | Pad3Shuffle4 | 3. AVX | Empty | 768 | 39.51 ns | 0.108 ns | 0.101 ns | 0.10 | - | - | - | - | // | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 112.02 ns | 0.140 ns | 0.131 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 768 | 38.60 ns | 0.091 ns | 0.080 ns | 0.34 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 112.02 ns | 0.140 ns | 0.131 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 768 | 38.60 ns | 0.091 ns | 0.080 ns | 0.34 | - | - | - | - | // | Pad3Shuffle4FastFallback | 3. AVX | Empty | 768 | 38.18 ns | 0.100 ns | 0.084 ns | 0.34 | - | - | - | - | // | | | | | | | | | | | | | -// | Pad3Shuffle4 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 777.95 ns | 1.719 ns | 1.342 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4 | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 73.11 ns | 0.090 ns | 0.075 ns | 0.09 | - | - | - | - | +// | Pad3Shuffle4 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 777.95 ns | 1.719 ns | 1.342 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4 | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 73.11 ns | 0.090 ns | 0.075 ns | 0.09 | - | - | - | - | // | Pad3Shuffle4 | 3. AVX | Empty | 1536 | 73.41 ns | 0.125 ns | 0.117 ns | 0.09 | - | - | - | - | // | | | | | | | | | | | | | -// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 218.14 ns | 0.377 ns | 0.334 ns | 1.00 | - | - | - | - | -// | Pad3Shuffle4FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 72.55 ns | 1.418 ns | 1.184 ns | 0.33 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 218.14 ns | 0.377 ns | 0.334 ns | 1.00 | - | - | - | - | +// | Pad3Shuffle4FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 72.55 ns | 1.418 ns | 1.184 ns | 0.33 | - | - | - | - | // | Pad3Shuffle4FastFallback | 3. AVX | Empty | 1536 | 73.15 ns | 0.330 ns | 0.292 ns | 0.34 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs index 8b7b89eb3..e4c12900f 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Shuffle3Channel.cs @@ -43,21 +43,21 @@ public class Shuffle3Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // |--------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 48.46 ns | 1.034 ns | 2.438 ns | 47.46 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle3 | 2. AVX | Empty | 96 | 32.42 ns | 0.537 ns | 0.476 ns | 32.34 ns | 0.66 | 0.04 | - | - | - | - | -// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - | +// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 96 | 32.51 ns | 0.373 ns | 0.349 ns | 32.56 ns | 0.66 | 0.03 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 199.04 ns | 1.512 ns | 1.180 ns | 199.17 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle3 | 2. AVX | Empty | 384 | 71.20 ns | 2.654 ns | 7.784 ns | 69.60 ns | 0.41 | 0.02 | - | - | - | - | -// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - | +// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 384 | 63.23 ns | 0.569 ns | 0.505 ns | 63.21 ns | 0.32 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 391.28 ns | 5.087 ns | 3.972 ns | 391.22 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle3 | 2. AVX | Empty | 768 | 109.12 ns | 2.149 ns | 2.010 ns | 108.66 ns | 0.28 | 0.01 | - | - | - | - | -// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - | +// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 768 | 106.51 ns | 0.734 ns | 0.613 ns | 106.56 ns | 0.27 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 773.70 ns | 5.516 ns | 4.890 ns | 772.96 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle3 | 2. AVX | Empty | 1536 | 190.41 ns | 1.090 ns | 0.851 ns | 190.38 ns | 0.25 | 0.00 | - | - | - | - | -// | Shuffle3 | 3. SSE | COMPlus_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - | +// | Shuffle3 | 3. SSE | DOTNET_EnableAVX=0 | 1536 | 190.94 ns | 0.985 ns | 0.769 ns | 190.85 ns | 0.25 | 0.00 | - | - | - | - | // 2023-02-21 // ########## @@ -74,18 +74,18 @@ public class Shuffle3Channel // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | // |--------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:| -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 96 | 44.55 ns | 0.564 ns | 0.528 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 96 | 15.46 ns | 0.064 ns | 0.060 ns | 0.35 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 96 | 44.55 ns | 0.564 ns | 0.528 ns | 1.00 | - | - | - | - | +// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 96 | 15.46 ns | 0.064 ns | 0.060 ns | 0.35 | - | - | - | - | // | Shuffle3 | 3. AVX | Empty | 96 | 15.18 ns | 0.056 ns | 0.053 ns | 0.34 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 384 | 155.68 ns | 0.539 ns | 0.504 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 384 | 30.04 ns | 0.100 ns | 0.089 ns | 0.19 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 384 | 155.68 ns | 0.539 ns | 0.504 ns | 1.00 | - | - | - | - | +// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 384 | 30.04 ns | 0.100 ns | 0.089 ns | 0.19 | - | - | - | - | // | Shuffle3 | 3. AVX | Empty | 384 | 29.70 ns | 0.061 ns | 0.054 ns | 0.19 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 768 | 302.76 ns | 1.023 ns | 0.957 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 768 | 50.24 ns | 0.098 ns | 0.092 ns | 0.17 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 768 | 302.76 ns | 1.023 ns | 0.957 ns | 1.00 | - | - | - | - | +// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 768 | 50.24 ns | 0.098 ns | 0.092 ns | 0.17 | - | - | - | - | // | Shuffle3 | 3. AVX | Empty | 768 | 49.28 ns | 0.156 ns | 0.131 ns | 0.16 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1536 | 596.53 ns | 2.675 ns | 2.503 ns | 1.00 | - | - | - | - | -// | Shuffle3 | 2. SSE | COMPlus_EnableAVX=0 | 1536 | 94.09 ns | 0.312 ns | 0.260 ns | 0.16 | - | - | - | - | +// | Shuffle3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1536 | 596.53 ns | 2.675 ns | 2.503 ns | 1.00 | - | - | - | - | +// | Shuffle3 | 2. SSE | DOTNET_EnableAVX=0 | 1536 | 94.09 ns | 0.312 ns | 0.260 ns | 0.16 | - | - | - | - | // | Shuffle3 | 3. AVX | Empty | 1536 | 93.57 ns | 0.196 ns | 0.183 ns | 0.16 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs index 5ade55c73..579e2c54d 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/Shuffle4Slice3Channel.cs @@ -47,45 +47,45 @@ public class Shuffle4Slice3Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|----------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 56.44 ns | 2.843 ns | 8.382 ns | 56.70 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 56.44 ns | 2.843 ns | 8.382 ns | 56.70 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3 | 2. AVX | Empty | 128 | 27.15 ns | 0.556 ns | 0.762 ns | 27.34 ns | 0.41 | 0.03 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 128 | 26.36 ns | 0.321 ns | 0.268 ns | 26.26 ns | 0.38 | 0.02 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 128 | 26.36 ns | 0.321 ns | 0.268 ns | 26.26 ns | 0.38 | 0.02 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 25.85 ns | 0.494 ns | 0.462 ns | 25.84 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 25.85 ns | 0.494 ns | 0.462 ns | 25.84 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 128 | 26.15 ns | 0.113 ns | 0.106 ns | 26.16 ns | 1.01 | 0.02 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 128 | 25.57 ns | 0.078 ns | 0.061 ns | 25.56 ns | 0.99 | 0.02 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 128 | 25.57 ns | 0.078 ns | 0.061 ns | 25.56 ns | 0.99 | 0.02 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 97.47 ns | 0.327 ns | 0.289 ns | 97.35 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 97.47 ns | 0.327 ns | 0.289 ns | 97.35 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3 | 2. AVX | Empty | 256 | 32.61 ns | 0.107 ns | 0.095 ns | 32.62 ns | 0.33 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.21 ns | 0.169 ns | 0.150 ns | 33.15 ns | 0.34 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 256 | 33.21 ns | 0.169 ns | 0.150 ns | 33.15 ns | 0.34 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 52.34 ns | 0.779 ns | 0.729 ns | 51.94 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 52.34 ns | 0.779 ns | 0.729 ns | 51.94 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 256 | 32.16 ns | 0.111 ns | 0.104 ns | 32.16 ns | 0.61 | 0.01 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 256 | 33.61 ns | 0.342 ns | 0.319 ns | 33.62 ns | 0.64 | 0.01 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 256 | 33.61 ns | 0.342 ns | 0.319 ns | 33.62 ns | 0.64 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 210.74 ns | 3.825 ns | 5.956 ns | 207.70 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 210.74 ns | 3.825 ns | 5.956 ns | 207.70 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3 | 2. AVX | Empty | 512 | 51.03 ns | 0.535 ns | 0.501 ns | 51.18 ns | 0.24 | 0.01 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 512 | 66.60 ns | 1.313 ns | 1.613 ns | 65.93 ns | 0.31 | 0.01 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 512 | 66.60 ns | 1.313 ns | 1.613 ns | 65.93 ns | 0.31 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 119.12 ns | 1.905 ns | 1.689 ns | 118.52 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 119.12 ns | 1.905 ns | 1.689 ns | 118.52 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 512 | 50.33 ns | 0.382 ns | 0.339 ns | 50.41 ns | 0.42 | 0.01 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 512 | 49.25 ns | 0.555 ns | 0.492 ns | 49.26 ns | 0.41 | 0.01 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 512 | 49.25 ns | 0.555 ns | 0.492 ns | 49.26 ns | 0.41 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 423.55 ns | 4.891 ns | 4.336 ns | 423.27 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 423.55 ns | 4.891 ns | 4.336 ns | 423.27 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3 | 2. AVX | Empty | 1024 | 77.13 ns | 1.355 ns | 2.264 ns | 76.19 ns | 0.19 | 0.01 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 79.39 ns | 0.103 ns | 0.086 ns | 79.37 ns | 0.19 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 79.39 ns | 0.103 ns | 0.086 ns | 79.37 ns | 0.19 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 226.57 ns | 2.930 ns | 2.598 ns | 226.10 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 226.57 ns | 2.930 ns | 2.598 ns | 226.10 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 1024 | 80.25 ns | 1.647 ns | 2.082 ns | 80.98 ns | 0.35 | 0.01 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 84.99 ns | 1.234 ns | 1.155 ns | 85.60 ns | 0.38 | 0.01 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 84.99 ns | 1.234 ns | 1.155 ns | 85.60 ns | 0.38 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 794.96 ns | 1.735 ns | 1.538 ns | 795.15 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 794.96 ns | 1.735 ns | 1.538 ns | 795.15 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3 | 2. AVX | Empty | 2048 | 128.41 ns | 0.417 ns | 0.390 ns | 128.24 ns | 0.16 | 0.00 | - | - | - | - | -// | Shuffle4Slice3 | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 127.24 ns | 0.294 ns | 0.229 ns | 127.23 ns | 0.16 | 0.00 | - | - | - | - | +// | Shuffle4Slice3 | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 127.24 ns | 0.294 ns | 0.229 ns | 127.23 ns | 0.16 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 382.97 ns | 1.064 ns | 0.831 ns | 382.87 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Slice3FastFallback | 2. AVX | Empty | 2048 | 126.93 ns | 0.382 ns | 0.339 ns | 126.94 ns | 0.33 | 0.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 149.36 ns | 1.875 ns | 1.754 ns | 149.33 ns | 0.39 | 0.00 | - | - | - | - | // 2023-02-21 // ########## @@ -102,42 +102,42 @@ public class Shuffle4Slice3Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | // |--------------------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|------:|------:|------:|----------:| -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 45.59 ns | 0.166 ns | 0.147 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 128 | 15.62 ns | 0.056 ns | 0.052 ns | 0.34 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 45.59 ns | 0.166 ns | 0.147 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 128 | 15.62 ns | 0.056 ns | 0.052 ns | 0.34 | - | - | - | - | // | Shuffle4Slice3 | 3. AVX | Empty | 128 | 16.37 ns | 0.047 ns | 0.040 ns | 0.36 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 13.23 ns | 0.028 ns | 0.026 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 128 | 14.41 ns | 0.013 ns | 0.012 ns | 1.09 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 13.23 ns | 0.028 ns | 0.026 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 128 | 14.41 ns | 0.013 ns | 0.012 ns | 1.09 | - | - | - | - | // | Shuffle4Slice3FastFallback | 3. AVX | Empty | 128 | 14.70 ns | 0.050 ns | 0.047 ns | 1.11 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 85.48 ns | 0.192 ns | 0.179 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 256 | 19.18 ns | 0.230 ns | 0.204 ns | 0.22 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 85.48 ns | 0.192 ns | 0.179 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 256 | 19.18 ns | 0.230 ns | 0.204 ns | 0.22 | - | - | - | - | // | Shuffle4Slice3 | 3. AVX | Empty | 256 | 18.66 ns | 0.017 ns | 0.015 ns | 0.22 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 24.34 ns | 0.078 ns | 0.073 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 256 | 18.58 ns | 0.061 ns | 0.057 ns | 0.76 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 24.34 ns | 0.078 ns | 0.073 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 256 | 18.58 ns | 0.061 ns | 0.057 ns | 0.76 | - | - | - | - | // | Shuffle4Slice3FastFallback | 3. AVX | Empty | 256 | 19.23 ns | 0.018 ns | 0.016 ns | 0.79 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 165.31 ns | 0.742 ns | 0.694 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 512 | 28.10 ns | 0.077 ns | 0.068 ns | 0.17 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 165.31 ns | 0.742 ns | 0.694 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 512 | 28.10 ns | 0.077 ns | 0.068 ns | 0.17 | - | - | - | - | // | Shuffle4Slice3 | 3. AVX | Empty | 512 | 28.99 ns | 0.018 ns | 0.014 ns | 0.18 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 53.45 ns | 0.270 ns | 0.226 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 512 | 27.50 ns | 0.034 ns | 0.028 ns | 0.51 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 53.45 ns | 0.270 ns | 0.226 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 512 | 27.50 ns | 0.034 ns | 0.028 ns | 0.51 | - | - | - | - | // | Shuffle4Slice3FastFallback | 3. AVX | Empty | 512 | 28.76 ns | 0.017 ns | 0.015 ns | 0.54 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 323.87 ns | 0.549 ns | 0.487 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 40.81 ns | 0.056 ns | 0.050 ns | 0.13 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 323.87 ns | 0.549 ns | 0.487 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 40.81 ns | 0.056 ns | 0.050 ns | 0.13 | - | - | - | - | // | Shuffle4Slice3 | 3. AVX | Empty | 1024 | 39.95 ns | 0.075 ns | 0.067 ns | 0.12 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 101.37 ns | 0.080 ns | 0.067 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 40.72 ns | 0.049 ns | 0.041 ns | 0.40 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 101.37 ns | 0.080 ns | 0.067 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 40.72 ns | 0.049 ns | 0.041 ns | 0.40 | - | - | - | - | // | Shuffle4Slice3FastFallback | 3. AVX | Empty | 1024 | 39.78 ns | 0.029 ns | 0.027 ns | 0.39 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3 | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 642.95 ns | 2.067 ns | 1.933 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3 | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 73.19 ns | 0.082 ns | 0.077 ns | 0.11 | - | - | - | - | +// | Shuffle4Slice3 | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 642.95 ns | 2.067 ns | 1.933 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3 | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 73.19 ns | 0.082 ns | 0.077 ns | 0.11 | - | - | - | - | // | Shuffle4Slice3 | 3. AVX | Empty | 2048 | 69.83 ns | 0.319 ns | 0.267 ns | 0.11 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 196.85 ns | 0.238 ns | 0.211 ns | 1.00 | - | - | - | - | -// | Shuffle4Slice3FastFallback | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 72.89 ns | 0.117 ns | 0.098 ns | 0.37 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 196.85 ns | 0.238 ns | 0.211 ns | 1.00 | - | - | - | - | +// | Shuffle4Slice3FastFallback | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 72.89 ns | 0.117 ns | 0.098 ns | 0.37 | - | - | - | - | // | Shuffle4Slice3FastFallback | 3. AVX | Empty | 2048 | 69.59 ns | 0.073 ns | 0.061 ns | 0.35 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs index 911c4e0a5..6a16bb571 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ShuffleByte4Channel.cs @@ -42,25 +42,25 @@ public class ShuffleByte4Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 17.39 ns | 0.187 ns | 0.175 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 128 | 21.72 ns | 0.299 ns | 0.279 ns | 1.25 | 0.02 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 128 | 18.10 ns | 0.346 ns | 0.289 ns | 1.04 | 0.02 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 35.51 ns | 0.711 ns | 0.790 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 256 | 23.90 ns | 0.508 ns | 0.820 ns | 0.69 | 0.02 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 256 | 20.40 ns | 0.133 ns | 0.111 ns | 0.57 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 73.39 ns | 0.310 ns | 0.259 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 512 | 26.10 ns | 0.418 ns | 0.391 ns | 0.36 | 0.01 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 512 | 27.59 ns | 0.556 ns | 0.571 ns | 0.38 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 150.64 ns | 2.903 ns | 2.716 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 1024 | 38.67 ns | 0.801 ns | 1.889 ns | 0.24 | 0.02 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 47.13 ns | 0.948 ns | 1.054 ns | 0.31 | 0.01 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 315.29 ns | 5.206 ns | 6.583 ns | 1.00 | 0.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 2048 | 57.37 ns | 1.152 ns | 1.078 ns | 0.18 | 0.01 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 65.75 ns | 1.198 ns | 1.600 ns | 0.21 | 0.01 | - | - | - | - | // 2023-02-21 // ########## @@ -77,22 +77,22 @@ public class ShuffleByte4Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated | // |---------------- |------------------- |-------------------------------------------------- |------ |----------:|---------:|---------:|------:|--------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 10.76 ns | 0.033 ns | 0.029 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 128 | 11.39 ns | 0.045 ns | 0.040 ns | 1.06 | 0.01 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 10.76 ns | 0.033 ns | 0.029 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 128 | 11.39 ns | 0.045 ns | 0.040 ns | 1.06 | 0.01 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 128 | 14.05 ns | 0.029 ns | 0.024 ns | 1.31 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 32.09 ns | 0.655 ns | 1.000 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 256 | 14.03 ns | 0.047 ns | 0.041 ns | 0.44 | 0.02 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 32.09 ns | 0.655 ns | 1.000 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 256 | 14.03 ns | 0.047 ns | 0.041 ns | 0.44 | 0.02 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 256 | 15.18 ns | 0.052 ns | 0.043 ns | 0.48 | 0.03 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 59.26 ns | 0.084 ns | 0.070 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 512 | 18.80 ns | 0.036 ns | 0.034 ns | 0.32 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 59.26 ns | 0.084 ns | 0.070 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 512 | 18.80 ns | 0.036 ns | 0.034 ns | 0.32 | 0.00 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 512 | 17.69 ns | 0.038 ns | 0.034 ns | 0.30 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 112.48 ns | 0.285 ns | 0.253 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 31.57 ns | 0.041 ns | 0.036 ns | 0.28 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 112.48 ns | 0.285 ns | 0.253 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 31.57 ns | 0.041 ns | 0.036 ns | 0.28 | 0.00 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 1024 | 28.41 ns | 0.068 ns | 0.064 ns | 0.25 | 0.00 | - | - | - | - | // | | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 218.59 ns | 0.303 ns | 0.283 ns | 1.00 | 0.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 53.04 ns | 0.106 ns | 0.099 ns | 0.24 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 218.59 ns | 0.303 ns | 0.283 ns | 1.00 | 0.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 53.04 ns | 0.106 ns | 0.099 ns | 0.24 | 0.00 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 2048 | 34.74 ns | 0.061 ns | 0.054 ns | 0.16 | 0.00 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs b/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs index 5bb3cf916..7cc894486 100644 --- a/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs +++ b/tests/ImageSharp.Benchmarks/Bulk/ShuffleFloat4Channel.cs @@ -42,25 +42,25 @@ public class ShuffleFloat4Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | // |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 63.647 ns | 0.5475 ns | 0.4853 ns | 1.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 128 | 9.818 ns | 0.1457 ns | 0.1292 ns | 0.15 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 128 | 15.267 ns | 0.1005 ns | 0.0940 ns | 0.24 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 125.586 ns | 1.9312 ns | 1.8064 ns | 1.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 256 | 15.878 ns | 0.1983 ns | 0.1758 ns | 0.13 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 256 | 29.170 ns | 0.2925 ns | 0.2442 ns | 0.23 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 263.859 ns | 2.6660 ns | 2.3634 ns | 1.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 512 | 29.452 ns | 0.3334 ns | 0.3118 ns | 0.11 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 512 | 52.912 ns | 0.1932 ns | 0.1713 ns | 0.20 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 495.717 ns | 1.9850 ns | 1.8567 ns | 1.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 1024 | 53.757 ns | 0.3212 ns | 0.2847 ns | 0.11 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 1024 | 107.815 ns | 1.6201 ns | 1.3528 ns | 0.22 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 980.134 ns | 3.7407 ns | 3.1237 ns | 1.00 | - | - | - | - | // | Shuffle4Channel | 2. AVX | Empty | 2048 | 105.120 ns | 0.6140 ns | 0.5443 ns | 0.11 | - | - | - | - | -// | Shuffle4Channel | 3. SSE | COMPlus_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - | +// | Shuffle4Channel | 3. SSE | DOTNET_EnableAVX=0 | 2048 | 216.473 ns | 2.3268 ns | 2.0627 ns | 0.22 | - | - | - | - | // 2023-02-21 // ########## @@ -77,22 +77,22 @@ public class ShuffleFloat4Channel // // | Method | Job | EnvironmentVariables | Count | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Gen 2 | Allocated | // |---------------- |------------------- |-------------------------------------------------- |------ |-----------:|----------:|----------:|------:|------:|------:|------:|----------:| -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 128 | 57.819 ns | 0.2360 ns | 0.1970 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 128 | 11.564 ns | 0.0234 ns | 0.0195 ns | 0.20 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 128 | 57.819 ns | 0.2360 ns | 0.1970 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 128 | 11.564 ns | 0.0234 ns | 0.0195 ns | 0.20 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 128 | 7.770 ns | 0.0696 ns | 0.0617 ns | 0.13 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 256 | 105.282 ns | 0.2713 ns | 0.2405 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 256 | 19.867 ns | 0.0393 ns | 0.0348 ns | 0.19 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 256 | 105.282 ns | 0.2713 ns | 0.2405 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 256 | 19.867 ns | 0.0393 ns | 0.0348 ns | 0.19 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 256 | 17.586 ns | 0.0582 ns | 0.0544 ns | 0.17 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 512 | 200.799 ns | 0.5678 ns | 0.5033 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 512 | 41.137 ns | 0.1524 ns | 0.1351 ns | 0.20 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 512 | 200.799 ns | 0.5678 ns | 0.5033 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 512 | 41.137 ns | 0.1524 ns | 0.1351 ns | 0.20 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 512 | 24.040 ns | 0.0445 ns | 0.0395 ns | 0.12 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 1024 | 401.046 ns | 0.5865 ns | 0.5199 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 1024 | 94.904 ns | 0.4633 ns | 0.4334 ns | 0.24 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 1024 | 401.046 ns | 0.5865 ns | 0.5199 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 1024 | 94.904 ns | 0.4633 ns | 0.4334 ns | 0.24 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 1024 | 68.456 ns | 0.1192 ns | 0.0996 ns | 0.17 | - | - | - | - | // | | | | | | | | | | | | | -// | Shuffle4Channel | 1. No HwIntrinsics | COMPlus_EnableHWIntrinsic=0,COMPlus_FeatureSIMD=0 | 2048 | 772.297 ns | 0.6270 ns | 0.5558 ns | 1.00 | - | - | - | - | -// | Shuffle4Channel | 2. SSE | COMPlus_EnableAVX=0 | 2048 | 184.561 ns | 0.4319 ns | 0.4040 ns | 0.24 | - | - | - | - | +// | Shuffle4Channel | 1. No HwIntrinsics | DOTNET_EnableHWIntrinsic=0,DOTNET_FeatureSIMD=0 | 2048 | 772.297 ns | 0.6270 ns | 0.5558 ns | 1.00 | - | - | - | - | +// | Shuffle4Channel | 2. SSE | DOTNET_EnableAVX=0 | 2048 | 184.561 ns | 0.4319 ns | 0.4040 ns | 0.24 | - | - | - | - | // | Shuffle4Channel | 3. AVX | Empty | 2048 | 133.634 ns | 1.7864 ns | 1.8345 ns | 0.17 | - | - | - | - | diff --git a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs index 92f8917cf..e21d0c76d 100644 --- a/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs +++ b/tests/ImageSharp.Benchmarks/Config.HwIntrinsics.cs @@ -33,24 +33,24 @@ public partial class Config // `FeatureSIMD` ends up impacting all SIMD support(including `System.Numerics`) but not things // like `LZCNT`, `BMI1`, or `BMI2` // `EnableSSE3_4` is a legacy switch that exists for compat and is basically the same as `EnableSSE3` - private const string EnableAES = "COMPlus_EnableAES"; - private const string EnableAVX = "COMPlus_EnableAVX"; - private const string EnableAVX2 = "COMPlus_EnableAVX2"; - private const string EnableBMI1 = "COMPlus_EnableBMI1"; - private const string EnableBMI2 = "COMPlus_EnableBMI2"; - private const string EnableFMA = "COMPlus_EnableFMA"; - private const string EnableHWIntrinsic = "COMPlus_EnableHWIntrinsic"; - private const string EnableLZCNT = "COMPlus_EnableLZCNT"; - private const string EnablePCLMULQDQ = "COMPlus_EnablePCLMULQDQ"; - private const string EnablePOPCNT = "COMPlus_EnablePOPCNT"; - private const string EnableSSE = "COMPlus_EnableSSE"; - private const string EnableSSE2 = "COMPlus_EnableSSE2"; - private const string EnableSSE3 = "COMPlus_EnableSSE3"; - private const string EnableSSE3_4 = "COMPlus_EnableSSE3_4"; - private const string EnableSSE41 = "COMPlus_EnableSSE41"; - private const string EnableSSE42 = "COMPlus_EnableSSE42"; - private const string EnableSSSE3 = "COMPlus_EnableSSSE3"; - private const string FeatureSIMD = "COMPlus_FeatureSIMD"; + private const string EnableAES = "DOTNET_EnableAES"; + private const string EnableAVX = "DOTNET_EnableAVX"; + private const string EnableAVX2 = "DOTNET_EnableAVX2"; + private const string EnableBMI1 = "DOTNET_EnableBMI1"; + private const string EnableBMI2 = "DOTNET_EnableBMI2"; + private const string EnableFMA = "DOTNET_EnableFMA"; + private const string EnableHWIntrinsic = "DOTNET_EnableHWIntrinsic"; + private const string EnableLZCNT = "DOTNET_EnableLZCNT"; + private const string EnablePCLMULQDQ = "DOTNET_EnablePCLMULQDQ"; + private const string EnablePOPCNT = "DOTNET_EnablePOPCNT"; + private const string EnableSSE = "DOTNET_EnableSSE"; + private const string EnableSSE2 = "DOTNET_EnableSSE2"; + private const string EnableSSE3 = "DOTNET_EnableSSE3"; + private const string EnableSSE3_4 = "DOTNET_EnableSSE3_4"; + private const string EnableSSE41 = "DOTNET_EnableSSE41"; + private const string EnableSSE42 = "DOTNET_EnableSSE42"; + private const string EnableSSSE3 = "DOTNET_EnableSSSE3"; + private const string FeatureSIMD = "DOTNET_FeatureSIMD"; public class HwIntrinsics_SSE_AVX : Config { diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 4408159ef..a705b24b2 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -23,7 +23,7 @@ - net8.0 + net8.0;net9.0 diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index b93d01191..832f3d171 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -19,7 +19,7 @@ - net8.0 + net8.0;net9.0 diff --git a/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs b/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs new file mode 100644 index 000000000..95b8d2013 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/GaussianEliminationSolverTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Processing.Processors.Transforms.Linear; + +namespace SixLabors.ImageSharp.Tests.Common; + +public class GaussianEliminationSolverTest +{ + [Theory] + [MemberData(nameof(MatrixTestData))] + public void CanSolve(double[][] matrix, double[] result, double[] expected) + { + GaussianEliminationSolver.Solve(matrix, result); + + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(result[i], expected[i], 4); + } + } + + public static TheoryData MatrixTestData + { + get + { + TheoryData data = []; + { + double[][] matrix = + [ + [2, 3, 4], + [1, 2, 3], + [3, -4, 0], + ]; + double[] result = [6, 4, 10]; + double[] expected = [18 / 11f, -14 / 11f, 18 / 11f]; + data.Add(matrix, result, expected); + } + + { + double[][] matrix = + [ + [1, 4, -1], + [2, 5, 8], + [1, 3, -3], + ]; + double[] result = [4, 15, 1]; + double[] expected = [1, 1, 1]; + data.Add(matrix, result, expected); + } + + { + double[][] matrix = + [ + [-1, 0, 0], + [0, 1, 0], + [0, 0, 1], + ]; + double[] result = [1, 2, 3]; + double[] expected = [-1, 2, 3]; + data.Add(matrix, result, expected); + } + + return data; + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 8b4aa3d70..09cfe2cbe 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -719,7 +719,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester // ImageMagick cannot decode this image. image.DebugSave(provider); image.CompareToReferenceOutput( - ImageComparer.Exact, + ImageComparer.TolerantPercentage(0.0018F), // NET 9+ Uses zlib-ng to decompress, which manages to decode 2 extra pixels. provider, appendPixelTypeToFileName: false); } diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 10492af8a..11b1749ab 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -516,6 +516,21 @@ public class WebpEncoderTests image.VerifyEncoder(provider, "webp", string.Empty, encoder); } + // https://github.com/SixLabors/ImageSharp/issues/2801 + [Theory] + [WithFile(Lossy.Issue2801, PixelTypes.Rgba32)] + public void WebpDecoder_CanDecode_Issue2801(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + WebpEncoder encoder = new() + { + Quality = 100 + }; + + using Image image = provider.GetImage(); + image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.TolerantPercentage(0.0994F)); + } + public static void RunEncodeLossy_WithPeakImage() { TestImageProvider provider = TestImageProvider.File(TestImageLossyFullPath); @@ -531,6 +546,44 @@ public class WebpEncoderTests [Fact] public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic); + [Theory] + [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] + public void CanSave_NonSeekableStream(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + WebpEncoder encoder = new(); + + using MemoryStream seekable = new(); + image.Save(seekable, encoder); + + using MemoryStream memoryStream = new(); + using NonSeekableStream nonSeekable = new(memoryStream); + + image.Save(nonSeekable, encoder); + + Assert.True(seekable.ToArray().SequenceEqual(memoryStream.ToArray())); + } + + [Theory] + [WithFile(TestPatternOpaque, PixelTypes.Rgba32)] + public async Task CanSave_NonSeekableStream_Async(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + WebpEncoder encoder = new(); + + await using MemoryStream seekable = new(); + image.Save(seekable, encoder); + + await using MemoryStream memoryStream = new(); + await using NonSeekableStream nonSeekable = new(memoryStream); + + await image.SaveAsync(nonSeekable, encoder); + + Assert.True(seekable.ToArray().SequenceEqual(memoryStream.ToArray())); + } + private static ImageComparer GetComparer(int quality) { float tolerance = 0.01f; // ~1.0% diff --git a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs index 1803cfddb..390170cfe 100644 --- a/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/ChunkedMemoryStreamTests.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Tests.IO; /// public class ChunkedMemoryStreamTests { + private readonly Random bufferFiller = new(123); + /// /// The default length in bytes of each buffer chunk when allocating large buffers. /// @@ -30,7 +32,7 @@ public class ChunkedMemoryStreamTests [Fact] public void MemoryStream_GetPositionTest_Negative() { - using var ms = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream ms = new(this.allocator); long iCurrentPos = ms.Position; for (int i = -1; i > -6; i--) { @@ -42,7 +44,7 @@ public class ChunkedMemoryStreamTests [Fact] public void MemoryStream_ReadTest_Negative() { - var ms2 = new ChunkedMemoryStream(this.allocator); + ChunkedMemoryStream ms2 = new(this.allocator); Assert.Throws(() => ms2.Read(null, 0, 0)); Assert.Throws(() => ms2.Read(new byte[] { 1 }, -1, 0)); @@ -64,7 +66,7 @@ public class ChunkedMemoryStreamTests public void MemoryStream_ReadByteTest(int length) { using MemoryStream ms = this.CreateTestStream(length); - using var cms = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream cms = new(this.allocator); ms.CopyTo(cms); cms.Position = 0; @@ -85,7 +87,7 @@ public class ChunkedMemoryStreamTests public void MemoryStream_ReadByteBufferTest(int length) { using MemoryStream ms = this.CreateTestStream(length); - using var cms = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream cms = new(this.allocator); ms.CopyTo(cms); cms.Position = 0; @@ -105,10 +107,11 @@ public class ChunkedMemoryStreamTests [InlineData(DefaultSmallChunkSize * 4)] [InlineData((int)(DefaultSmallChunkSize * 5.5))] [InlineData(DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize * 32)] public void MemoryStream_ReadByteBufferSpanTest(int length) { using MemoryStream ms = this.CreateTestStream(length); - using var cms = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream cms = new(this.allocator); ms.CopyTo(cms); cms.Position = 0; @@ -122,18 +125,24 @@ public class ChunkedMemoryStreamTests } } - [Fact] - public void MemoryStream_WriteToTests() + [Theory] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize * 32)] + public void MemoryStream_WriteToTests(int length) { - using (var ms2 = new ChunkedMemoryStream(this.allocator)) + using (ChunkedMemoryStream ms2 = new(this.allocator)) { byte[] bytArrRet; - byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + byte[] bytArr = this.CreateTestBuffer(length); // [] Write to memoryStream, check the memoryStream ms2.Write(bytArr, 0, bytArr.Length); - using var readonlyStream = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream readonlyStream = new(this.allocator); ms2.WriteTo(readonlyStream); readonlyStream.Flush(); readonlyStream.Position = 0; @@ -146,11 +155,11 @@ public class ChunkedMemoryStreamTests } // [] Write to memoryStream, check the memoryStream - using (var ms2 = new ChunkedMemoryStream(this.allocator)) - using (var ms3 = new ChunkedMemoryStream(this.allocator)) + using (ChunkedMemoryStream ms2 = new(this.allocator)) + using (ChunkedMemoryStream ms3 = new(this.allocator)) { byte[] bytArrRet; - byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + byte[] bytArr = this.CreateTestBuffer(length); ms2.Write(bytArr, 0, bytArr.Length); ms2.WriteTo(ms3); @@ -164,21 +173,29 @@ public class ChunkedMemoryStreamTests } } - [Fact] - public void MemoryStream_WriteToSpanTests() + [Theory] + [InlineData(DefaultSmallChunkSize)] + [InlineData((int)(DefaultSmallChunkSize * 1.5))] + [InlineData(DefaultSmallChunkSize * 4)] + [InlineData((int)(DefaultSmallChunkSize * 5.5))] + [InlineData(DefaultSmallChunkSize * 16)] + [InlineData(DefaultSmallChunkSize * 32)] + public void MemoryStream_WriteToSpanTests(int length) { - using (var ms2 = new ChunkedMemoryStream(this.allocator)) + using (ChunkedMemoryStream ms2 = new(this.allocator)) { Span bytArrRet; - Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + Span bytArr = this.CreateTestBuffer(length); // [] Write to memoryStream, check the memoryStream ms2.Write(bytArr, 0, bytArr.Length); - using var readonlyStream = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream readonlyStream = new(this.allocator); ms2.WriteTo(readonlyStream); + readonlyStream.Flush(); readonlyStream.Position = 0; + bytArrRet = new byte[(int)readonlyStream.Length]; readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); for (int i = 0; i < bytArr.Length; i++) @@ -188,13 +205,14 @@ public class ChunkedMemoryStreamTests } // [] Write to memoryStream, check the memoryStream - using (var ms2 = new ChunkedMemoryStream(this.allocator)) - using (var ms3 = new ChunkedMemoryStream(this.allocator)) + using (ChunkedMemoryStream ms2 = new(this.allocator)) + using (ChunkedMemoryStream ms3 = new(this.allocator)) { Span bytArrRet; - Span bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + Span bytArr = this.CreateTestBuffer(length); ms2.Write(bytArr, 0, bytArr.Length); + ms2.WriteTo(ms3); ms3.Position = 0; bytArrRet = new byte[(int)ms3.Length]; @@ -209,37 +227,35 @@ public class ChunkedMemoryStreamTests [Fact] public void MemoryStream_WriteByteTests() { - using (var ms2 = new ChunkedMemoryStream(this.allocator)) - { - byte[] bytArrRet; - byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; + using ChunkedMemoryStream ms2 = new(this.allocator); + byte[] bytArrRet; + byte[] bytArr = new byte[] { byte.MinValue, byte.MaxValue, 1, 2, 3, 4, 5, 6, 128, 250 }; - for (int i = 0; i < bytArr.Length; i++) - { - ms2.WriteByte(bytArr[i]); - } + for (int i = 0; i < bytArr.Length; i++) + { + ms2.WriteByte(bytArr[i]); + } - using var readonlyStream = new ChunkedMemoryStream(this.allocator); - ms2.WriteTo(readonlyStream); - readonlyStream.Flush(); - readonlyStream.Position = 0; - bytArrRet = new byte[(int)readonlyStream.Length]; - readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); - for (int i = 0; i < bytArr.Length; i++) - { - Assert.Equal(bytArr[i], bytArrRet[i]); - } + using ChunkedMemoryStream readonlyStream = new(this.allocator); + ms2.WriteTo(readonlyStream); + readonlyStream.Flush(); + readonlyStream.Position = 0; + bytArrRet = new byte[(int)readonlyStream.Length]; + readonlyStream.Read(bytArrRet, 0, (int)readonlyStream.Length); + for (int i = 0; i < bytArr.Length; i++) + { + Assert.Equal(bytArr[i], bytArrRet[i]); } } [Fact] public void MemoryStream_WriteToTests_Negative() { - using var ms2 = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream ms2 = new(this.allocator); Assert.Throws(() => ms2.WriteTo(null)); ms2.Write(new byte[] { 1 }, 0, 1); - var readonlyStream = new MemoryStream(new byte[1028], false); + MemoryStream readonlyStream = new(new byte[1028], false); Assert.Throws(() => ms2.WriteTo(readonlyStream)); readonlyStream.Dispose(); @@ -286,7 +302,7 @@ public class ChunkedMemoryStreamTests [MemberData(nameof(CopyToData))] public void CopyTo(Stream source, byte[] expected) { - using var destination = new ChunkedMemoryStream(this.allocator); + using ChunkedMemoryStream destination = new(this.allocator); source.CopyTo(destination); Assert.InRange(source.Position, source.Length, int.MaxValue); // Copying the data should have read to the end of the stream or stayed past the end. Assert.Equal(expected, destination.ToArray()); @@ -297,16 +313,16 @@ public class ChunkedMemoryStreamTests IEnumerable allImageFiles = Directory.EnumerateFiles(TestEnvironment.InputImagesDirectoryFullPath, "*.*", SearchOption.AllDirectories) .Where(s => !s.EndsWith("txt", StringComparison.OrdinalIgnoreCase)); - var result = new List(); + List result = new(); foreach (string path in allImageFiles) { - result.Add(path.Substring(TestEnvironment.InputImagesDirectoryFullPath.Length)); + result.Add(path[TestEnvironment.InputImagesDirectoryFullPath.Length..]); } return result; } - public static IEnumerable AllTestImages = GetAllTestImages(); + public static IEnumerable AllTestImages { get; } = GetAllTestImages(); [Theory] [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] @@ -334,40 +350,77 @@ public class ChunkedMemoryStreamTests ((TestImageProvider.FileProvider)provider).FilePath); using FileStream fs = File.OpenRead(fullPath); - using var nonSeekableStream = new NonSeekableStream(fs); + using NonSeekableStream nonSeekableStream = new(fs); + + using Image actual = Image.Load(nonSeekableStream); + + ImageComparer.Exact.VerifySimilarity(expected, actual); + expected.Dispose(); + } + + [Theory] + [WithFileCollection(nameof(AllTestImages), PixelTypes.Rgba32)] + public void EncoderIntegrationTest(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } + + Image expected; + try + { + expected = provider.GetImage(); + } + catch + { + // The image is invalid + return; + } - var actual = Image.Load(nonSeekableStream); + string fullPath = Path.Combine( + TestEnvironment.InputImagesDirectoryFullPath, + ((TestImageProvider.FileProvider)provider).FilePath); + + using MemoryStream ms = new(); + using NonSeekableStream nonSeekableStream = new(ms); + expected.SaveAsWebp(nonSeekableStream); + + using Image actual = Image.Load(nonSeekableStream); ImageComparer.Exact.VerifySimilarity(expected, actual); + expected.Dispose(); } public static IEnumerable CopyToData() { // Stream is positioned @ beginning of data byte[] data1 = new byte[] { 1, 2, 3 }; - var stream1 = new MemoryStream(data1); + MemoryStream stream1 = new(data1); yield return new object[] { stream1, data1 }; // Stream is positioned in the middle of data byte[] data2 = new byte[] { 0xff, 0xf3, 0xf0 }; - var stream2 = new MemoryStream(data2) { Position = 1 }; + MemoryStream stream2 = new(data2) { Position = 1 }; yield return new object[] { stream2, new byte[] { 0xf3, 0xf0 } }; // Stream is positioned after end of data byte[] data3 = data2; - var stream3 = new MemoryStream(data3) { Position = data3.Length + 1 }; + MemoryStream stream3 = new(data3) { Position = data3.Length + 1 }; yield return new object[] { stream3, Array.Empty() }; } - private MemoryStream CreateTestStream(int length) + private byte[] CreateTestBuffer(int length) { byte[] buffer = new byte[length]; - var random = new Random(); - random.NextBytes(buffer); - - return new MemoryStream(buffer); + this.bufferFiller.NextBytes(buffer); + return buffer; } + + private MemoryStream CreateTestStream(int length) + => new(this.CreateTestBuffer(length)); } diff --git a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs b/tests/ImageSharp.Tests/Image/NonSeekableStream.cs index 4b1f6e156..2941490e9 100644 --- a/tests/ImageSharp.Tests/Image/NonSeekableStream.cs +++ b/tests/ImageSharp.Tests/Image/NonSeekableStream.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. namespace SixLabors.ImageSharp.Tests; @@ -14,7 +14,7 @@ internal class NonSeekableStream : Stream public override bool CanSeek => false; - public override bool CanWrite => false; + public override bool CanWrite => this.dataStream.CanWrite; public override bool CanTimeout => this.dataStream.CanTimeout; @@ -91,5 +91,5 @@ internal class NonSeekableStream : Stream => throw new NotSupportedException(); public override void Write(byte[] buffer, int offset, int count) - => throw new NotImplementedException(); + => this.dataStream.Write(buffer, offset, count); } diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 41e6e525f..9af4f41a8 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -12,7 +12,7 @@ - net8.0 + net8.0;net9.0 diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs index 38ac9c18c..6b6db69c1 100644 --- a/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ProjectiveTransformTests.cs @@ -55,6 +55,14 @@ public class ProjectiveTransformTests { TaperSide.Right, TaperCorner.RightOrBottom }, }; + public static readonly TheoryData QuadDistortionData = new() + { + { new PointF(0, 0), new PointF(150, 0), new PointF(150, 150), new PointF(0, 150) }, // source == destination + { new PointF(25, 50), new PointF(210, 25), new PointF(140, 210), new PointF(15, 125) }, // Distortion + { new PointF(-50, -50), new PointF(200, -50), new PointF(200, 200), new PointF(-50, 200) }, // Scaling + { new PointF(150, 0), new PointF(150, 150), new PointF(0, 150), new PointF(0, 0) }, // Rotation + }; + public ProjectiveTransformTests(ITestOutputHelper output) => this.Output = output; [Theory] @@ -93,6 +101,24 @@ public class ProjectiveTransformTests } } + [Theory] + [WithTestPatternImages(nameof(QuadDistortionData), 150, 150, PixelTypes.Rgba32)] + public void Transform_WithQuadDistortion(TestImageProvider provider, PointF topLeft, PointF topRight, PointF bottomRight, PointF bottomLeft) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() + .AppendQuadDistortion(topLeft, topRight, bottomRight, bottomLeft); + + image.Mutate(i => i.Transform(builder)); + + FormattableString testOutputDetails = $"{topLeft}-{topRight}-{bottomRight}-{bottomLeft}"; + image.DebugSave(provider, testOutputDetails); + image.CompareFirstFrameToReferenceOutput(TolerantComparer, provider, testOutputDetails); + } + } + [Theory] [WithSolidFilledImages(100, 100, 0, 0, 255, PixelTypes.Rgba32)] public void RawTransformMatchesDocumentedExample(TestImageProvider provider) @@ -128,11 +154,11 @@ public class ProjectiveTransformTests using (Image image = provider.GetImage()) { #pragma warning disable SA1117 // Parameters should be on same line or separate lines - Matrix4x4 matrix = new( - 0.260987f, -0.434909f, 0, -0.0022184f, - 0.373196f, 0.949882f, 0, -0.000312129f, - 0, 0, 1, 0, - 52, 165, 0, 1); + Matrix4x4 matrix = new( + 0.260987f, -0.434909f, 0, -0.0022184f, + 0.373196f, 0.949882f, 0, -0.000312129f, + 0, 0, 1, 0, + 52, 165, 0, 1); #pragma warning restore SA1117 // Parameters should be on same line or separate lines ProjectiveTransformBuilder builder = new ProjectiveTransformBuilder() diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 13f72a634..4130474b5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -827,6 +827,7 @@ public static class TestImages public const string Issue2257 = "Webp/issues/Issue2257.webp"; public const string Issue2670 = "Webp/issues/Issue2670.webp"; public const string Issue2763 = "Webp/issues/Issue2763.png"; + public const string Issue2801 = "Webp/issues/Issue2801.webp"; } } diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs index 07ad5e8f0..63126dcbc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs +++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs @@ -104,7 +104,7 @@ public static class FeatureTestRunner ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, @@ -148,7 +148,7 @@ public static class FeatureTestRunner ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, @@ -192,7 +192,7 @@ public static class FeatureTestRunner ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, @@ -241,7 +241,7 @@ public static class FeatureTestRunner ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, @@ -288,7 +288,7 @@ public static class FeatureTestRunner ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, @@ -333,7 +333,7 @@ public static class FeatureTestRunner ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, @@ -379,7 +379,7 @@ public static class FeatureTestRunner ProcessStartInfo processStartInfo = new(); if (intrinsic.Key != HwIntrinsics.AllowAll) { - processStartInfo.Environment[$"COMPlus_{intrinsic.Value}"] = "0"; + processStartInfo.Environment[$"DOTNET_{intrinsic.Value}"] = "0"; RemoteExecutor.Invoke( action, diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png new file mode 100644 index 000000000..38c603855 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=-50, Y=-50 ]-PointF [ X=200, Y=-50 ]-PointF [ X=200, Y=200 ]-PointF [ X=-50, Y=200 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:abce6af307a81a8ebac8e502142b00b2615403b5570c8dbe7b6895cfdd1a6d60 +size 66879 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png new file mode 100644 index 000000000..f7ea0d006 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=0, Y=0 ]-PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4cda265a50aa26711efafdbcd947c9a01eff872611df5298920583f9a3d4224 +size 26458 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png new file mode 100644 index 000000000..78c37cc44 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=150, Y=0 ]-PointF [ X=150, Y=150 ]-PointF [ X=0, Y=150 ]-PointF [ X=0, Y=0 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:278a488a858b8eda141493fe00c617eb1f664196853da8341d7e5b7f231ddce4 +size 24645 diff --git a/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png new file mode 100644 index 000000000..b4740828d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/ProjectiveTransformTests/Transform_WithQuadDistortion_Rgba32_TestPattern150x150_PointF [ X=25, Y=50 ]-PointF [ X=210, Y=25 ]-PointF [ X=140, Y=210 ]-PointF [ X=15, Y=125 ].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e03e79e6fab3a9e43041e54640a04c7cc3677709e7d879f9f410cf8afc7547a7 +size 42691 diff --git a/tests/Images/Input/Webp/issues/Issue2801.webp b/tests/Images/Input/Webp/issues/Issue2801.webp new file mode 100644 index 000000000..a3b5fee6e --- /dev/null +++ b/tests/Images/Input/Webp/issues/Issue2801.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e90a0d853ddf70d823d8da44eb6c57081e955b1fb7f436a1fd88ca5e5c75a003 +size 261212