From 7c1a1afc6193f55c6d007490a6167c7f0ebe3aa8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 7 Aug 2021 22:56:02 +0200 Subject: [PATCH 001/228] UniformUnmanagedMemoryPoolMemoryAllocator --- Directory.Build.props | 3 + src/ImageSharp/Advanced/AotCompilerTools.cs | 2 +- src/ImageSharp/Configuration.cs | 2 +- src/ImageSharp/ImageSharp.csproj | 2 +- .../Memory/Allocators/AllocationOptions.cs | 13 +- .../Allocators/AllocationOptionsExtensions.cs | 10 + .../ArrayPoolMemoryAllocator.Buffer{T}.cs | 16 +- ...oolMemoryAllocator.CommonFactoryMethods.cs | 2 +- .../Allocators/ArrayPoolMemoryAllocator.cs | 25 +- .../Memory/Allocators/IManagedByteBuffer.cs | 18 - .../Allocators/Internals/BasicArrayBuffer.cs | 5 +- .../Allocators/Internals/BasicByteBuffer.cs | 20 -- .../Allocators/Internals/Gen2GcCallback.cs | 115 +++++++ .../Allocators/Internals/ManagedBufferBase.cs | 5 +- .../Internals/SharedArrayPoolBuffer{T}.cs | 40 +++ .../UniformUnmanagedMemoryPool.Buffer{T}.cs | 89 +++++ .../Internals/UniformUnmanagedMemoryPool.cs | 323 ++++++++++++++++++ .../Internals/UnmanagedBuffer{T}.cs | 65 ++++ .../Internals/UnmanagedMemoryHandle.cs | 80 +++++ .../Memory/Allocators/MemoryAllocator.cs | 40 ++- .../Allocators/MemoryAllocatorOptions.cs | 57 ++++ .../Allocators/SimpleGcMemoryAllocator.cs | 8 - ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 149 ++++++++ .../Allocators/UnmanagedMemoryAllocator.cs | 32 ++ src/ImageSharp/Memory/Buffer2D{T}.cs | 86 +---- .../MemoryGroup{T}.Consumed.cs | 7 +- .../MemoryGroup{T}.Owned.cs | 74 +++- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 110 +++++- .../Memory/MemoryAllocatorExtensions.cs | 17 - .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../ImageSharp.Benchmarks.csproj | 4 +- .../LoadResizeSaveStressBenchmarks.cs | 25 +- .../LoadResizeSaveStressRunner.cs | 2 +- .../ImageSharp.Tests.ProfilingSandbox.csproj | 1 + .../LoadResizeSaveParallelMemoryStress.cs | 241 ++++++++++--- .../Program.cs | 5 +- .../Formats/GeneralFormatTests.cs | 2 - .../Formats/Jpg/SpectralJpegTests.cs | 4 +- .../PackBitsTiffCompressionTests.cs | 2 +- .../Formats/Tiff/TiffEncoderHeaderTests.cs | 2 +- .../Helpers/ParallelRowIteratorTests.cs | 2 +- .../ImageSharp.Tests/Image/ImageFrameTests.cs | 6 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 6 +- .../ImageSharp.Tests/ImageSharp.Tests.csproj | 3 +- .../ArrayPoolMemoryAllocatorTests.cs | 11 +- .../Memory/Allocators/BufferTestSuite.cs | 61 +--- .../SimpleGcMemoryAllocatorTests.cs | 10 +- .../UniformUnmanagedMemoryPoolTests.Trim.cs | 101 ++++++ .../UniformUnmanagedMemoryPoolTests.cs | 292 ++++++++++++++++ ...niformUnmanagedPoolMemoryAllocatorTests.cs | 289 ++++++++++++++++ .../Allocators/UnmanagedMemoryHandleTests.cs | 184 ++++++++++ .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 2 - .../MemoryGroupTests.Allocate.cs | 121 ++++++- .../DiscontiguousBuffers/MemoryGroupTests.cs | 12 - .../Processors/Dithering/DitherTests.cs | 2 +- .../Processors/Transforms/ResizeTests.cs | 2 +- .../ImageProviders/FileProvider.cs | 10 +- .../TestUtilities/TestEnvironment.cs | 12 +- .../TestUtilities/TestImageExtensions.cs | 29 +- .../TestUtilities/TestMemoryAllocator.cs | 8 +- .../TestUtilities/TestUtils.cs | 3 + 61 files changed, 2484 insertions(+), 387 deletions(-) create mode 100644 src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs delete mode 100644 src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs delete mode 100644 src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs create mode 100644 src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs create mode 100644 src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs create mode 100644 src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs create mode 100644 tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs create mode 100644 tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs create mode 100644 tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs create mode 100644 tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs diff --git a/Directory.Build.props b/Directory.Build.props index b3e18e5a5..4a0fb856d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -26,4 +26,7 @@ true + + $(DefineConstants);NETCORE31COMPATIBLE + diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index be2e964fc..74b8a85b0 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -524,7 +524,7 @@ namespace SixLabors.ImageSharp.Advanced private static void AotCompileMemoryManagers() where TPixel : unmanaged, IPixel { - AotCompileMemoryManager(); + AotCompileMemoryManager(); AotCompileMemoryManager(); } diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 49b7aa79b..0ac4c29ea 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the that is currently in use. /// - public MemoryAllocator MemoryAllocator { get; set; } = ArrayPoolMemoryAllocator.CreateDefault(); + public MemoryAllocator MemoryAllocator { get; set; } = MemoryAllocator.CreateDefault(); /// /// Gets the maximum header size of all the formats. diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 7719b1242..9e8051892 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -13,8 +13,8 @@ Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET Debug;Release;Debug-InnerLoop;Release-InnerLoop + $(DefineConstants);DEBUG - diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs index 3c865f357..72c785532 100644 --- a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs +++ b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs @@ -1,21 +1,30 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; + namespace SixLabors.ImageSharp.Memory { /// /// Options for allocating buffers. /// + [Flags] public enum AllocationOptions { /// /// Indicates that the buffer should just be allocated. /// - None, + None = 0, /// /// Indicates that the allocated buffer should be cleaned following allocation. /// - Clean + Clean = 1, + + /// + /// Affects only group allocations. + /// Indicates that the requested or should be made of contiguous blocks up to . + /// + Contiguous = 2 } } diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs new file mode 100644 index 000000000..d3e5bca6e --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/AllocationOptionsExtensions.cs @@ -0,0 +1,10 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + internal static class AllocationOptionsExtensions + { + public static bool Has(this AllocationOptions options, AllocationOptions flag) => (options & flag) == flag; + } +} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs index 0c35c8828..5200a2793 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs @@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { /// - /// Contains and . + /// Contains . /// public partial class ArrayPoolMemoryAllocator { @@ -87,19 +87,5 @@ namespace SixLabors.ImageSharp.Memory throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); } } - - /// - /// The implementation of . - /// - private sealed class ManagedByteBuffer : Buffer, IManagedByteBuffer - { - public ManagedByteBuffer(byte[] data, int length, ArrayPool sourcePool) - : base(data, length, sourcePool) - { - } - - /// - public byte[] Array => this.Data; - } } } diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs index 8aa1b9063..4b06f537b 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Memory /// This is the default. Should be good for most use cases. /// /// The memory manager. - public static ArrayPoolMemoryAllocator CreateDefault() + public static new ArrayPoolMemoryAllocator CreateDefault() { return new ArrayPoolMemoryAllocator( DefaultMaxPooledBufferSizeInBytes, diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs index a79e042a3..eb34b813f 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs @@ -10,6 +10,7 @@ namespace SixLabors.ImageSharp.Memory /// /// Implements by allocating memory from . /// + [Obsolete("ArrayPoolMemoryAllocator is obsolete. Use MemoryAllocator.CreateDefault() instead.")] public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator { private readonly int maxArraysPerBucketNormalPool; @@ -136,7 +137,7 @@ namespace SixLabors.ImageSharp.Memory byte[] byteArray = pool.Rent(bufferSizeInBytes); var buffer = new Buffer(byteArray, length, pool); - if (options == AllocationOptions.Clean) + if (options.Has(AllocationOptions.Clean)) { buffer.GetSpan().Clear(); } @@ -144,27 +145,7 @@ namespace SixLabors.ImageSharp.Memory return buffer; } - /// - public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - - ArrayPool pool = this.GetArrayPool(length); - byte[] byteArray = pool.Rent(length); - - var buffer = new ManagedByteBuffer(byteArray, length, pool); - if (options == AllocationOptions.Clean) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } - - private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) - { - return maxPoolSizeInBytes / 4; - } + private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) => maxPoolSizeInBytes / 4; [MethodImpl(InliningOptions.ColdPath)] private static void ThrowInvalidAllocationException(int length, int max) => diff --git a/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs b/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs deleted file mode 100644 index 8088a2c47..000000000 --- a/src/ImageSharp/Memory/Allocators/IManagedByteBuffer.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Buffers; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Represents a byte buffer backed by a managed array. Useful for interop with classic .NET API-s. - /// - public interface IManagedByteBuffer : IMemoryOwner - { - /// - /// Gets the managed array backing this buffer instance. - /// - byte[] Array { get; } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs index 3a3c695b2..7dbbabff3 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/BasicArrayBuffer.cs @@ -2,11 +2,12 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; namespace SixLabors.ImageSharp.Memory.Internals { /// - /// Wraps an array as an instance. + /// Wraps an array as an instance. /// /// internal class BasicArrayBuffer : ManagedBufferBase @@ -57,4 +58,4 @@ namespace SixLabors.ImageSharp.Memory.Internals return this.Array; } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs b/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs deleted file mode 100644 index 499a9228c..000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/BasicByteBuffer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Memory.Internals -{ - /// - /// Provides an based on . - /// - internal sealed class BasicByteBuffer : BasicArrayBuffer, IManagedByteBuffer - { - /// - /// Initializes a new instance of the class. - /// - /// The byte array. - internal BasicByteBuffer(byte[] array) - : base(array) - { - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs new file mode 100644 index 000000000..3a0479359 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs @@ -0,0 +1,115 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// Port of BCL internal utility: +// https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs +#if NETCORE31COMPATIBLE +using System; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Schedules a callback roughly every gen 2 GC (you may see a Gen 0 an Gen 1 but only once) + /// (We can fix this by capturing the Gen 2 count at startup and testing, but I mostly don't care) + /// + internal sealed class Gen2GcCallback : CriticalFinalizerObject + { + private readonly Func callback0; + private readonly Func callback1; + private GCHandle weakTargetObj; + + private Gen2GcCallback(Func callback) + { + this.callback0 = callback; + } + + private Gen2GcCallback(Func callback, object targetObj) + { + this.callback1 = callback; + this.weakTargetObj = GCHandle.Alloc(targetObj, GCHandleType.Weak); + } + + ~Gen2GcCallback() + { + if (this.weakTargetObj.IsAllocated) + { + // Check to see if the target object is still alive. + object targetObj = this.weakTargetObj.Target; + if (targetObj == null) + { + // The target object is dead, so this callback object is no longer needed. + this.weakTargetObj.Free(); + return; + } + + // Execute the callback method. + try + { + if (!this.callback1(targetObj)) + { + // If the callback returns false, this callback object is no longer needed. + this.weakTargetObj.Free(); + return; + } + } + catch + { + // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. +#if DEBUG + // Except in DEBUG, as we really shouldn't be hitting any exceptions here. + throw; +#endif + } + } + else + { + // Execute the callback method. + try + { + if (!this.callback0()) + { + // If the callback returns false, this callback object is no longer needed. + return; + } + } + catch + { + // Ensure that we still get a chance to resurrect this object, even if the callback throws an exception. +#if DEBUG + // Except in DEBUG, as we really shouldn't be hitting any exceptions here. + throw; +#endif + } + } + + // Resurrect ourselves by re-registering for finalization. + GC.ReRegisterForFinalize(this); + } + + /// + /// Schedule 'callback' to be called in the next GC. If the callback returns true it is + /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. + /// + public static void Register(Func callback) + { + // Create a unreachable object that remembers the callback function and target object. + _ = new Gen2GcCallback(callback); + } + + /// + /// Schedule 'callback' to be called in the next GC. If the callback returns true it is + /// rescheduled for the next Gen 2 GC. Otherwise the callbacks stop. + /// + /// NOTE: This callback will be kept alive until either the callback function returns false, + /// or the target object dies. + /// + public static void Register(Func callback, object targetObj) + { + // Create a unreachable object that remembers the callback function and target object. + _ = new Gen2GcCallback(callback, targetObj); + } + } +} +#endif diff --git a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs index 3f54e335e..654c78647 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.Buffers; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory.Internals @@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Memory.Internals this.pinHandle = GCHandle.Alloc(this.GetPinnableObject(), GCHandleType.Pinned); } - void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); + void* ptr = Unsafe.Add((void*)this.pinHandle.AddrOfPinnedObject(), elementIndex); return new MemoryHandle(ptr, this.pinHandle); } @@ -42,4 +43,4 @@ namespace SixLabors.ImageSharp.Memory.Internals /// The pinnable . protected abstract object GetPinnableObject(); } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs new file mode 100644 index 000000000..c4fed8426 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal class SharedArrayPoolBuffer : ManagedBufferBase + where T : struct + { + private readonly int lengthInBytes; + private byte[] array; + + public SharedArrayPoolBuffer(int lengthInElements) + { + this.lengthInBytes = lengthInElements * Unsafe.SizeOf(); + this.array = ArrayPool.Shared.Rent(this.lengthInBytes); + } + + ~SharedArrayPoolBuffer() => this.Dispose(false); + + protected override void Dispose(bool disposing) + { + if (this.array == null) + { + return; + } + + ArrayPool.Shared.Return(this.array); + this.array = null; + } + + public override Span GetSpan() => MemoryMarshal.Cast(this.array.AsSpan(0, this.lengthInBytes)); + + protected override object GetPinnableObject() => this.array; + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs new file mode 100644 index 000000000..c315aa548 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -0,0 +1,89 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal partial class UniformUnmanagedMemoryPool + { + public unsafe class Buffer : MemoryManager + where T : struct + { + private UniformUnmanagedMemoryPool pool; + private readonly int length; + + public Buffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) + { + this.pool = pool; + this.BufferHandle = bufferHandle; + this.length = length; + } + + private void* Pointer => (void*)this.BufferHandle.DangerousGetHandle(); + + protected UnmanagedMemoryHandle BufferHandle { get; private set; } + + public override Span GetSpan() => new Span(this.Pointer, this.length); + + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + // Will be released in Unpin + bool unused = false; + this.BufferHandle.DangerousAddRef(ref unused); + + void* pbData = Unsafe.Add(this.Pointer, elementIndex); + return new MemoryHandle(pbData); + } + + /// + public override void Unpin() => this.BufferHandle.DangerousRelease(); + + /// + protected override void Dispose(bool disposing) + { + if (this.pool == null) + { + return; + } + + this.pool.Return(this.BufferHandle); + this.pool = null; + this.BufferHandle = null; + } + + internal void MarkDisposed() + { + this.pool = null; + this.BufferHandle = null; + } + } + + public class FinalizableBuffer : Buffer + where T : struct + { + public FinalizableBuffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) + : base(pool, bufferHandle, length) + { + bufferHandle.AssignedToNewOwner(); + } + + ~FinalizableBuffer() => this.Dispose(false); + + protected override void Dispose(bool disposing) + { + if (!disposing && this.BufferHandle != null) + { + // We need to prevent handle finalization here. + // See comments on UnmanagedMemoryHandle.Resurrect() + this.BufferHandle.Resurrect(); + } + + base.Dispose(disposing); + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs new file mode 100644 index 000000000..c1a2d90bd --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -0,0 +1,323 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Diagnostics; +using System.Threading; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal partial class UniformUnmanagedMemoryPool + { + private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); + + private readonly TrimSettings trimSettings; + private UnmanagedMemoryHandle[] buffers; + private int index; + private Timer trimTimer; + private long lastTrimTimestamp; + + public UniformUnmanagedMemoryPool(int bufferLength, int capacity) + : this(bufferLength, capacity, TrimSettings.Default) + { + } + + public UniformUnmanagedMemoryPool(int bufferLength, int capacity, TrimSettings trimSettings) + { + this.trimSettings = trimSettings; + this.Capacity = capacity; + this.BufferLength = bufferLength; + this.buffers = new UnmanagedMemoryHandle[capacity]; + + if (trimSettings.Enabled) + { + // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds, + // and also invoke it on Gen 2 GC. + // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. + this.trimTimer = new Timer( + s => ((UniformUnmanagedMemoryPool)s)?.Trim(), + this, + this.trimSettings.TrimPeriodMilliseconds / 4, + this.trimSettings.TrimPeriodMilliseconds / 4); + +#if NETCORE31COMPATIBLE + Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); +#endif + } + } + + public int BufferLength { get; } + + public int Capacity { get; } + + public UnmanagedMemoryHandle Rent(AllocationOptions allocationOptions = AllocationOptions.None) + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + + // Avoid taking the lock if the pool is released or is over limit: + if (buffersLocal == null || this.index == buffersLocal.Length) + { + return null; + } + + UnmanagedMemoryHandle array; + + lock (buffersLocal) + { + // Check again after taking the lock: + if (this.buffers == null || this.index == buffersLocal.Length) + { + return null; + } + + array = buffersLocal[this.index]; + buffersLocal[this.index++] = null; + } + + if (array == null) + { + array = new UnmanagedMemoryHandle(this.BufferLength); + } + + if (allocationOptions.Has(AllocationOptions.Clean)) + { + this.GetSpan(array).Clear(); + } + + return array; + } + + public UnmanagedMemoryHandle[] Rent(int bufferCount, AllocationOptions allocationOptions = AllocationOptions.None) + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + + // Avoid taking the lock if the pool is released or is over limit: + if (buffersLocal == null || this.index + bufferCount >= buffersLocal.Length + 1) + { + return null; + } + + UnmanagedMemoryHandle[] result; + lock (buffersLocal) + { + // Check again after taking the lock: + if (this.buffers == null || this.index + bufferCount >= buffersLocal.Length + 1) + { + return null; + } + + result = new UnmanagedMemoryHandle[bufferCount]; + for (int i = 0; i < bufferCount; i++) + { + result[i] = buffersLocal[this.index]; + buffersLocal[this.index++] = null; + } + } + + for (int i = 0; i < result.Length; i++) + { + if (result[i] == null) + { + result[i] = new UnmanagedMemoryHandle(this.BufferLength); + } + + if (allocationOptions.Has(AllocationOptions.Clean)) + { + this.GetSpan(result[i]).Clear(); + } + } + + return result; + } + + public void Return(UnmanagedMemoryHandle buffer) + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + if (buffersLocal == null) + { + buffer.Dispose(); + return; + } + + lock (buffersLocal) + { + // Check again after taking the lock: + if (this.buffers == null) + { + buffer.Dispose(); + return; + } + + if (this.index == 0) + { + ThrowReturnedMoreArraysThanRented(); // DEBUG-only exception + buffer.Dispose(); + return; + } + + this.buffers[--this.index] = buffer; + } + } + + public void Return(Span buffers) + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + if (buffersLocal == null) + { + DisposeAll(buffers); + return; + } + + lock (buffersLocal) + { + // Check again after taking the lock: + if (this.buffers == null) + { + DisposeAll(buffers); + return; + } + + if (this.index - buffers.Length + 1 <= 0) + { + ThrowReturnedMoreArraysThanRented(); + DisposeAll(buffers); + return; + } + + for (int i = buffers.Length - 1; i >= 0; i--) + { + buffersLocal[--this.index] = buffers[i]; + } + } + } + + public void Release() + { + this.trimTimer?.Dispose(); + this.trimTimer = null; + UnmanagedMemoryHandle[] oldBuffers = Interlocked.Exchange(ref this.buffers, null); + DebugGuard.NotNull(oldBuffers, nameof(oldBuffers)); + DisposeAll(oldBuffers); + } + + private static void DisposeAll(Span buffers) + { + foreach (UnmanagedMemoryHandle handle in buffers) + { + handle?.Dispose(); + } + } + + private unsafe Span GetSpan(UnmanagedMemoryHandle h) => + new Span((byte*)h.DangerousGetHandle(), this.BufferLength); + + // This indicates a bug in the library, however Return() might be called from a finalizer, + // therefore we should never throw here in production. + [Conditional("DEBUG")] + private static void ThrowReturnedMoreArraysThanRented() => + throw new InvalidMemoryOperationException("Returned more arrays then rented"); + + private bool Trim() + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; + if (buffersLocal == null) + { + return false; + } + + bool isHighPressure = this.IsHighMemoryPressure(); + + if (isHighPressure) + { + return this.TrimHighPressure(buffersLocal); + } + + long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp; + if (millisecondsSinceLastTrim > this.trimSettings.TrimPeriodMilliseconds) + { + return this.TrimLowPressure(buffersLocal); + } + + return true; + } + + private bool TrimHighPressure(UnmanagedMemoryHandle[] buffersLocal) + { + lock (buffersLocal) + { + if (this.buffers == null) + { + return false; + } + + // Trim all: + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i] != null; i++) + { + buffersLocal[i] = null; + } + } + + return true; + } + + private bool TrimLowPressure(UnmanagedMemoryHandle[] arraysLocal) + { + lock (arraysLocal) + { + if (this.buffers == null) + { + return false; + } + + // Count the arrays in the pool: + int retainedCount = 0; + for (int i = this.index; i < arraysLocal.Length && arraysLocal[i] != null; i++) + { + retainedCount++; + } + + // Trim 'trimRate' of 'retainedCount': + int trimCount = (int)Math.Ceiling(retainedCount * this.trimSettings.Rate); + int trimStart = this.index + retainedCount - 1; + int trimStop = this.index + retainedCount - trimCount; + for (int i = trimStart; i >= trimStop; i--) + { + arraysLocal[i].Dispose(); + arraysLocal[i] = null; + } + + this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; + } + + return true; + } + + private bool IsHighMemoryPressure() + { +#if NETCORE31COMPATIBLE + GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); + return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate; +#else + // We don't have high pressure detection triggering full trimming on other platforms, + // to counterpart this, the maximum pool size is small. + return false; +#endif + } + + public class TrimSettings + { + // Trim half of the retained pool buffers every minute. + public int TrimPeriodMilliseconds { get; set; } = 60_000; + + public float Rate { get; set; } = 0.5f; + + // Be more strict about high pressure threshold than ArrayPool.Shared. + // A 32 bit process can OOM before reaching HighMemoryLoadThresholdBytes. + public float HighPressureThresholdRate { get; set; } = 0.5f; + + public bool Enabled => this.Rate > 0; + + public static TrimSettings Default => new TrimSettings(); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs new file mode 100644 index 000000000..03c504279 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Allocates and provides an implementation giving + /// access to unmanaged buffers allocated by . + /// + /// The element type. + internal sealed unsafe class UnmanagedBuffer : MemoryManager + where T : struct + { + private readonly UnmanagedMemoryHandle bufferHandle; + private readonly int lengthInElements; + + /// + /// Initializes a new instance of the class. + /// + /// The number of elements to allocate. + public UnmanagedBuffer(int lengthInElements) + { + this.lengthInElements = lengthInElements; + this.bufferHandle = new UnmanagedMemoryHandle(lengthInElements * Unsafe.SizeOf()); + } + + private void* Pointer => (void*)this.bufferHandle.DangerousGetHandle(); + + public override Span GetSpan() + => new Span(this.Pointer, this.lengthInElements); + + /// + public override MemoryHandle Pin(int elementIndex = 0) + { + // Will be released in Unpin + bool unused = false; + this.bufferHandle.DangerousAddRef(ref unused); + + void* pbData = Unsafe.Add(this.Pointer, elementIndex); + return new MemoryHandle(pbData); + } + + /// + public override void Unpin() => this.bufferHandle.DangerousRelease(); + + /// + protected override void Dispose(bool disposing) + { + if (this.bufferHandle.IsInvalid) + { + return; + } + + if (disposing) + { + this.bufferHandle.Dispose(); + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs new file mode 100644 index 000000000..216b526b7 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -0,0 +1,80 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal sealed class UnmanagedMemoryHandle : SafeHandle + { + private readonly int lengthInBytes; + private bool resurrected; + + // Track allocations for testing purposes: + private static int totalOutstandingHandles; + + public UnmanagedMemoryHandle(int lengthInBytes) + : base(IntPtr.Zero, true) + { + this.SetHandle(Marshal.AllocHGlobal(lengthInBytes)); + this.lengthInBytes = lengthInBytes; + if (lengthInBytes > 0) + { + GC.AddMemoryPressure(lengthInBytes); + } + + Interlocked.Increment(ref totalOutstandingHandles); + } + + /// + /// Gets a value indicating the total outstanding handle allocations for testing purposes. + /// + internal static int TotalOutstandingHandles => totalOutstandingHandles; + + /// + public override bool IsInvalid => this.handle == IntPtr.Zero; + + protected override bool ReleaseHandle() + { + if (this.IsInvalid) + { + return false; + } + + Marshal.FreeHGlobal(this.handle); + if (this.lengthInBytes > 0) + { + GC.RemoveMemoryPressure(this.lengthInBytes); + } + + this.handle = IntPtr.Zero; + Interlocked.Decrement(ref totalOutstandingHandles); + return true; + } + + /// + /// UnmanagedMemoryHandle's finalizer would release the underlying handle returning the memory to the OS. + /// We want to prevent this when a finalizable owner (buffer or MemoryGroup) is returning the handle to + /// in it's finalizer. + /// Since UnmanagedMemoryHandle is CriticalFinalizable, it is guaranteed that the owner's finalizer is called first. + /// + internal void Resurrect() + { + GC.SuppressFinalize(this); + this.resurrected = true; + } + + internal void AssignedToNewOwner() + { + if (this.resurrected) + { + // The handle has been resurrected + GC.ReRegisterForFinalize(this); + this.resurrected = false; + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index af56b99a0..840e07d1a 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -17,6 +17,21 @@ namespace SixLabors.ImageSharp.Memory /// The length of the largest contiguous buffer that can be handled by this allocator instance. protected internal abstract int GetBufferCapacityInBytes(); + /// + /// Creates a default instance of a optimized for the executing platform. + /// + /// The . + public static MemoryAllocator CreateDefault() => + new UniformUnmanagedMemoryPoolMemoryAllocator(null, null); + + /// + /// Creates the default using the provided options. + /// + /// The . + /// The . + public static MemoryAllocator CreateDefault(MemoryAllocatorOptions options) => + new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes, options.MinimumContiguousBlockBytes); + /// /// Allocates an , holding a of length . /// @@ -29,16 +44,6 @@ namespace SixLabors.ImageSharp.Memory public abstract IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) where T : struct; - /// - /// Allocates an . - /// - /// The requested buffer length. - /// The allocation options. - /// The . - /// When length is zero or negative. - /// When length is over the capacity of the allocator. - public abstract IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None); - /// /// Releases all retained resources not being in use. /// Eg: by resetting array pools and letting GC to free the arrays. @@ -46,5 +51,20 @@ namespace SixLabors.ImageSharp.Memory public virtual void ReleaseRetainedResources() { } + + /// + /// Allocates a . + /// + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The . + /// A new . + /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. + internal virtual MemoryGroup AllocateGroup( + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + where T : struct + => MemoryGroup.Allocate(this, totalLength, bufferAlignment, options); } } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs new file mode 100644 index 000000000..8ea7f6f7b --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Defines options for creating the default . + /// + public class MemoryAllocatorOptions + { + private int? maximumPoolSizeMegabytes; + private int? minimumContiguousBlockBytes; + + /// + /// Gets or sets a value defining the maximum size of the 's internal memory pool + /// in Megabytes. means platform default. + /// + public int? MaximumPoolSizeMegabytes + { + get => this.maximumPoolSizeMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes)); + } + + this.maximumPoolSizeMegabytes = value; + } + } + + /// + /// Gets or sets a value defining the minimum contiguous block size when allocating buffers for + /// , or . + /// means platform default. + /// + /// + /// Overriding this value is useful for interop scenarios + /// ensuring succeeds. + /// + public int? MinimumContiguousBlockBytes + { + get => this.minimumContiguousBlockBytes; + set + { + if (value.HasValue) + { + // It doesn't make sense to set this to small values in practice. + // Defining an arbitrary minimum of 65536. + Guard.MustBeGreaterThanOrEqualTo(value.Value, 65536, nameof(this.MaximumPoolSizeMegabytes)); + } + + this.minimumContiguousBlockBytes = value; + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs index 84494f685..a53ecbc66 100644 --- a/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs @@ -21,13 +21,5 @@ namespace SixLabors.ImageSharp.Memory return new BasicArrayBuffer(new T[length]); } - - /// - public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - - return new BasicByteBuffer(new byte[length]); - } } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs new file mode 100644 index 000000000..51c20519d --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -0,0 +1,149 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocator + { + private const int OneMegabyte = 1 << 20; + private const int DefaultContiguousPoolBlockSizeBytes = 4 * OneMegabyte; + private const int DefaultNonPoolBlockSizeBytes = 32 * OneMegabyte; + private readonly int sharedArrayPoolThresholdInBytes; + private readonly int poolBufferSizeInBytes; + private readonly int poolCapacity; + + private UniformUnmanagedMemoryPool pool; + private readonly UnmanagedMemoryAllocator nonPoolAllocator; + + public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes, int? minimumContiguousBlockBytes) + : this( + minimumContiguousBlockBytes.HasValue ? minimumContiguousBlockBytes.Value : DefaultContiguousPoolBlockSizeBytes, + maxPoolSizeMegabytes.HasValue ? (long)maxPoolSizeMegabytes.Value * OneMegabyte : GetDefaultMaxPoolSizeBytes(), + minimumContiguousBlockBytes.HasValue ? Math.Max(minimumContiguousBlockBytes.Value, DefaultNonPoolBlockSizeBytes) : DefaultNonPoolBlockSizeBytes) + { + } + + public UniformUnmanagedMemoryPoolMemoryAllocator( + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes) + : this( + OneMegabyte, + poolBufferSizeInBytes, + maxPoolSizeInBytes, + unmanagedBufferSizeInBytes) + { + } + + // Internal constructor allowing to change the shared array pool threshold for testing purposes. + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes) + { + this.sharedArrayPoolThresholdInBytes = sharedArrayPoolThresholdInBytes; + this.poolBufferSizeInBytes = poolBufferSizeInBytes; + this.poolCapacity = (int)(maxPoolSizeInBytes / poolBufferSizeInBytes); + this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity); + this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); + } + + /// + protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes; + + /// + public override IMemoryOwner Allocate( + int length, + AllocationOptions options = AllocationOptions.None) + { + Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); + int lengthInBytes = length * Unsafe.SizeOf(); + + if (lengthInBytes <= this.sharedArrayPoolThresholdInBytes) + { + var buffer = new SharedArrayPoolBuffer(length); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + if (lengthInBytes <= this.poolBufferSizeInBytes) + { + UnmanagedMemoryHandle array = this.pool.Rent(options); + if (array != null) + { + return new UniformUnmanagedMemoryPool.FinalizableBuffer(this.pool, array, length); + } + } + + return this.nonPoolAllocator.Allocate(length, options); + } + + /// + internal override MemoryGroup AllocateGroup( + long totalLength, + int bufferAlignment, + AllocationOptions options = AllocationOptions.None) + { + long totalLengthInBytes = totalLength * Unsafe.SizeOf(); + if (totalLengthInBytes <= this.sharedArrayPoolThresholdInBytes) + { + var buffer = new SharedArrayPoolBuffer((int)totalLength); + return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); + } + + if (totalLengthInBytes <= this.poolBufferSizeInBytes) + { + // Optimized path renting single array from the pool + UnmanagedMemoryHandle array = this.pool.Rent(options); + if (array != null) + { + var buffer = new UniformUnmanagedMemoryPool.FinalizableBuffer(this.pool, array, (int)totalLength); + return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); + } + } + + // Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails: + MemoryGroup poolGroup = options.Has(AllocationOptions.Contiguous) ? + null : + MemoryGroup.Allocate(this.pool, totalLength, bufferAlignment, options); + return poolGroup ?? MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); + } + + public override void ReleaseRetainedResources() + { + UniformUnmanagedMemoryPool oldPool = Interlocked.Exchange( + ref this.pool, + new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity)); + oldPool.Release(); + } + + private static long GetDefaultMaxPoolSizeBytes() + { +#if NETCORE31COMPATIBLE + // On .NET Core 3.1+, determine the pool as portion of the total available memory. + // There is a bug in GC.GetGCMemoryInfo() on .NET 5 + 32 bit, making TotalAvailableMemoryBytes unreliable: + // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 + if (Environment.Is64BitProcess || !RuntimeInformation.FrameworkDescription.StartsWith(".NET 5.0")) + { + GCMemoryInfo info = GC.GetGCMemoryInfo(); + return info.TotalAvailableMemoryBytes / 8; + } +#endif + + // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0: + return 128 * OneMegabyte; + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs new file mode 100644 index 000000000..731c8e014 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + internal class UnmanagedMemoryAllocator : MemoryAllocator + { + private readonly int bufferCapacityInBytes; + + public UnmanagedMemoryAllocator(int bufferCapacityInBytes) + { + this.bufferCapacityInBytes = bufferCapacityInBytes; + } + + protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; + + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) + { + var buffer = new UnmanagedBuffer(length); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + } +} diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 21c19f5d5..d97f16285 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -19,8 +19,6 @@ namespace SixLabors.ImageSharp.Memory public sealed class Buffer2D : IDisposable where T : struct { - private Memory cachedMemory = default; - /// /// Initializes a new instance of the class. /// @@ -32,11 +30,6 @@ namespace SixLabors.ImageSharp.Memory this.FastMemoryGroup = memoryGroup; this.Width = width; this.Height = height; - - if (memoryGroup.Count == 1) - { - this.cachedMemory = memoryGroup[0]; - } } /// @@ -89,11 +82,7 @@ namespace SixLabors.ImageSharp.Memory /// /// Disposes the instance /// - public void Dispose() - { - this.FastMemoryGroup.Dispose(); - this.cachedMemory = default; - } + public void Dispose() => this.FastMemoryGroup.Dispose(); /// /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. @@ -111,39 +100,14 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return this.cachedMemory.Length > 0 - ? this.cachedMemory.Span.Slice(y * this.Width, this.Width) - : this.GetRowMemorySlow(y).Span; + return this.GetRowMemoryCore(y).Span; } [MethodImpl(InliningOptions.ShortMethod)] internal ref T GetElementUnsafe(int x, int y) { - if (this.cachedMemory.Length > 0) - { - Span span = this.cachedMemory.Span; - ref T start = ref MemoryMarshal.GetReference(span); - return ref Unsafe.Add(ref start, (y * this.Width) + x); - } - - return ref this.GetElementSlow(x, y); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. - /// This method is intended for internal use only, since it does not use the indirection provided by - /// . - /// - /// The y (row) coordinate. - /// The . - [MethodImpl(InliningOptions.ShortMethod)] - internal Memory GetFastRowMemory(int y) - { - DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return this.cachedMemory.Length > 0 - ? this.cachedMemory.Slice(y * this.Width, this.Width) - : this.GetRowMemorySlow(y); + Span span = this.GetRowMemoryCore(y).Span; + return ref span[x]; } /// @@ -168,11 +132,7 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Span DangerousGetSingleSpan() - { - // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory.Span : this.DangerousGetSingleSpanSlow(); - } + internal Span DangerousGetSingleSpan() => this.FastMemoryGroup.Single().Span; /// /// Gets a to the backing data of if the backing group consists of a single contiguous memory buffer. @@ -183,11 +143,7 @@ namespace SixLabors.ImageSharp.Memory /// Thrown when the backing group is discontiguous. /// [MethodImpl(InliningOptions.ShortMethod)] - internal Memory DangerousGetSingleMemory() - { - // TODO: If we need a public version of this method, we need to cache the non-fast Memory of this.MemoryGroup - return this.cachedMemory.Length != 0 ? this.cachedMemory : this.DangerousGetSingleMemorySlow(); - } + internal Memory DangerousGetSingleMemory() => this.FastMemoryGroup.Single(); /// /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), @@ -195,27 +151,14 @@ namespace SixLabors.ImageSharp.Memory /// internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - bool swap = MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); - SwapOwnData(destination, source, swap); + MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); + SwapOwnData(destination, source); } - [MethodImpl(InliningOptions.ColdPath)] - private Memory GetRowMemorySlow(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); - - [MethodImpl(InliningOptions.ColdPath)] - private Memory DangerousGetSingleMemorySlow() => this.FastMemoryGroup.Single(); - - [MethodImpl(InliningOptions.ColdPath)] - private Span DangerousGetSingleSpanSlow() => this.FastMemoryGroup.Single().Span; - - [MethodImpl(InliningOptions.ColdPath)] - private ref T GetElementSlow(int x, int y) - { - Span span = this.GetRowMemorySlow(y).Span; - return ref span[x]; - } + [MethodImpl(InliningOptions.ShortMethod)] + private Memory GetRowMemoryCore(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); - private static void SwapOwnData(Buffer2D a, Buffer2D b, bool swapCachedMemory) + private static void SwapOwnData(Buffer2D a, Buffer2D b) { Size aSize = a.Size(); Size bSize = b.Size(); @@ -225,13 +168,6 @@ namespace SixLabors.ImageSharp.Memory a.Width = bSize.Width; a.Height = bSize.Height; - - if (swapCachedMemory) - { - Memory aCached = a.cachedMemory; - a.cachedMemory = b.cachedMemory; - b.cachedMemory = aCached; - } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index cc2a2f17c..2e690ce9b 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -50,9 +50,12 @@ namespace SixLabors.ImageSharp.Memory return ((IList>)this.source).GetEnumerator(); } - public override void Dispose() + protected override void Dispose(bool disposing) { - this.View.Invalidate(); + if (disposing) + { + this.View.Invalidate(); + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 35290c109..7c0c3b764 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { @@ -17,6 +18,9 @@ namespace SixLabors.ImageSharp.Memory public sealed class Owned : MemoryGroup, IEnumerable> { private IMemoryOwner[] memoryOwners; + private byte[][] pooledArrays; + private UniformUnmanagedMemoryPool unmanagedMemoryPool; + private UnmanagedMemoryHandle[] pooledHandles; public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) : base(bufferLength, totalLength) @@ -26,6 +30,15 @@ namespace SixLabors.ImageSharp.Memory this.View = new MemoryGroupView(this); } + public Owned(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] pooledArrays, int bufferLength, long totalLength, int sizeOfLastBuffer) + : this(CreateBuffers(pool, pooledArrays, bufferLength, sizeOfLastBuffer), bufferLength, totalLength, true) + { + this.pooledHandles = pooledArrays; + this.unmanagedMemoryPool = pool; + } + + ~Owned() => this.Dispose(false); + public bool Swappable { get; } private bool IsDisposed => this.memoryOwners == null; @@ -49,6 +62,23 @@ namespace SixLabors.ImageSharp.Memory } } + private static IMemoryOwner[] CreateBuffers( + UniformUnmanagedMemoryPool pool, + UnmanagedMemoryHandle[] pooledBuffers, + int bufferLength, + int sizeOfLastBuffer) + { + var result = new IMemoryOwner[pooledBuffers.Length]; + for (int i = 0; i < pooledBuffers.Length - 1; i++) + { + pooledBuffers[i].AssignedToNewOwner(); + result[i] = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[i], bufferLength); + } + + result[result.Length - 1] = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer); + return result; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public override MemoryGroupEnumerator GetEnumerator() @@ -63,7 +93,7 @@ namespace SixLabors.ImageSharp.Memory return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); } - public override void Dispose() + protected override void Dispose(bool disposing) { if (this.IsDisposed) { @@ -72,13 +102,37 @@ namespace SixLabors.ImageSharp.Memory this.View.Invalidate(); - foreach (IMemoryOwner memoryOwner in this.memoryOwners) + if (this.unmanagedMemoryPool != null) { - memoryOwner.Dispose(); + this.unmanagedMemoryPool.Return(this.pooledHandles); + if (!disposing) + { + foreach (UnmanagedMemoryHandle handle in this.pooledHandles) + { + // We need to prevent handle finalization here. + // See comments on UnmanagedMemoryHandle.Resurrect() + handle.Resurrect(); + } + } + + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + ((UniformUnmanagedMemoryPool.Buffer)memoryOwner).MarkDisposed(); + } + } + else if (disposing) + { + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + memoryOwner.Dispose(); + } } this.memoryOwners = null; this.IsValid = false; + this.pooledArrays = null; + this.unmanagedMemoryPool = null; + this.pooledHandles = null; } [MethodImpl(InliningOptions.ShortMethod)] @@ -91,10 +145,7 @@ namespace SixLabors.ImageSharp.Memory } [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowObjectDisposedException() - { - throw new ObjectDisposedException(nameof(MemoryGroup)); - } + private static void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(MemoryGroup)); internal static void SwapContents(Owned a, Owned b) { @@ -104,14 +155,23 @@ namespace SixLabors.ImageSharp.Memory IMemoryOwner[] tempOwners = a.memoryOwners; long tempTotalLength = a.TotalLength; int tempBufferLength = a.BufferLength; + byte[][] tempPooledArrays = a.pooledArrays; + UniformUnmanagedMemoryPool tempUnmangedPool = a.unmanagedMemoryPool; + UnmanagedMemoryHandle[] tempPooledHandles = a.pooledHandles; a.memoryOwners = b.memoryOwners; a.TotalLength = b.TotalLength; a.BufferLength = b.BufferLength; + a.pooledArrays = b.pooledArrays; + a.unmanagedMemoryPool = b.unmanagedMemoryPool; + a.pooledHandles = b.pooledHandles; b.memoryOwners = tempOwners; b.TotalLength = tempTotalLength; b.BufferLength = tempBufferLength; + b.pooledArrays = tempPooledArrays; + b.unmanagedMemoryPool = tempUnmangedPool; + b.pooledHandles = tempPooledHandles; a.View.Invalidate(); b.View.Invalidate(); diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 451a8f7e3..9b00d9cbf 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { @@ -44,7 +45,13 @@ namespace SixLabors.ImageSharp.Memory public abstract Memory this[int index] { get; } /// - public abstract void Dispose(); + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + protected abstract void Dispose(bool disposing); /// public abstract MemoryGroupEnumerator GetEnumerator(); @@ -67,44 +74,47 @@ namespace SixLabors.ImageSharp.Memory /// Creates a new memory group, allocating it's buffers with the provided allocator. /// /// The to use. - /// The total length of the buffer. - /// The expected alignment (eg. to make sure image rows fit into single buffers). + /// The total length of the buffer. + /// The expected alignment (eg. to make sure image rows fit into single buffers). /// The . /// A new . /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. public static MemoryGroup Allocate( MemoryAllocator allocator, - long totalLength, - int bufferAlignment, + long totalLengthInElements, + int bufferAlignmentInElements, AllocationOptions options = AllocationOptions.None) { + int bufferCapacityInBytes = options.Has(AllocationOptions.Contiguous) ? + int.MaxValue : + allocator.GetBufferCapacityInBytes(); Guard.NotNull(allocator, nameof(allocator)); - Guard.MustBeGreaterThanOrEqualTo(totalLength, 0, nameof(totalLength)); - Guard.MustBeGreaterThanOrEqualTo(bufferAlignment, 0, nameof(bufferAlignment)); + Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); - int blockCapacityInElements = allocator.GetBufferCapacityInBytes() / ElementSize; + int blockCapacityInElements = bufferCapacityInBytes / ElementSize; - if (bufferAlignment > blockCapacityInElements) + if (bufferAlignmentInElements > blockCapacityInElements) { throw new InvalidMemoryOperationException( - $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignment}."); + $"The buffer capacity of the provided MemoryAllocator is insufficient for the requested buffer alignment: {bufferAlignmentInElements}."); } - if (totalLength == 0) + if (totalLengthInElements == 0) { var buffers0 = new IMemoryOwner[1] { allocator.Allocate(0, options) }; return new Owned(buffers0, 0, 0, true); } - int numberOfAlignedSegments = blockCapacityInElements / bufferAlignment; - int bufferLength = numberOfAlignedSegments * bufferAlignment; - if (totalLength > 0 && totalLength < bufferLength) + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; + int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; + if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) { - bufferLength = (int)totalLength; + bufferLength = (int)totalLengthInElements; } - int sizeOfLastBuffer = (int)(totalLength % bufferLength); - long bufferCount = totalLength / bufferLength; + int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); + long bufferCount = totalLengthInElements / bufferLength; if (sizeOfLastBuffer == 0) { @@ -126,7 +136,71 @@ namespace SixLabors.ImageSharp.Memory buffers[buffers.Length - 1] = allocator.Allocate(sizeOfLastBuffer, options); } - return new Owned(buffers, bufferLength, totalLength, true); + return new Owned(buffers, bufferLength, totalLengthInElements, true); + } + + public static MemoryGroup CreateContiguous(IMemoryOwner buffer, bool clear) + { + if (clear) + { + buffer.GetSpan().Clear(); + } + + int length = buffer.Memory.Length; + var buffers = new IMemoryOwner[1] { buffer }; + return new Owned(buffers, length, length, true); + } + + public static MemoryGroup Allocate( + UniformUnmanagedMemoryPool pool, + long totalLengthInElements, + int bufferAlignmentInElements, + AllocationOptions options = AllocationOptions.None) + { + Guard.NotNull(pool, nameof(pool)); + Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); + Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); + + int blockCapacityInElements = pool.BufferLength / ElementSize; + + if (bufferAlignmentInElements > blockCapacityInElements) + { + return null; + } + + if (totalLengthInElements == 0) + { + throw new InvalidMemoryOperationException("Allocating 0 length buffer from UniformByteArrayPool is disallowed"); + } + + int numberOfAlignedSegments = blockCapacityInElements / bufferAlignmentInElements; + int bufferLength = numberOfAlignedSegments * bufferAlignmentInElements; + if (totalLengthInElements > 0 && totalLengthInElements < bufferLength) + { + bufferLength = (int)totalLengthInElements; + } + + int sizeOfLastBuffer = (int)(totalLengthInElements % bufferLength); + int bufferCount = (int)(totalLengthInElements / bufferLength); + + if (sizeOfLastBuffer == 0) + { + sizeOfLastBuffer = bufferLength; + } + else + { + bufferCount++; + } + + UnmanagedMemoryHandle[] arrays = pool.Rent(bufferCount, options); + + if (arrays == null) + { + // Pool is full + return null; + } + + return new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer); } public static MemoryGroup Wrap(params Memory[] source) diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 2f70ac05e..9cf1af659 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -83,22 +83,5 @@ namespace SixLabors.ImageSharp.Memory int length = (width * pixelSizeInBytes) + paddingInBytes; return memoryAllocator.Allocate(length); } - - /// - /// Allocates a . - /// - /// The to use. - /// The total length of the buffer. - /// The expected alignment (eg. to make sure image rows fit into single buffers). - /// The . - /// A new . - /// Thrown when 'blockAlignment' converted to bytes is greater than the buffer capacity of the allocator. - internal static MemoryGroup AllocateGroup( - this MemoryAllocator memoryAllocator, - long totalLength, - int bufferAlignment, - AllocationOptions options = AllocationOptions.None) - where T : struct - => MemoryGroup.Allocate(memoryAllocator, totalLength, bufferAlignment, options); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index a58c20f68..220d85250 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.sourceLength = sourceLength; this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean); + this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean | AllocationOptions.Contiguous); this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 84b83ee14..e69a006ed 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -38,9 +38,9 @@ - + - + diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index f1f7de3dc..d3191c695 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -3,6 +3,7 @@ using System; using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave { @@ -16,6 +17,11 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // private const JpegKind Filter = JpegKind.Progressive; private const JpegKind Filter = JpegKind.Any; +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + private ArrayPoolMemoryAllocator arrayPoolMemoryAllocator; +#pragma warning restore CS0618 + private MemoryAllocator defaultMemoryAllocator; + [GlobalSetup] public void Setup() { @@ -26,6 +32,11 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave }; Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); this.runner.Init(); + this.defaultMemoryAllocator = Configuration.Default.MemoryAllocator; + +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + this.arrayPoolMemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); +#pragma warning restore CS0618 } private void ForEachImage(Action action, int maxDegreeOfParallelism) @@ -48,7 +59,19 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave [Benchmark] [ArgumentsSource(nameof(ParallelismValues))] - public void ImageSharp(int maxDegreeOfParallelism) => this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + public void ImageSharp_DefaultMemoryAllocator(int maxDegreeOfParallelism) + { + Configuration.Default.MemoryAllocator = this.defaultMemoryAllocator; + this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + } + + [Benchmark] + [ArgumentsSource(nameof(ParallelismValues))] + public void ImageSharp_ArrayPoolMemoryAllocator(int maxDegreeOfParallelism) + { + Configuration.Default.MemoryAllocator = this.arrayPoolMemoryAllocator; + this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); + } [Benchmark] [ArgumentsSource(nameof(ParallelismValues))] diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index c15f641b4..7bdc64f02 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public int MaxDegreeOfParallelism { get; set; } = -1; - public JpegKind Filter { get; set; } + public JpegKind Filter { get; set; } = JpegKind.Any; private static readonly string[] ProgressiveFiles = { diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 10deb24c6..b26666da6 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -44,6 +44,7 @@ + diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 2aadf02eb..3782e5584 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -3,29 +3,107 @@ using System; using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Runtime; using System.Text; +using System.Threading; +using CommandLine; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { // See ImageSharp.Benchmarks/LoadResizeSave/README.md internal class LoadResizeSaveParallelMemoryStress { - private readonly LoadResizeSaveStressRunner benchmarks; - private LoadResizeSaveParallelMemoryStress() { - this.benchmarks = new LoadResizeSaveStressRunner() + this.Benchmarks = new LoadResizeSaveStressRunner() { // MaxDegreeOfParallelism = 10, // Filter = JpegKind.Baseline }; - this.benchmarks.Init(); + this.Benchmarks.Init(); + } + + public LoadResizeSaveStressRunner Benchmarks { get; } + + public static void Run(string[] args) + { + var options = CommandLineOptions.Parse(args); + + var lrs = new LoadResizeSaveParallelMemoryStress(); + if (options != null) + { + lrs.Benchmarks.MaxDegreeOfParallelism = options.MaxDegreeOfParallelism; + } + + Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); + Stopwatch timer; + + if (options == null || !options.ImageSharp) + { + RunBenchmarkSwitcher(lrs, out timer); + } + else + { + Console.WriteLine("Running ImageSharp with options:"); + Console.WriteLine(options.ToString()); + Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); + timer = Stopwatch.StartNew(); + try + { + for (int i = 0; i < options.RepeatCount; i++) + { + lrs.ImageSharpBenchmarkParallel(); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + + timer.Stop(); + + if (options.ReleaseRetainedResourcesAtEnd) + { + Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); + } + + for (int i = 0; i < options.FinalGcCount; i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + Thread.Sleep(1000); + } + } + + var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels); + Console.WriteLine(stats.GetMarkdown()); + if (options?.FileOutput != null) + { + PrintFileOutput(options.FileOutput, stats); + } + + if (options != null && options.PauseAtEnd) + { + Console.WriteLine("Press ENTER"); + Console.ReadLine(); + } } - private double TotalProcessedMegapixels => this.benchmarks.TotalProcessedMegapixels; + private static void PrintFileOutput(string fileOutput, Stats stats) + { + string[] ss = fileOutput.Split(';'); + string fileName = ss[0]; + string content = ss[1] + .Replace("TotalSeconds", stats.TotalSeconds.ToString(CultureInfo.InvariantCulture)) + .Replace("EOL", Environment.NewLine, StringComparison.OrdinalIgnoreCase); + File.AppendAllText(fileName, content); + } - public static void Run() + private static void RunBenchmarkSwitcher(LoadResizeSaveParallelMemoryStress lrs, out Stopwatch timer) { Console.WriteLine(@"Choose a library for image resizing stress test: @@ -41,48 +119,34 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox if (key < ConsoleKey.D1 || key > ConsoleKey.D6) { Console.WriteLine("Unrecognized command."); - return; + Environment.Exit(-1); } - try - { - var lrs = new LoadResizeSaveParallelMemoryStress(); + timer = Stopwatch.StartNew(); - Console.WriteLine($"\nEnvironment.ProcessorCount={Environment.ProcessorCount}"); - Console.WriteLine($"Running with MaxDegreeOfParallelism={lrs.benchmarks.MaxDegreeOfParallelism} ..."); - var timer = Stopwatch.StartNew(); - - switch (key) - { - case ConsoleKey.D1: - lrs.SystemDrawingBenchmarkParallel(); - break; - case ConsoleKey.D2: - lrs.ImageSharpBenchmarkParallel(); - break; - case ConsoleKey.D3: - lrs.MagicScalerBenchmarkParallel(); - break; - case ConsoleKey.D4: - lrs.SkiaBitmapBenchmarkParallel(); - break; - case ConsoleKey.D5: - lrs.NetVipsBenchmarkParallel(); - break; - case ConsoleKey.D6: - lrs.MagickBenchmarkParallel(); - break; - } - - timer.Stop(); - var stats = new Stats(timer, lrs.TotalProcessedMegapixels); - Console.WriteLine("Done. TotalProcessedMegapixels: " + lrs.TotalProcessedMegapixels); - Console.WriteLine(stats.GetMarkdown()); - } - catch (Exception ex) + switch (key) { - Console.WriteLine(ex.ToString()); + case ConsoleKey.D1: + lrs.SystemDrawingBenchmarkParallel(); + break; + case ConsoleKey.D2: + lrs.ImageSharpBenchmarkParallel(); + break; + case ConsoleKey.D3: + lrs.MagicScalerBenchmarkParallel(); + break; + case ConsoleKey.D4: + lrs.SkiaBitmapBenchmarkParallel(); + break; + case ConsoleKey.D5: + lrs.NetVipsBenchmarkParallel(); + break; + case ConsoleKey.D6: + lrs.MagickBenchmarkParallel(); + break; } + + timer.Stop(); } private struct Stats @@ -125,18 +189,95 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } } - private void ForEachImage(Action action) => this.benchmarks.ForEachImageParallel(action); + private enum AllocatorKind + { + Classic, + Unmanaged + } + + private class CommandLineOptions + { + [Option('i', "imagesharp", Required = false, Default = false)] + public bool ImageSharp { get; set; } + + [Option('a', "allocator", Required = false, Default = AllocatorKind.Unmanaged)] + public AllocatorKind Allocator { get; set; } + + [Option('m', "max-contiguous", Required = false, Default = 4)] + public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; + + [Option('s', "poolsize", Required = false, Default = 4096)] + public int MaxPoolSizeMegaBytes { get; set; } = 4096; + + [Option('u', "max-unmg", Required = false, Default = 32)] + public int MaxCapacityOfUnmanagedBuffersMegaBytes { get; set; } = 32; + + [Option('p', "parallelism", Required = false, Default = -1)] + public int MaxDegreeOfParallelism { get; set; } = -1; + + [Option('r', "repeat-count", Required = false, Default = 1)] + public int RepeatCount { get; set; } = 1; + + // This is to test trimming and virtual memory decommit + [Option('g', "final-gc-count", Required = false, Default = 0)] + public int FinalGcCount { get; set; } + + [Option('e', "release-at-end", Required = false, Default = false)] + public bool ReleaseRetainedResourcesAtEnd { get; set; } + + [Option('w', "pause", Required = false, Default = false)] + public bool PauseAtEnd { get; set; } + + [Option('f', "file", Required = false, Default = null)] + public string FileOutput { get; set; } + + public static CommandLineOptions Parse(string[] args) + { + CommandLineOptions result = null; + Parser.Default.ParseArguments(args).WithParsed(o => + { + result = o; + }); + return result; + } + + public override string ToString() => + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.Allocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfUnmanagedBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; + + public MemoryAllocator CreateMemoryAllocator() + { + switch (this.Allocator) + { + case AllocatorKind.Classic: +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + return ArrayPoolMemoryAllocator.CreateDefault(); +#pragma warning restore CS0618 + case AllocatorKind.Unmanaged: + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes)); + default: + throw new ArgumentOutOfRangeException(); + } + } + + private static long B(double megaBytes) => (long)(megaBytes * 1024 * 1024); + } + + private void ForEachImage(Action action) => this.Benchmarks.ForEachImageParallel(action); - private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.benchmarks.SystemDrawingResize); + private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize); - private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.benchmarks.ImageSharpResize); + private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.Benchmarks.ImageSharpResize); - private void MagickBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagickResize); + private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); - private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.benchmarks.MagicScalerResize); + private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); - private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.benchmarks.SkiaBitmapResize); + private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); - private void NetVipsBenchmarkParallel() => this.ForEachImage(this.benchmarks.NetVipsResize); + private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); } } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index e6e82b981..a1bca9e6a 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Threading; +using SixLabors.ImageSharp.Memory.Internals; using SixLabors.ImageSharp.Tests.Formats.Jpg; using SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations; using SixLabors.ImageSharp.Tests.ProfilingBenchmarks; @@ -31,7 +33,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { - LoadResizeSaveParallelMemoryStress.Run(); + LoadResizeSaveParallelMemoryStress.Run(args); + // TrimPoolTest(); // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index c0843a51b..09513010b 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -98,8 +98,6 @@ namespace SixLabors.ImageSharp.Tests.Formats public void QuantizeImageShouldPreserveMaximumColorPrecision(TestImageProvider provider, string quantizerName) where TPixel : unmanaged, IPixel { - provider.Configuration.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - IQuantizer quantizer = GetQuantizer(quantizerName); using (Image image = provider.GetImage()) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 40b9e6867..aaf0f13f8 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -46,7 +46,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); [Theory(Skip = "Debug only, enable manually!")] - //[Theory] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] public void Decoder_ParseStream_SaveSpectralResult(TestImageProvider provider) where TPixel : unmanaged, IPixel @@ -126,7 +125,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Component{i}: [total: {total} | average: {average}]"); averageDifference += average; totalDifference += total; - tolerance += libJpegComponent.SpectralBlocks.DangerousGetSingleSpan().Length; + Size s = libJpegComponent.SpectralBlocks.Size(); + tolerance += s.Width * s.Height; } averageDifference /= componentCount; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index b67cb8325..066264c33 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); var buffer = new byte[expectedResult.Length]; - using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default); + using var decompressor = new PackBitsTiffCompression(MemoryAllocator.CreateDefault(), default, default); decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer); Assert.Equal(expectedResult, buffer); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs index acbe2b489..3cc52aa1a 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Trait("Format", "Tiff")] public class TiffEncoderHeaderTests { - private static readonly MemoryAllocator MemoryAllocator = new ArrayPoolMemoryAllocator(); + private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.CreateDefault(); private static readonly Configuration Configuration = Configuration.Default; private static readonly ITiffEncoderOptions Options = new TiffEncoder(); diff --git a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs index 7d4f2da42..f856b1b12 100644 --- a/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs +++ b/tests/ImageSharp.Tests/Helpers/ParallelRowIteratorTests.cs @@ -361,7 +361,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers in operation); // Assert: - TestImageExtensions.CompareBuffers(expected.DangerousGetSingleSpan(), actual.DangerousGetSingleSpan()); + TestImageExtensions.CompareBuffers(expected, actual); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index d4aef7538..bbe1a2335 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -16,8 +16,12 @@ namespace SixLabors.ImageSharp.Tests private void LimitBufferCapacity(int bufferCapacityInBytes) { - var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + // TODO: Create a test-only MemoryAllocator for this +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + var allocator = ArrayPoolMemoryAllocator.CreateDefault(); +#pragma warning restore CS0618 allocator.BufferCapacityInBytes = bufferCapacityInBytes; + this.configuration.MemoryAllocator = allocator; } [Theory] diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 1296f26c4..671fc2b90 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -99,8 +99,12 @@ namespace SixLabors.ImageSharp.Tests private void LimitBufferCapacity(int bufferCapacityInBytes) { - var allocator = (ArrayPoolMemoryAllocator)this.configuration.MemoryAllocator; + // TODO: Create a test-only MemoryAllocator for this +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + var allocator = ArrayPoolMemoryAllocator.CreateDefault(); +#pragma warning restore CS0618 allocator.BufferCapacityInBytes = bufferCapacityInBytes; + this.configuration.MemoryAllocator = allocator; } [Theory] diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 30bd544fa..88b6b8813 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -7,6 +7,7 @@ AnyCPU;x64;x86 SixLabors.ImageSharp.Tests Debug;Release;Debug-InnerLoop;Release-InnerLoop + $(DefineConstants);NETCORE31COMPATIBLE @@ -21,7 +22,7 @@ - + diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs index 50ec09ce3..b3e3718ec 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs @@ -11,6 +11,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Memory.Allocators { +#pragma warning disable CS0618 public class ArrayPoolMemoryAllocatorTests { private const int MaxPooledBufferSizeInBytes = 2048; @@ -223,15 +224,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal(0, buffer.Memory.Length); } - [Theory] - [InlineData(-1)] - public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) - { - ArgumentOutOfRangeException ex = Assert.Throws(() => - this.LocalFixture.MemoryAllocator.AllocateManagedByteBuffer(length)); - Assert.Equal("length", ex.ParamName); - } - private class MemoryAllocatorFixture { public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } = @@ -268,4 +260,5 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { } } +#pragma warning restore CS0618 } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs index 1cadf1653..c3c3d40b9 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/BufferTestSuite.cs @@ -96,8 +96,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void CanAllocateCleanBuffer_byte(int desiredLength) { - this.TestCanAllocateCleanBuffer(desiredLength, false); - this.TestCanAllocateCleanBuffer(desiredLength, true); + this.TestCanAllocateCleanBuffer(desiredLength); } [Theory] @@ -114,30 +113,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators this.TestCanAllocateCleanBuffer(desiredLength); } - private IMemoryOwner Allocate(int desiredLength, AllocationOptions options, bool managedByteBuffer) - where T : struct - { - if (managedByteBuffer) - { - if (!(this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength, options) is IMemoryOwner buffer)) - { - throw new InvalidOperationException("typeof(T) != typeof(byte)"); - } - - return buffer; - } - - return this.MemoryAllocator.Allocate(desiredLength, options); - } - - private void TestCanAllocateCleanBuffer(int desiredLength, bool testManagedByteBuffer = false) + private void TestCanAllocateCleanBuffer(int desiredLength) where T : struct, IEquatable { ReadOnlySpan expected = new T[desiredLength]; for (int i = 0; i < 10; i++) { - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.Clean, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.Clean)) { Assert.True(buffer.GetSpan().SequenceEqual(expected)); } @@ -155,14 +138,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void SpanPropertyIsAlwaysTheSame_byte(int desiredLength) { - this.TestSpanPropertyIsAlwaysTheSame(desiredLength, false); - this.TestSpanPropertyIsAlwaysTheSame(desiredLength, true); + this.TestSpanPropertyIsAlwaysTheSame(desiredLength); } - private void TestSpanPropertyIsAlwaysTheSame(int desiredLength, bool testManagedByteBuffer = false) + private void TestSpanPropertyIsAlwaysTheSame(int desiredLength) where T : struct { - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength, AllocationOptions.None)) { ref T a = ref MemoryMarshal.GetReference(buffer.GetSpan()); ref T b = ref MemoryMarshal.GetReference(buffer.GetSpan()); @@ -184,14 +166,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void WriteAndReadElements_byte(int desiredLength) { - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), false); - this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1), true); + this.TestWriteAndReadElements(desiredLength, x => (byte)(x + 1)); } - private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue, bool testManagedByteBuffer = false) + private void TestWriteAndReadElements(int desiredLength, Func getExpectedValue) where T : struct { - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) { var expectedVals = new T[buffer.Length()]; @@ -214,8 +195,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [MemberData(nameof(LengthValues))] public void IndexingSpan_WhenOutOfRange_Throws_byte(int desiredLength) { - this.TestIndexOutOfRangeShouldThrow(desiredLength, false); - this.TestIndexOutOfRangeShouldThrow(desiredLength, true); + this.TestIndexOutOfRangeShouldThrow(desiredLength); } [Theory] @@ -232,12 +212,12 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators this.TestIndexOutOfRangeShouldThrow(desiredLength); } - private T TestIndexOutOfRangeShouldThrow(int desiredLength, bool testManagedByteBuffer = false) + private T TestIndexOutOfRangeShouldThrow(int desiredLength) where T : struct, IEquatable { var dummy = default(T); - using (IMemoryOwner buffer = this.Allocate(desiredLength, AllocationOptions.None, testManagedByteBuffer)) + using (IMemoryOwner buffer = this.MemoryAllocator.Allocate(desiredLength)) { Assert.ThrowsAny( () => @@ -264,23 +244,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators return dummy; } - [Theory] - [InlineData(1)] - [InlineData(7)] - [InlineData(1024)] - [InlineData(6666)] - public void ManagedByteBuffer_ArrayIsCorrect(int desiredLength) - { - using (IManagedByteBuffer buffer = this.MemoryAllocator.AllocateManagedByteBuffer(desiredLength)) - { - ref byte array0 = ref buffer.Array[0]; - ref byte span0 = ref buffer.GetReference(); - - Assert.True(Unsafe.AreSame(ref span0, ref array0)); - Assert.True(buffer.Array.Length >= buffer.GetSpan().Length); - } - } - [Fact] public void GetMemory_ReturnsValidMemory() { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs index 8e7b30567..40d3bfe53 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs @@ -28,17 +28,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal("length", ex.ParamName); } - [Theory] - [InlineData(-1)] - public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) - { - ArgumentOutOfRangeException ex = Assert.Throws(() => this.MemoryAllocator.AllocateManagedByteBuffer(length)); - Assert.Equal("length", ex.ParamName); - } - [StructLayout(LayoutKind.Explicit, Size = 512)] private struct BigStruct { } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs new file mode 100644 index 000000000..9726c9c58 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -0,0 +1,101 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public partial class UniformUnmanagedMemoryPoolTests + { + [CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)] + public class NonParallelTests + { + } + + [Collection(nameof(NonParallelTests))] + public class Trim + { + [Fact] + public void TrimPeriodElapsed_TrimsHalfOfUnusedArrays() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() + { + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 5_000 }; + var pool = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + + UnmanagedMemoryHandle[] a = pool.Rent(64); + UnmanagedMemoryHandle[] b = pool.Rent(64); + pool.Return(a); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + Thread.Sleep(15_000); + + // We expect at least 2 Trim actions, first trim 32, then 16 arrays. + // 128 - 32 - 16 = 80 + Assert.True( + UnmanagedMemoryHandle.TotalOutstandingHandles <= 80, + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80"); + pool.Return(b); + } + } + +#if NETCORE31COMPATIBLE + public static readonly bool Is32BitProcess = !Environment.Is64BitProcess; + private static readonly List PressureArrays = new List(); + + [ConditionalFact(nameof(Is32BitProcess))] + public static void GC_Collect_OnHighLoad_TrimsEntirePool() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() + { + Assert.False(Environment.Is64BitProcess); + const int OneMb = 1024 * 1024; + + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { HighPressureThresholdRate = 0.2f }; + + GCMemoryInfo memInfo = GC.GetGCMemoryInfo(); + int highLoadThreshold = (int)(memInfo.HighMemoryLoadThresholdBytes / OneMb); + highLoadThreshold = (int)(trimSettings.HighPressureThresholdRate * highLoadThreshold); + + var pool = new UniformUnmanagedMemoryPool(OneMb, 16, trimSettings); + pool.Return(pool.Rent(16)); + Assert.Equal(16, UnmanagedMemoryHandle.TotalOutstandingHandles); + + for (int i = 0; i < highLoadThreshold; i++) + { + byte[] array = new byte[OneMb]; + TouchPage(array); + PressureArrays.Add(array); + } + + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); // The pool should be fully trimmed after this point + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + + static void TouchPage(byte[] b) + { + uint size = (uint)b.Length; + const uint pageSize = 4096; + uint numPages = size / pageSize; + + for (uint i = 0; i < numPages; i++) + { + b[i * pageSize] = (byte)(i % 256); + } + } + } + } +#endif + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs new file mode 100644 index 000000000..86bbff181 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -0,0 +1,292 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; +using Xunit.Abstractions; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public partial class UniformUnmanagedMemoryPoolTests + { + private readonly ITestOutputHelper output; + + public UniformUnmanagedMemoryPoolTests(ITestOutputHelper output) + { + this.output = output; + } + + private static unsafe Span GetSpan(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) => + new Span((void*)h.DangerousGetHandle(), pool.BufferLength); + + [Theory] + [InlineData(3, 11)] + [InlineData(7, 4)] + public void Constructor_InitializesProperties(int arrayLength, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(arrayLength, capacity); + Assert.Equal(arrayLength, pool.BufferLength); + Assert.Equal(capacity, pool.Capacity); + } + + [Theory] + [InlineData(1, 3)] + [InlineData(8, 10)] + public void Rent_SingleBuffer_ReturnsCorrectBuffer(int length, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(length, capacity); + for (int i = 0; i < capacity; i++) + { + UnmanagedMemoryHandle h = pool.Rent(); + CheckBuffer(length, pool, h); + } + } + + [Fact] + public void Return_DoesNotDeallocateMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + UnmanagedMemoryHandle a = pool.Rent(); + UnmanagedMemoryHandle[] b = pool.Rent(2); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + pool.Return(a); + pool.Return(b); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + private static void CheckBuffer(int length, UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) + { + Assert.NotNull(h); + Assert.False(h.IsClosed); + Span span = GetSpan(pool, h); + span.Fill(123); + + byte[] expected = new byte[length]; + expected.AsSpan().Fill(123); + Assert.True(span.SequenceEqual(expected)); + } + + [Theory] + [InlineData(1, 1)] + [InlineData(1, 5)] + [InlineData(42, 7)] + [InlineData(5, 10)] + public void Rent_MultiBuffer_ReturnsCorrectBuffers(int length, int bufferCount) + { + var pool = new UniformUnmanagedMemoryPool(length, 10); + UnmanagedMemoryHandle[] handles = pool.Rent(bufferCount); + Assert.NotNull(handles); + Assert.Equal(bufferCount, handles.Length); + + foreach (UnmanagedMemoryHandle h in handles) + { + CheckBuffer(length, pool, h); + } + } + + [Fact] + public void Rent_MultipleTimesWithoutReturn_ReturnsDifferentHandles() + { + var pool = new UniformUnmanagedMemoryPool(128, 10); + UnmanagedMemoryHandle[] a = pool.Rent(2); + UnmanagedMemoryHandle b = pool.Rent(); + + Assert.NotEqual(a[0].DangerousGetHandle(), a[1].DangerousGetHandle()); + Assert.NotEqual(a[0].DangerousGetHandle(), b.DangerousGetHandle()); + Assert.NotEqual(a[1].DangerousGetHandle(), b.DangerousGetHandle()); + } + + [Theory] + [InlineData(4, 2, 10)] + [InlineData(5, 1, 6)] + [InlineData(12, 4, 12)] + public void RentReturnRent_SameBuffers(int totalCount, int rentUnit, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + var allHandles = new HashSet(); + var handleUnits = new List(); + + UnmanagedMemoryHandle[] handles; + for (int i = 0; i < totalCount; i += rentUnit) + { + handles = pool.Rent(rentUnit); + Assert.NotNull(handles); + handleUnits.Add(handles); + foreach (UnmanagedMemoryHandle array in handles) + { + allHandles.Add(array); + } + } + + foreach (UnmanagedMemoryHandle[] arrayUnit in handleUnits) + { + if (arrayUnit.Length == 1) + { + // Test single-array return: + pool.Return(arrayUnit.Single()); + } + else + { + pool.Return(arrayUnit); + } + } + + handles = pool.Rent(totalCount); + + Assert.NotNull(handles); + + foreach (UnmanagedMemoryHandle array in handles) + { + Assert.Contains(array, allHandles); + } + } + + [Fact] + public void Rent_SingleBuffer_OverCapacity_ReturnsNull() + { + var pool = new UniformUnmanagedMemoryPool(7, 1000); + Assert.NotNull(pool.Rent(1000)); + Assert.Null(pool.Rent()); + } + + [Theory] + [InlineData(0, 6, 5)] + [InlineData(5, 1, 5)] + [InlineData(4, 7, 10)] + public void Rent_MultiBuffer_OverCapacity_ReturnsNull(int initialRent, int attempt, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + Assert.NotNull(pool.Rent(initialRent)); + Assert.Null(pool.Rent(attempt)); + } + + [Theory] + [InlineData(0, 5, 5)] + [InlineData(5, 1, 6)] + [InlineData(4, 7, 11)] + [InlineData(3, 3, 7)] + public void Rent_MultiBuff_BelowCapacity_Succeeds(int initialRent, int attempt, int capacity) + { + var pool = new UniformUnmanagedMemoryPool(128, capacity); + Assert.NotNull(pool.Rent(initialRent)); + Assert.NotNull(pool.Rent(attempt)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Release_SubsequentRentReturnsNull(bool multiple) + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + pool.Rent(); // Dummy rent + pool.Release(); + if (multiple) + { + UnmanagedMemoryHandle b = pool.Rent(); + Assert.Null(b); + } + else + { + UnmanagedMemoryHandle[] b = pool.Rent(2); + Assert.Null(b); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Release_SubsequentReturnClosesHandle(bool multiple) + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + if (multiple) + { + UnmanagedMemoryHandle[] b = pool.Rent(2); + pool.Release(); + + Assert.False(b[0].IsClosed); + Assert.False(b[1].IsClosed); + + pool.Return(b); + + Assert.True(b[0].IsClosed); + Assert.True(b[1].IsClosed); + } + else + { + UnmanagedMemoryHandle b = pool.Rent(); + pool.Release(); + Assert.False(b.IsClosed); + pool.Return(b); + Assert.True(b.IsClosed); + } + } + + [Fact] + public void Release_ShouldFreeRetainedMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var pool = new UniformUnmanagedMemoryPool(16, 16); + UnmanagedMemoryHandle a = pool.Rent(); + UnmanagedMemoryHandle[] b = pool.Rent(2); + pool.Return(a); + pool.Return(b); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + pool.Release(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void RentReturn_IsThreadSafe() + { + int count = Environment.ProcessorCount * 200; + var pool = new UniformUnmanagedMemoryPool(8, count); + var rnd = new Random(0); + + Parallel.For(0, Environment.ProcessorCount, (int i) => + { + var allArrays = new List(); + int pauseAt = rnd.Next(100); + for (int j = 0; j < 100; j++) + { + UnmanagedMemoryHandle[] data = pool.Rent(2); + + GetSpan(pool, data[0]).Fill((byte)i); + GetSpan(pool, data[1]).Fill((byte)i); + allArrays.Add(data[0]); + allArrays.Add(data[1]); + + if (j == pauseAt) + { + Thread.Sleep(15); + } + } + + Span expected = new byte[8]; + expected.Fill((byte)i); + + foreach (UnmanagedMemoryHandle array in allArrays) + { + Assert.True(expected.SequenceEqual(GetSpan(pool, array))); + pool.Return(new[] { array }); + } + }); + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs new file mode 100644 index 000000000..a872deef0 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -0,0 +1,289 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class UniformUnmanagedPoolMemoryAllocatorTests + { + public class BufferTests1 : BufferTestSuite + { + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes: 1024, + poolBufferSizeInBytes: 2048, + maxPoolSizeInBytes: 2048 * 4, + unmanagedBufferSizeInBytes: 4096); + + public BufferTests1() + : base(CreateMemoryAllocator()) + { + } + } + + public class BufferTests2 : BufferTestSuite + { + private static MemoryAllocator CreateMemoryAllocator() => + new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes: 512, + poolBufferSizeInBytes: 1024, + maxPoolSizeInBytes: 1024 * 4, + unmanagedBufferSizeInBytes: 2048); + + public BufferTests2() + : base(CreateMemoryAllocator()) + { + } + } + + public static TheoryData AllocateData = + new TheoryData() + { + { default(S4), 16, 256, 256, 1024, 64, 64, 1, -1, 64 }, + { default(S4), 16, 256, 256, 1024, 256, 256, 1, -1, 256 }, + { default(S4), 16, 256, 256, 1024, 250, 256, 1, -1, 250 }, + { default(S4), 16, 256, 256, 1024, 248, 250, 1, -1, 248 }, + { default(S4), 16, 1024, 2048, 4096, 512, 256, 2, 256, 256 }, + { default(S4), 16, 1024, 1024, 1024, 511, 256, 2, 256, 255 }, + }; + + [Theory] + [MemberData(nameof(AllocateData))] + public void AllocateGroup_BufferSizesAreCorrect( + T dummy, + int sharedArrayPoolThresholdInBytes, + int maxContiguousPoolBufferInBytes, + int maxPoolSizeInBytes, + int maxCapacityOfUnmanagedBuffers, + long allocationLengthInElements, + int bufferAlignmentInElements, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedArrayPoolThresholdInBytes, + maxContiguousPoolBufferInBytes, + maxPoolSizeInBytes, + maxCapacityOfUnmanagedBuffers); + + using MemoryGroup g = allocator.AllocateGroup(allocationLengthInElements, bufferAlignmentInElements); + MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup( + expectedNumberOfBuffers, + expectedBufferSize, + expectedSizeOfLastBuffer, + g); + } + + [Fact] + public void AllocateGroup_MultipleTimes_ExceedPoolLimit() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + 64, + 128, + 1024, + 1024); + + var groups = new List>(); + for (int i = 0; i < 16; i++) + { + int lengthInElements = 128 / Unsafe.SizeOf(); + MemoryGroup g = allocator.AllocateGroup(lengthInElements, 32); + MemoryGroupTests.Allocate.ValidateAllocateMemoryGroup(1, -1, lengthInElements, g); + groups.Add(g); + } + + foreach (MemoryGroup g in groups) + { + g.Dispose(); + } + } + + [Theory] + [InlineData(512)] + [InlineData(2048)] + [InlineData(8192)] + [InlineData(65536)] + public void AllocateGroup_OptionsContiguous_AllocatesContiguousBuffer(int lengthInBytes) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + 128, + 1024, + 2048, + 4096); + int length = lengthInBytes / Unsafe.SizeOf(); + using MemoryGroup g = allocator.AllocateGroup(length, 32, AllocationOptions.Contiguous); + Assert.Equal(length, g.BufferLength); + Assert.Equal(length, g.TotalLength); + Assert.Equal(1, g.Count); + } + + [Fact] + public void MemoryAllocator_CreateDefault_WithoutOptions_AllocatesDiscontiguousMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var allocator = MemoryAllocator.CreateDefault(); + long sixteenMegabytes = 16 * (1 << 20); + + // Should allocate 4 times 4MB discontiguos blocks: + MemoryGroup g = allocator.AllocateGroup(sixteenMegabytes, 1024); + Assert.Equal(4, g.Count); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void MemoryAllocator_CreateDefault_WithOptions_CanForceContiguousAllocation(bool poolAllocation) + { + RemoteExecutor.Invoke(RunTest, poolAllocation.ToString()).Dispose(); + + static void RunTest(string poolAllocationStr) + { + int fortyEightMegabytes = 48 * (1 << 20); + var allocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions() + { + MaximumPoolSizeMegabytes = bool.Parse(poolAllocationStr) ? 64 : 0, + MinimumContiguousBlockBytes = fortyEightMegabytes + }); + + MemoryGroup g = allocator.AllocateGroup(fortyEightMegabytes, 1024); + Assert.Equal(1, g.Count); + Assert.Equal(fortyEightMegabytes, g.TotalLength); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void BufferDisposal_ReturnsToPool(bool shared) + { + RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + + static void RunTest(string sharedStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + IMemoryOwner buffer0 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); + buffer0.GetSpan()[0] = 42; + buffer0.Dispose(); + using IMemoryOwner buffer1 = allocator.Allocate(bool.Parse(sharedStr) ? 300 : 600); + Assert.Equal(42, buffer1.GetSpan()[0]); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void MemoryGroupDisposal_ReturnsToPool(bool shared) + { + RemoteExecutor.Invoke(RunTest, shared.ToString()).Dispose(); + + static void RunTest(string sharedStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + MemoryGroup g0 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); + g0.Single().Span[0] = 42; + g0.Dispose(); + using MemoryGroup g1 = allocator.AllocateGroup(bool.Parse(sharedStr) ? 300 : 600, 100); + Assert.Equal(42, g1.Single().Span[0]); + } + } + + [Fact] + public void ReleaseRetainedResources_ShouldFreePooledMemory() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); + MemoryGroup g = allocator.AllocateGroup(2048, 128); + g.Dispose(); + Assert.Equal(4, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void ReleaseRetainedResources_DoesNotFreeOutstandingBuffers() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(128, 512, 16 * 512, 1024); + IMemoryOwner b = allocator.Allocate(256); + MemoryGroup g = allocator.AllocateGroup(2048, 128); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + b.Dispose(); + g.Dispose(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Theory] + [InlineData(300)] + [InlineData(600)] + [InlineData(1200)] + public void MemoryGroupFinalizer_ReturnsToPool(int length) + { + // RunTest(length.ToString()); + RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + + static void RunTest(string lengthStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + int lengthInner = int.Parse(lengthStr); + + AllocateGroupAndForget(allocator, lengthInner); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + AllocateGroupAndForget(allocator, lengthInner, true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + using MemoryGroup g = allocator.AllocateGroup(lengthInner, 100); + Assert.Equal(42, g.First().Span[0]); + } + } + + private static void AllocateGroupAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + { + MemoryGroup g = allocator.AllocateGroup(length, 100); + if (check) + { + Assert.Equal(42, g.First().Span[0]); + } + + g.First().Span[0] = 42; + + if (length < 512) + { + // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, + // repeat rental to make sure per-core buckets are also utilized. + MemoryGroup g1 = allocator.AllocateGroup(length, 100); + g1.First().Span[0] = 42; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs new file mode 100644 index 000000000..5744f81ec --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs @@ -0,0 +1,184 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class UnmanagedMemoryHandleTests + { + [Fact] + public unsafe void Constructor_AllocatesReadWriteMemory() + { + using var h = new UnmanagedMemoryHandle(128); + Assert.False(h.IsClosed); + Assert.False(h.IsInvalid); + byte* ptr = (byte*)h.DangerousGetHandle(); + for (int i = 0; i < 128; i++) + { + ptr[i] = (byte)i; + } + + for (int i = 0; i < 128; i++) + { + Assert.Equal((byte)i, ptr[i]); + } + } + + [Fact] + public void Dispose_ClosesHandle() + { + var h = new UnmanagedMemoryHandle(128); + h.Dispose(); + Assert.True(h.IsClosed); + Assert.True(h.IsInvalid); + } + + [Theory] + [InlineData(1)] + [InlineData(13)] + public void CreateDispose_TracksAllocations(int count) + { + RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + + static void RunTest(string countStr) + { + int countInner = int.Parse(countStr); + var l = new List(); + for (int i = 0; i < countInner; i++) + { + Assert.Equal(i, UnmanagedMemoryHandle.TotalOutstandingHandles); + var h = new UnmanagedMemoryHandle(42); + Assert.Equal(i + 1, UnmanagedMemoryHandle.TotalOutstandingHandles); + l.Add(h); + } + + for (int i = 0; i < countInner; i++) + { + Assert.Equal(countInner - i, UnmanagedMemoryHandle.TotalOutstandingHandles); + l[i].Dispose(); + Assert.Equal(countInner - i - 1, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + } + + [Theory] + [InlineData(2)] + [InlineData(12)] + public void CreateFinalize_TracksAllocations(int count) + { + RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + + static void RunTest(string countStr) + { + int countInner = int.Parse(countStr); + List l = FillList(countInner); + + l.RemoveRange(0, l.Count / 2); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements + Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + + static List FillList(int countInner) + { + var l = new List(); + for (int i = 0; i < countInner; i++) + { + var h = new UnmanagedMemoryHandle(42); + l.Add(h); + } + + return l; + } + } + + [Fact] + public void Resurrect_PreventsFinalization() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + AllocateResurrect(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + + static void AllocateResurrect() + { + var h = new UnmanagedMemoryHandle(42); + h.Resurrect(); + } + } + + private static UnmanagedMemoryHandle resurrectedHandle; + + private class HandleOwner + { + private UnmanagedMemoryHandle handle; + + public HandleOwner(UnmanagedMemoryHandle handle) => this.handle = handle; + + ~HandleOwner() + { + resurrectedHandle = this.handle; + this.handle.Resurrect(); + } + } + + [Fact] + public void AssignedToNewOwner_ReRegistersForFinalization() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + AllocateAndForget(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + GC.Collect(); + GC.WaitForPendingFinalizers(); + VerifyResurrectedHandle(true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + VerifyResurrectedHandle(false); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + + static void AllocateAndForget() + { + _ = new HandleOwner(new UnmanagedMemoryHandle(42)); + } + + static void VerifyResurrectedHandle(bool reAssign) + { + Assert.NotNull(resurrectedHandle); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + Assert.False(resurrectedHandle.IsClosed); + Assert.False(resurrectedHandle.IsInvalid); + resurrectedHandle.AssignedToNewOwner(); + if (reAssign) + { + _ = new HandleOwner(resurrectedHandle); + } + + resurrectedHandle = null; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 015b3617b..23e0ba5f1 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -227,13 +227,11 @@ namespace SixLabors.ImageSharp.Tests.Memory int actual1 = dest.GetRowSpan(0)[0]; int actual2 = dest.GetRowSpan(0)[0]; int actual3 = dest.GetSafeRowMemory(0).Span[0]; - int actual4 = dest.GetFastRowMemory(0).Span[0]; int actual5 = dest[0, 0]; Assert.Equal(1, actual1); Assert.Equal(1, actual2); Assert.Equal(1, actual3); - Assert.Equal(1, actual4); Assert.Equal(1, actual5); } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index e9094fcca..07b99584d 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -2,10 +2,15 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; using Xunit; +using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { @@ -39,6 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { default(S4), 50, 7, 21, 3, 7, 7 }, { default(S4), 50, 7, 23, 4, 7, 2 }, { default(S4), 50, 6, 13, 2, 12, 1 }, + { default(S4), 1024, 20, 800, 4, 240, 80 }, { default(short), 200, 50, 49, 1, 49, 49 }, { default(short), 200, 50, 1, 1, 1, 1 }, @@ -47,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [Theory] [MemberData(nameof(AllocateData))] - public void BufferSizesAreCorrect( + public void Allocate_FromMemoryAllocator_BufferSizesAreCorrect( T dummy, int bufferCapacity, int bufferAlignment, @@ -63,6 +69,94 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers using var g = MemoryGroup.Allocate(this.MemoryAllocator, totalLength, bufferAlignment); // Assert: + ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); + } + + [Theory] + [MemberData(nameof(AllocateData))] + public void Allocate_FromPool_BufferSizesAreCorrect( + T dummy, + int bufferCapacity, + int bufferAlignment, + long totalLength, + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer) + where T : struct + { + if (totalLength == 0) + { + // Invalid case for UniformByteArrayPool allocations + return; + } + + var pool = new UniformUnmanagedMemoryPool(bufferCapacity, expectedNumberOfBuffers); + + // Act: + using var g = MemoryGroup.Allocate(pool, totalLength, bufferAlignment); + + // Assert: + ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); + } + + private static unsafe Span GetSpan(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) => + new Span((void*)h.DangerousGetHandle(), pool.BufferLength); + + [Theory] + [InlineData(AllocationOptions.None)] + [InlineData(AllocationOptions.Clean)] + public void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) + { + var pool = new UniformUnmanagedMemoryPool(10, 5); + UnmanagedMemoryHandle[] buffers = pool.Rent(5); + foreach (UnmanagedMemoryHandle b in buffers) + { + GetSpan(pool, b).Fill(42); + } + + pool.Return(buffers); + + using var g = MemoryGroup.Allocate(pool, 50, 10, options); + Span expected = stackalloc byte[10]; + expected.Fill((byte)(options == AllocationOptions.Clean ? 0 : 42)); + foreach (Memory memory in g) + { + Assert.True(expected.SequenceEqual(memory.Span)); + } + } + + [Theory] + [InlineData(64, 4, 60, 240, false)] + [InlineData(64, 4, 60, 244, true)] + public void Allocate_FromPool_AroundLimit( + int bufferCapacityBytes, + int poolCapacity, + int alignmentBytes, + int requestBytes, + bool shouldReturnNull) + { + var pool = new UniformUnmanagedMemoryPool(bufferCapacityBytes, poolCapacity); + int alignmentElements = alignmentBytes / Unsafe.SizeOf(); + int requestElements = requestBytes / Unsafe.SizeOf(); + + using var g = MemoryGroup.Allocate(pool, requestElements, alignmentElements); + if (shouldReturnNull) + { + Assert.Null(g); + } + else + { + Assert.NotNull(g); + } + } + + internal static void ValidateAllocateMemoryGroup( + int expectedNumberOfBuffers, + int expectedBufferSize, + int expectedSizeOfLastBuffer, + MemoryGroup g) + where T : struct + { Assert.Equal(expectedNumberOfBuffers, g.Count); if (expectedBufferSize >= 0) @@ -123,6 +217,31 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); } + + [Theory] + [InlineData(128)] + [InlineData(1024)] + public void Allocate_OptionsContiguous_AllocatesContiguousBuffer(int lengthInBytes) + { + this.MemoryAllocator.BufferCapacityInBytes = 256; + int length = lengthInBytes / Unsafe.SizeOf(); + using var g = MemoryGroup.Allocate(this.MemoryAllocator, length, 32, AllocationOptions.Contiguous); + Assert.Equal(length, g.BufferLength); + Assert.Equal(length, g.TotalLength); + Assert.Equal(1, g.Count); + } } } + + [StructLayout(LayoutKind.Sequential, Size = 5)] + internal struct S5 + { + public override string ToString() => "S5"; + } + + [StructLayout(LayoutKind.Sequential, Size = 4)] + internal struct S4 + { + public override string ToString() => "S4"; + } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index 3ab5797dd..a93dbbeb3 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -229,17 +229,5 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers target[k] = source[k] * 2; } } - - [StructLayout(LayoutKind.Sequential, Size = 5)] - private struct S5 - { - public override string ToString() => "S5"; - } - - [StructLayout(LayoutKind.Sequential, Size = 4)] - private struct S4 - { - public override string ToString() => "S4"; - } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs index 0465cae94..7ae85c197 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs @@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering /// but it is very different because of floating point inaccuracies. /// private static readonly bool SkipAllDitherTests = - !TestEnvironment.Is64BitProcess && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion); + !TestEnvironment.Is64BitProcess && TestEnvironment.NetCoreVersion == null; [Theory] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 42cf1e3c1..93e68a3e0 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -342,7 +342,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms // Resize_WorksWithAllResamplers_TestPattern301x1180_NearestNeighbor-300x480.png // TODO: Should we investigate this? bool allowHigherInaccuracy = !TestEnvironment.Is64BitProcess - && string.IsNullOrEmpty(TestEnvironment.NetCoreVersion) + && TestEnvironment.NetCoreVersion == null && sampler is NearestNeighborResampler; var comparer = ImageComparer.TolerantPercentage(allowHigherInaccuracy ? 0.3f : 0.017f); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index f57c19f12..4b2736009 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -8,6 +8,7 @@ using System.IO; using System.Reflection; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using Xunit.Abstractions; @@ -157,7 +158,14 @@ namespace SixLabors.ImageSharp.Tests return this.LoadImage(decoder); } - int bufferCapacity = this.Configuration.MemoryAllocator.GetBufferCapacityInBytes(); + int bufferCapacity = -1; +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + if (this.Configuration.MemoryAllocator is ArrayPoolMemoryAllocator arrayPoolMemoryAllocator) +#pragma warning restore CS0618 + { + bufferCapacity = arrayPoolMemoryAllocator.BufferCapacityInBytes; + } + var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index b14c2bf78..00633b959 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -24,14 +24,14 @@ namespace SixLabors.ImageSharp.Tests private static readonly Lazy SolutionDirectoryFullPathLazy = new Lazy(GetSolutionDirectoryFullPathImpl); - private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); + private static readonly Lazy NetCoreVersionLazy = new Lazy(GetNetCoreVersion); static TestEnvironment() => PrepareRemoteExecutor(); /// /// Gets the .NET Core version, if running on .NET Core, otherwise returns an empty string. /// - internal static string NetCoreVersion => NetCoreVersionLazy.Value; + internal static Version NetCoreVersion => NetCoreVersionLazy.Value; // ReSharper disable once InconsistentNaming @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests internal static bool Is64BitProcess => IntPtr.Size == 8; - internal static bool IsFramework => string.IsNullOrEmpty(NetCoreVersion); + internal static bool IsFramework => NetCoreVersion == null; /// /// A dummy operation to enforce the execution of the static constructor. @@ -262,17 +262,17 @@ namespace SixLabors.ImageSharp.Tests /// Solution borrowed from: /// https://github.com/dotnet/BenchmarkDotNet/issues/448#issuecomment-308424100 /// - private static string GetNetCoreVersion() + private static Version GetNetCoreVersion() { Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) { - return assemblyPath[netCoreAppIndex + 1]; + return Version.Parse(assemblyPath[netCoreAppIndex + 1]); } - return string.Empty; + return null; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 26378796b..333807079 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -416,6 +416,27 @@ namespace SixLabors.ImageSharp.Tests } } + public static void CompareBuffers(Buffer2D expected, Buffer2D actual) + where T : struct, IEquatable + { + Assert.True(expected.Size() == actual.Size(), "Buffer sizes are not equal!"); + + for (int y = 0; y < expected.Height; y++) + { + Span expectedRow = expected.GetRowSpan(y); + Span actualRow = actual.GetRowSpan(y); + for (int x = 0; x < expectedRow.Length; x++) + { + T expectedVal = expectedRow[x]; + T actualVal = actualRow[x]; + + Assert.True( + expectedVal.Equals(actualVal), + $"Buffers differ at position ({x},{y})! Expected: {expectedVal} | Actual: {actualVal}"); + } + } + } + /// /// All pixels in all frames should be exactly equal to 'expectedPixel'. /// @@ -663,7 +684,11 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider) where TPixel : unmanaged, IPixel { - var allocator = (ArrayPoolMemoryAllocator)provider.Configuration.MemoryAllocator; + // TODO: Use a test-only allocator for this. +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete + var allocator = ArrayPoolMemoryAllocator.CreateDefault(); +#pragma warning restore + provider.Configuration.MemoryAllocator = allocator; return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); } @@ -746,6 +771,7 @@ namespace SixLabors.ImageSharp.Tests internal class AllocatorBufferCapacityConfigurator { +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete private readonly ArrayPoolMemoryAllocator allocator; private readonly int pixelSizeInBytes; @@ -754,6 +780,7 @@ namespace SixLabors.ImageSharp.Tests this.allocator = allocator; this.pixelSizeInBytes = pixelSizeInBytes; } +#pragma warning restore CS0618 public void InBytes(int totalBytes) => this.allocator.BufferCapacityInBytes = totalBytes; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index ab9611d2f..89bc787bf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -40,12 +40,6 @@ namespace SixLabors.ImageSharp.Tests.Memory return new BasicArrayBuffer(array, length, this); } - public override IManagedByteBuffer AllocateManagedByteBuffer(int length, AllocationOptions options = AllocationOptions.None) - { - byte[] array = this.AllocateArray(length, options); - return new ManagedByteBuffer(array, this); - } - private T[] AllocateArray(int length, AllocationOptions options) where T : struct { @@ -171,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests.Memory } } - private class ManagedByteBuffer : BasicArrayBuffer, IManagedByteBuffer + private class ManagedByteBuffer : BasicArrayBuffer, IMemoryOwner { public ManagedByteBuffer(byte[] array, TestMemoryAllocator allocator) : base(array, allocator) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 32b5eaf18..8c03e8deb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -165,7 +165,10 @@ namespace SixLabors.ImageSharp.Tests int width = expected.Width; expected.Mutate(process); + // TODO: Use a test-only allocator for this +#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete var allocator = ArrayPoolMemoryAllocator.CreateDefault(); +#pragma warning restore CS0618 provider.Configuration.MemoryAllocator = allocator; allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); From ca17d33ea3d1b6cbd3491d28440b2e09215ab728 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 7 Aug 2021 23:17:40 +0200 Subject: [PATCH 002/228] use WeakReference with timer, configure trim period for sandbox --- .../Internals/UniformUnmanagedMemoryPool.cs | 15 ++++++++++--- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 22 ++++++++++++++++--- .../LoadResizeSaveParallelMemoryStress.cs | 10 ++++++++- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index c1a2d90bd..c3e41cf7e 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -34,9 +34,10 @@ namespace SixLabors.ImageSharp.Memory.Internals // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds, // and also invoke it on Gen 2 GC. // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. + var weakPoolRef = new WeakReference(this); this.trimTimer = new Timer( - s => ((UniformUnmanagedMemoryPool)s)?.Trim(), - this, + s => TimerCallback((WeakReference)s), + weakPoolRef, this.trimSettings.TrimPeriodMilliseconds / 4, this.trimSettings.TrimPeriodMilliseconds / 4); @@ -217,6 +218,14 @@ namespace SixLabors.ImageSharp.Memory.Internals private static void ThrowReturnedMoreArraysThanRented() => throw new InvalidMemoryOperationException("Returned more arrays then rented"); + private static void TimerCallback(WeakReference weakPoolRef) + { + if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool)) + { + pool.Trim(); + } + } + private bool Trim() { UnmanagedMemoryHandle[] buffersLocal = this.buffers; @@ -307,7 +316,7 @@ namespace SixLabors.ImageSharp.Memory.Internals public class TrimSettings { // Trim half of the retained pool buffers every minute. - public int TrimPeriodMilliseconds { get; set; } = 60_000; + public int TrimPeriodMilliseconds { get; set; } = 20_000; public float Rate { get; set; } = 0.5f; diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 51c20519d..6ea69c892 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -18,6 +18,7 @@ namespace SixLabors.ImageSharp.Memory private readonly int sharedArrayPoolThresholdInBytes; private readonly int poolBufferSizeInBytes; private readonly int poolCapacity; + private readonly UniformUnmanagedMemoryPool.TrimSettings trimSettings; private UniformUnmanagedMemoryPool pool; private readonly UnmanagedMemoryAllocator nonPoolAllocator; @@ -42,17 +43,32 @@ namespace SixLabors.ImageSharp.Memory { } - // Internal constructor allowing to change the shared array pool threshold for testing purposes. internal UniformUnmanagedMemoryPoolMemoryAllocator( int sharedArrayPoolThresholdInBytes, int poolBufferSizeInBytes, long maxPoolSizeInBytes, int unmanagedBufferSizeInBytes) + : this( + sharedArrayPoolThresholdInBytes, + poolBufferSizeInBytes, + maxPoolSizeInBytes, + unmanagedBufferSizeInBytes, + UniformUnmanagedMemoryPool.TrimSettings.Default) + { + } + + internal UniformUnmanagedMemoryPoolMemoryAllocator( + int sharedArrayPoolThresholdInBytes, + int poolBufferSizeInBytes, + long maxPoolSizeInBytes, + int unmanagedBufferSizeInBytes, + UniformUnmanagedMemoryPool.TrimSettings trimSettings) { this.sharedArrayPoolThresholdInBytes = sharedArrayPoolThresholdInBytes; this.poolBufferSizeInBytes = poolBufferSizeInBytes; this.poolCapacity = (int)(maxPoolSizeInBytes / poolBufferSizeInBytes); - this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity); + this.trimSettings = trimSettings; + this.pool = new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings); this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes); } @@ -125,7 +141,7 @@ namespace SixLabors.ImageSharp.Memory { UniformUnmanagedMemoryPool oldPool = Interlocked.Exchange( ref this.pool, - new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity)); + new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings)); oldPool.Release(); } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 3782e5584..53960b954 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -11,6 +11,7 @@ using System.Threading; using CommandLine; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { @@ -231,6 +232,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox [Option('f', "file", Required = false, Default = null)] public string FileOutput { get; set; } + [Option('t', "trim-time", Required = false, Default = 60)] + public int TrimTimeSeconds { get; set; } + public static CommandLineOptions Parse(string[] args) { CommandLineOptions result = null; @@ -257,7 +261,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 1024 * 1024, (int)B(this.MaxContiguousPoolBufferMegaBytes), B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes)); + (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes), + new UniformUnmanagedMemoryPool.TrimSettings + { + TrimPeriodMilliseconds = this.TrimTimeSeconds * 100 + }); default: throw new ArgumentOutOfRangeException(); } From fd94dbfb310af7200f3caa99348ca3d83cd53621 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 7 Aug 2021 23:41:32 +0200 Subject: [PATCH 003/228] fix trimming --- .../Internals/UniformUnmanagedMemoryPool.cs | 9 ++--- .../LoadResizeSaveParallelMemoryStress.cs | 34 +++++++++++++------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index c3e41cf7e..97599b5ab 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -44,6 +44,7 @@ namespace SixLabors.ImageSharp.Memory.Internals #if NETCORE31COMPATIBLE Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); #endif + this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; } } @@ -262,6 +263,7 @@ namespace SixLabors.ImageSharp.Memory.Internals // Trim all: for (int i = this.index; i < buffersLocal.Length && buffersLocal[i] != null; i++) { + buffersLocal[i].Dispose(); buffersLocal[i] = null; } } @@ -316,13 +318,12 @@ namespace SixLabors.ImageSharp.Memory.Internals public class TrimSettings { // Trim half of the retained pool buffers every minute. - public int TrimPeriodMilliseconds { get; set; } = 20_000; + public int TrimPeriodMilliseconds { get; set; } = 60_000; public float Rate { get; set; } = 0.5f; - // Be more strict about high pressure threshold than ArrayPool.Shared. - // A 32 bit process can OOM before reaching HighMemoryLoadThresholdBytes. - public float HighPressureThresholdRate { get; set; } = 0.5f; + // Be more strict about high pressure on 32 bit. + public unsafe float HighPressureThresholdRate { get; set; } = sizeof(IntPtr) == 8 ? 0.9f : 0.6f; public bool Enabled => this.Rate > 0; diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 53960b954..f03a91449 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox [Option('f', "file", Required = false, Default = null)] public string FileOutput { get; set; } - [Option('t', "trim-time", Required = false, Default = 60)] - public int TrimTimeSeconds { get; set; } + [Option('t', "trim-time", Required = false, Default = null)] + public int? TrimTimeSeconds { get; set; } public static CommandLineOptions Parse(string[] args) { @@ -257,15 +257,27 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox return ArrayPoolMemoryAllocator.CreateDefault(); #pragma warning restore CS0618 case AllocatorKind.Unmanaged: - return new UniformUnmanagedMemoryPoolMemoryAllocator( - 1024 * 1024, - (int)B(this.MaxContiguousPoolBufferMegaBytes), - B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes), - new UniformUnmanagedMemoryPool.TrimSettings - { - TrimPeriodMilliseconds = this.TrimTimeSeconds * 100 - }); + if (this.TrimTimeSeconds.HasValue) + { + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes), + new UniformUnmanagedMemoryPool.TrimSettings + { + TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 + }); + } + else + { + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes)); + } + default: throw new ArgumentOutOfRangeException(); } From 1a41aaa10b7f7e3f7cd078507f500a0c4b47a010 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 17:42:16 +0200 Subject: [PATCH 004/228] LoadResizeSaveParallelMemoryStress help text --- .../LoadResizeSaveParallelMemoryStress.cs | 46 ++++++++++--------- .../Program.cs | 1 - 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index f03a91449..487420318 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -5,10 +5,10 @@ using System; using System.Diagnostics; using System.Globalization; using System.IO; -using System.Runtime; using System.Text; using System.Threading; using CommandLine; +using CommandLine.Text; using SixLabors.ImageSharp.Benchmarks.LoadResizeSave; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; @@ -22,7 +22,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { this.Benchmarks = new LoadResizeSaveStressRunner() { - // MaxDegreeOfParallelism = 10, // Filter = JpegKind.Baseline }; this.Benchmarks.Init(); @@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox public static void Run(string[] args) { - var options = CommandLineOptions.Parse(args); + var options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; var lrs = new LoadResizeSaveParallelMemoryStress(); if (options != null) @@ -198,55 +197,60 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private class CommandLineOptions { - [Option('i', "imagesharp", Required = false, Default = false)] + [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] public bool ImageSharp { get; set; } - [Option('a', "allocator", Required = false, Default = AllocatorKind.Unmanaged)] + [Option('a', "allocator", Required = false, Default = AllocatorKind.Unmanaged, HelpText = "Select allocator: Classic (ArrayPoolMemoryAllocator) or Unmanaged")] public AllocatorKind Allocator { get; set; } - [Option('m', "max-contiguous", Required = false, Default = 4)] + [Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")] public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; - [Option('s', "poolsize", Required = false, Default = 4096)] + [Option('s', "poolsize", Required = false, Default = 4096, HelpText = "The size of the pool in MegaBytes")] public int MaxPoolSizeMegaBytes { get; set; } = 4096; - [Option('u', "max-unmg", Required = false, Default = 32)] - public int MaxCapacityOfUnmanagedBuffersMegaBytes { get; set; } = 32; + [Option('u', "max-nonpool", Required = false, Default = 32, HelpText = "Maximum size of non-pooled contiguous blocks in MegaBytes")] + public int MaxCapacityOfNonPoolBuffersMegaBytes { get; set; } = 32; - [Option('p', "parallelism", Required = false, Default = -1)] + [Option('p', "parallelism", Required = false, Default = -1, HelpText = "Level of parallelism")] public int MaxDegreeOfParallelism { get; set; } = -1; - [Option('r', "repeat-count", Required = false, Default = 1)] + [Option('r', "repeat-count", Required = false, Default = 1, HelpText = "Times to run the whole benchmark")] public int RepeatCount { get; set; } = 1; - // This is to test trimming and virtual memory decommit - [Option('g', "final-gc-count", Required = false, Default = 0)] + [Option('g', "final-gc-count", Required = false, Default = 0, HelpText = "How many times to GC.Collect after execution")] public int FinalGcCount { get; set; } - [Option('e', "release-at-end", Required = false, Default = false)] + [Option('e', "release-at-end", Required = false, Default = false, HelpText = "Specify to run ReleaseRetainedResources() after execution")] public bool ReleaseRetainedResourcesAtEnd { get; set; } - [Option('w', "pause", Required = false, Default = false)] + [Option('w', "pause", Required = false, Default = false, HelpText = "Specify to pause and wait for user input after the execution")] public bool PauseAtEnd { get; set; } - [Option('f', "file", Required = false, Default = null)] + [Option('f', "file", Required = false, Default = null, HelpText = "Specify to print the execution time to a file. Format: ';' see the code for formatstr semantics.")] public string FileOutput { get; set; } - [Option('t', "trim-time", Required = false, Default = null)] + [Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")] public int? TrimTimeSeconds { get; set; } public static CommandLineOptions Parse(string[] args) { CommandLineOptions result = null; - Parser.Default.ParseArguments(args).WithParsed(o => + ParserResult parserResult = Parser.Default.ParseArguments(args).WithParsed(o => { result = o; }); + + if (result == null) + { + Console.WriteLine(HelpText.RenderUsageText(parserResult)); + } + return result; } public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.Allocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfUnmanagedBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.Allocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; public MemoryAllocator CreateMemoryAllocator() { @@ -263,7 +267,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 1024 * 1024, (int)B(this.MaxContiguousPoolBufferMegaBytes), B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes), + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes), new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 @@ -275,7 +279,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 1024 * 1024, (int)B(this.MaxContiguousPoolBufferMegaBytes), B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfUnmanagedBuffersMegaBytes)); + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes)); } default: diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index a1bca9e6a..87d6a363b 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -34,7 +34,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox public static void Main(string[] args) { LoadResizeSaveParallelMemoryStress.Run(args); - // TrimPoolTest(); // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); From aa26b63e4b4e5619c5ec9847c01c8a96dc2a9d16 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 18:33:54 +0200 Subject: [PATCH 005/228] Set_MaximumPoolSizeMegabytes_CreateImage_MaximumPoolSizeMegabytes --- .../Image/LargeImageIntegrationTests.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index afa217bbc..240c9f568 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using Xunit; @@ -19,10 +21,28 @@ namespace SixLabors.ImageSharp.Tests image.DebugSave(provider); } + [Fact] + public unsafe void Set_MaximumPoolSizeMegabytes_CreateImage_MaximumPoolSizeMegabytes() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + Configuration.Default.MemoryAllocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions() + { + MinimumContiguousBlockBytes = sizeof(Rgba32) * 8192 * 4096 + }); + using Image image = new Image(8192, 4096); + Assert.True(image.TryGetSinglePixelSpan(out Span span)); + Assert.Equal(8192 * 4096, span.Length); + } + } + [Theory] [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] - public void GetSingleSpan(TestImageProvider provider) + public void TryGetSinglePixelSpan_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) { + provider.LimitAllocatorBufferCapacity().InPixels(10); using Image image = provider.GetImage(); Assert.False(image.TryGetSinglePixelSpan(out Span imageSpan)); From 172b0a0ca2351b9a7c97291112625ab5fa2e1676 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 20:16:06 +0200 Subject: [PATCH 006/228] MinimumContiguousBlockBytes -> MinimumContiguousBlockSizeBytes --- src/ImageSharp/Memory/Allocators/MemoryAllocator.cs | 2 +- .../Memory/Allocators/MemoryAllocatorOptions.cs | 8 ++++---- .../LoadResizeSave/LoadResizeSaveStressBenchmarks.cs | 6 +++--- .../ImageSharp.Tests/Image/LargeImageIntegrationTests.cs | 2 +- .../UniformUnmanagedPoolMemoryAllocatorTests.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 840e07d1a..6be5c50fa 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Memory /// The . /// The . public static MemoryAllocator CreateDefault(MemoryAllocatorOptions options) => - new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes, options.MinimumContiguousBlockBytes); + new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes, options.MinimumContiguousBlockSizeBytes); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index 8ea7f6f7b..acdf36c6a 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Memory public class MemoryAllocatorOptions { private int? maximumPoolSizeMegabytes; - private int? minimumContiguousBlockBytes; + private int? minimumContiguousBlockSizeBytes; /// /// Gets or sets a value defining the maximum size of the 's internal memory pool @@ -38,9 +38,9 @@ namespace SixLabors.ImageSharp.Memory /// Overriding this value is useful for interop scenarios /// ensuring succeeds. /// - public int? MinimumContiguousBlockBytes + public int? MinimumContiguousBlockSizeBytes { - get => this.minimumContiguousBlockBytes; + get => this.minimumContiguousBlockSizeBytes; set { if (value.HasValue) @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThanOrEqualTo(value.Value, 65536, nameof(this.MaximumPoolSizeMegabytes)); } - this.minimumContiguousBlockBytes = value; + this.minimumContiguousBlockSizeBytes = value; } } } diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index d3191c695..c573c95ce 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -48,9 +48,9 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public int[] ParallelismValues { get; } = { Environment.ProcessorCount, - Environment.ProcessorCount / 2, - Environment.ProcessorCount / 4, - 1 + // Environment.ProcessorCount / 2, + // Environment.ProcessorCount / 4, + // 1 }; [Benchmark(Baseline = true)] diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index 240c9f568..f4d082b9b 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests { Configuration.Default.MemoryAllocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions() { - MinimumContiguousBlockBytes = sizeof(Rgba32) * 8192 * 4096 + MinimumContiguousBlockSizeBytes = sizeof(Rgba32) * 8192 * 4096 }); using Image image = new Image(8192, 4096); Assert.True(image.TryGetSinglePixelSpan(out Span span)); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index a872deef0..3557c241e 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -158,7 +158,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators var allocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions() { MaximumPoolSizeMegabytes = bool.Parse(poolAllocationStr) ? 64 : 0, - MinimumContiguousBlockBytes = fortyEightMegabytes + MinimumContiguousBlockSizeBytes = fortyEightMegabytes }); MemoryGroup g = allocator.AllocateGroup(fortyEightMegabytes, 1024); From 4251eac41a6c11e731ea225abab8580afd13c292 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 20:25:14 +0200 Subject: [PATCH 007/228] disable CA2015 --- .../Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs | 2 ++ .../Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index c4fed8426..048cb7433 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -20,7 +20,9 @@ namespace SixLabors.ImageSharp.Memory.Internals this.array = ArrayPool.Shared.Rent(this.lengthInBytes); } +#pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span ~SharedArrayPoolBuffer() => this.Dispose(false); +#pragma warning restore protected override void Dispose(bool disposing) { diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs index c315aa548..d7bb5413d 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -71,7 +71,9 @@ namespace SixLabors.ImageSharp.Memory.Internals bufferHandle.AssignedToNewOwner(); } +#pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span ~FinalizableBuffer() => this.Dispose(false); +#pragma warning restore protected override void Dispose(bool disposing) { From c9d13965e3badd7ecc228cf69359ffe1838c2149 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 22:49:46 +0200 Subject: [PATCH 008/228] comments and docs --- src/ImageSharp/ImageFrame{TPixel}.cs | 10 ++++++++++ src/ImageSharp/Image{TPixel}.cs | 3 +++ src/ImageSharp/IndexedImageFrame{TPixel}.cs | 3 +++ .../Allocators/Internals/SharedArrayPoolBuffer{T}.cs | 3 +++ .../Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs | 4 ++++ 5 files changed, 23 insertions(+) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index a336c9c59..8249f8136 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -170,6 +170,9 @@ namespace SixLabors.ImageSharp /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the first pixel on that row. + /// + /// WARNING: Disposing or leaking the underlying image while still working with it's + /// might lead to memory corruption. /// /// The row. /// The @@ -185,6 +188,13 @@ namespace SixLabors.ImageSharp /// /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. + /// + /// To ensure the memory is contiguous, should be initialized + /// with a that enforces larger contiguous buffers. + /// See . + /// + /// WARNING: Disposing or leaking the underlying image while still working with it's + /// might lead to memory corruption. /// /// The . /// The . diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 2aa9c5394..1a7f84c15 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -206,6 +206,9 @@ namespace SixLabors.ImageSharp /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the first pixel on that row. + /// + /// WARNING: Disposing or leaking the underlying image while still working with it's + /// might lead to memory corruption. /// /// The row. /// The diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs index 7668d7600..d841cbea9 100644 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -75,6 +75,9 @@ namespace SixLabors.ImageSharp /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the first pixel on that row. + /// + /// WARNING: Disposing or leaking the underlying while still working with it's + /// might lead to memory corruption. /// /// The row index in the pixel buffer. /// The pixel row as a . diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 048cb7433..7edd9f5a7 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -20,6 +20,9 @@ namespace SixLabors.ImageSharp.Memory.Internals this.array = ArrayPool.Shared.Rent(this.lengthInBytes); } + // The worst thing that could happen is that a VERY poorly written user code holding a Span on the stack, + // while loosing the reference to Image (or disposing it) may write to an unrelated ArrayPool array. + // This is an unlikely scenario we mitigate by a warning in GetPixelRowSpan(i) APIs. #pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span ~SharedArrayPoolBuffer() => this.Dispose(false); #pragma warning restore diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs index d7bb5413d..84b93496d 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -71,6 +71,10 @@ namespace SixLabors.ImageSharp.Memory.Internals bufferHandle.AssignedToNewOwner(); } + // A VERY poorly written user code holding a Span on the stack, + // while loosing the reference to Image (or disposing it) may write to (now unrelated) pool buffer, + // or cause memory corruption if the underlying UmnanagedMemoryHandle has been released. + // This is an unlikely scenario we mitigate a warning in GetPixelRowSpan(i) APIs. #pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span ~FinalizableBuffer() => this.Dispose(false); #pragma warning restore From e13edbafe732630c20356832229f39c18d3a8525 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 22:55:50 +0200 Subject: [PATCH 009/228] fix .NET Framework build error + a few warnings --- .../Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs | 2 ++ .../LoadResizeSaveParallelMemoryStress.cs | 2 +- tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 6ea69c892..8d8e8e3df 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -13,6 +13,8 @@ namespace SixLabors.ImageSharp.Memory internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocator { private const int OneMegabyte = 1 << 20; + + // 4 MB seemed to perform slightly better in benchmarks than 2MB or higher values: private const int DefaultContiguousPoolBlockSizeBytes = 4 * OneMegabyte; private const int DefaultNonPoolBlockSizeBytes = 32 * OneMegabyte; private readonly int sharedArrayPoolThresholdInBytes; diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 487420318..0c0691059 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox string fileName = ss[0]; string content = ss[1] .Replace("TotalSeconds", stats.TotalSeconds.ToString(CultureInfo.InvariantCulture)) - .Replace("EOL", Environment.NewLine, StringComparison.OrdinalIgnoreCase); + .Replace("EOL", Environment.NewLine); File.AppendAllText(fileName, content); } diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index f4d082b9b..09ed2afa5 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -42,7 +42,6 @@ namespace SixLabors.ImageSharp.Tests [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] public void TryGetSinglePixelSpan_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) { - provider.LimitAllocatorBufferCapacity().InPixels(10); using Image image = provider.GetImage(); Assert.False(image.TryGetSinglePixelSpan(out Span imageSpan)); From 0cdbf7226ba11e6130b5d57c46a95b93db38d9e4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 23:09:42 +0200 Subject: [PATCH 010/228] disable MemoryGroupFinalizer_ReturnsToPool on MacOS --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 3557c241e..951d5323f 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -236,7 +236,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [Theory] + public static bool IsNotMacOS => !TestEnvironment.IsOSX; + + // TODO: This doesn't seem to work on MacOS. Open an issue & investigate. + [ConditionalTheory(nameof(IsNotMacOS))] [InlineData(300)] [InlineData(600)] [InlineData(1200)] From 20d6228b3dba4c5adc96f4f48a7d2849a16c3af7 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 8 Aug 2021 23:20:01 +0200 Subject: [PATCH 011/228] make MemoryGroupFinalizer_ReturnsToPool Windows-only --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 951d5323f..70eba2e6a 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -236,10 +236,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - public static bool IsNotMacOS => !TestEnvironment.IsOSX; + public static bool IsWindows => TestEnvironment.IsWindows; - // TODO: This doesn't seem to work on MacOS. Open an issue & investigate. - [ConditionalTheory(nameof(IsNotMacOS))] + // TODO: This doesn't seem to work on Unix. Open an issue & investigate. + [ConditionalTheory(nameof(IsWindows))] [InlineData(300)] [InlineData(600)] [InlineData(1200)] From 1a86d6d57d8bfcbeaff2c00b212545ad4a7da3c9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 9 Aug 2021 00:00:08 +0200 Subject: [PATCH 012/228] try to mitigate 32 bit failures --- tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs | 9 +++++++++ .../TestUtilities/ImageProviders/TestImageProvider.cs | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index bd24e1a8d..d43c6f0a3 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -29,6 +29,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif { TestImages.Gif.Ratio4x1, 4, 1, PixelResolutionUnit.AspectRatio } }; + public GifEncoderTests() + { + // Free the pool on 32 bit: + if (!TestEnvironment.Is64BitProcess) + { + Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); + } + } + [Theory] [WithTestPatternImages(100, 100, TestPixelTypes, false)] [WithTestPatternImages(100, 100, TestPixelTypes, false)] diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index 700c40b72..a30cdb4c5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -21,11 +21,17 @@ namespace SixLabors.ImageSharp.Tests public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable where TPixel : unmanaged, IPixel { + public TestImageProvider() + { + this.Configuration = Configuration.CreateDefaultInstance(); + this.Configuration.MemoryAllocator = Configuration.Default.MemoryAllocator; + } + public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); public virtual string SourceFileOrDescription => string.Empty; - public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); + public Configuration Configuration { get; set; } /// /// Gets the utility instance to provide information about the test image & manage input/output. From 32ae7cff835f2c02b80c7d04b4bc9df8a9167a21 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 14 Aug 2021 19:54:19 +0200 Subject: [PATCH 013/228] API chunks --- src/ImageSharp/ImageFrame{TPixel}.cs | 18 ++++++ src/ImageSharp/Image{TPixel}.cs | 19 ++++++ src/ImageSharp/PixelAccessor{TPixel}.cs | 62 +++++++++++++++++++ .../LoadResizeSaveParallelMemoryStress.cs | 3 + .../Program.cs | 26 +++++++- .../TestUtilities/TestEnvironment.cs | 2 +- 6 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/PixelAccessor{TPixel}.cs diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 8249f8136..0174b249b 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -12,6 +12,8 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { + + /// /// Represents a pixel-specific image frame containing all pixel data and . /// In case of animated formats like gif, it contains the single frame in a animation. @@ -185,6 +187,22 @@ namespace SixLabors.ImageSharp return this.PixelBuffer.GetRowSpan(rowIndex); } + public void ProcessPixelRows(PixelAccessorAction processPixels) => throw new NotImplementedException(); + + public void ProcessPixelRows( + Image image2, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + => throw new NotImplementedException(); + + public void ProcessPixelRows( + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel + => throw new NotImplementedException(); + /// /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 1a7f84c15..c1a084611 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -203,6 +204,22 @@ namespace SixLabors.ImageSharp } } + public void ProcessPixelRows(PixelAccessorAction processPixels) => throw new NotImplementedException(); + + public void ProcessPixelRows( + Image image2, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + => throw new NotImplementedException(); + + public void ProcessPixelRows( + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel + => throw new NotImplementedException(); + /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the first pixel on that row. @@ -242,6 +259,8 @@ namespace SixLabors.ImageSharp return false; } + public bool DangerousTryGetSinglePixelMemory(out Memory memory) => throw new NotImplementedException(); + /// /// Clones the current image /// diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs new file mode 100644 index 000000000..0a7f408af --- /dev/null +++ b/src/ImageSharp/PixelAccessor{TPixel}.cs @@ -0,0 +1,62 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp +{ + /// + /// A delegate to be executed on a . + /// + /// The pixel type. + public delegate void PixelAccessorAction(PixelAccessor pixelAccessor) + where TPixel : unmanaged, IPixel; + + /// + /// A delegate to be executed on two instances of . + /// + /// The first pixel type. + /// The second pixel type. + public delegate void PixelAccessorAction( + PixelAccessor pixelAccessor1, + PixelAccessor pixelAccessor2) + where TPixel1 : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel; + + /// + /// A delegate to be executed on three instances of . + /// + /// The first pixel type. + /// The second pixel type. + /// The third pixel type. + public delegate void PixelAccessorAction( + PixelAccessor pixelAccessor1, + PixelAccessor pixelAccessor2, + PixelAccessor pixelAccessor3) + where TPixel1 : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel + where TPixel3 : unmanaged, IPixel; + + /// + /// Provides efficient access the pixel buffers of an . + /// + /// The pixel type. + public ref struct PixelAccessor + where TPixel : unmanaged, IPixel + { + /// + /// Gets the height of the backing . + /// + public int Height { get; } + + /// + /// Gets the representation of the pixels as a of contiguous memory + /// at row beginning from the first pixel on that row. + /// + /// The row. + /// The . + /// Thrown when row index is out of range. + public Span GetRowSpan(int rowIndex) => throw new NotImplementedException(); + } +} diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 0c0691059..38dd9f812 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -31,6 +31,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox public static void Run(string[] args) { + Console.WriteLine($"Running: {typeof(LoadResizeSaveParallelMemoryStress).Assembly.Location}"); + Console.WriteLine($"64 bit: {Environment.Is64BitProcess}"); var options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; var lrs = new LoadResizeSaveParallelMemoryStress(); @@ -80,6 +82,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels); + Console.WriteLine("Total Megapixels: " + stats.TotalMegapixels); Console.WriteLine(stats.GetMarkdown()); if (options?.FileOutput != null) { diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 87d6a363b..2e0d8d97c 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Reflection; using System.Threading; using SixLabors.ImageSharp.Memory.Internals; using SixLabors.ImageSharp.Tests.Formats.Jpg; @@ -33,7 +34,16 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox /// public static void Main(string[] args) { - LoadResizeSaveParallelMemoryStress.Run(args); + try + { + Console.WriteLine("WUT: " + GetNetCoreVersion()); + LoadResizeSaveParallelMemoryStress.Run(args); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + // RunJpegEncoderProfilingTests(); // RunJpegColorProfilingTests(); // RunDecodeJpegProfilingTests(); @@ -43,6 +53,20 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox // Console.ReadLine(); } + private static Version GetNetCoreVersion() + { + Assembly assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + Console.WriteLine(assembly.Location); + string[] assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + { + return Version.Parse(assemblyPath[netCoreAppIndex + 1]); + } + + return null; + } + private static void RunJpegEncoderProfilingTests() { var benchmarks = new JpegProfilingBenchmarks(new ConsoleOutput()); diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index 00633b959..00d8a5c1c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Tests /// private static void PrepareRemoteExecutor() { - if (!IsFramework) + if (!IsFramework || !Environment.Is64BitProcess) { return; } From 5dd85948650c655e2e27ec4de9e91e3582f5703b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 16 Aug 2021 22:47:58 +0200 Subject: [PATCH 014/228] retry allocation on OOM --- .../Internals/UnmanagedBuffer{T}.cs | 2 +- .../Internals/UnmanagedMemoryHandle.cs | 68 +++++++++++++++++-- .../LoadResizeSaveParallelMemoryStress.cs | 2 +- .../Allocators/UnmanagedMemoryHandleTests.cs | 14 ++-- 4 files changed, 73 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 03c504279..9755f3744 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Memory.Internals public UnmanagedBuffer(int lengthInElements) { this.lengthInElements = lengthInElements; - this.bufferHandle = new UnmanagedMemoryHandle(lengthInElements * Unsafe.SizeOf()); + this.bufferHandle = UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()); } private void* Pointer => (void*)this.bufferHandle.DangerousGetHandle(); diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs index 216b526b7..12ea933bb 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -10,16 +10,23 @@ namespace SixLabors.ImageSharp.Memory.Internals { internal sealed class UnmanagedMemoryHandle : SafeHandle { + // Number of allocation re-attempts when OutOfMemoryException is thrown. + private const int MaxAllocationAttempts = 1000; + private readonly int lengthInBytes; private bool resurrected; // Track allocations for testing purposes: private static int totalOutstandingHandles; - public UnmanagedMemoryHandle(int lengthInBytes) - : base(IntPtr.Zero, true) + private static long totalOomRetries; + + // A Monitor to wait/signal when we are low on memory. + private static object lowMemoryMonitor; + + private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes) + : base(handle, true) { - this.SetHandle(Marshal.AllocHGlobal(lengthInBytes)); this.lengthInBytes = lengthInBytes; if (lengthInBytes > 0) { @@ -30,10 +37,15 @@ namespace SixLabors.ImageSharp.Memory.Internals } /// - /// Gets a value indicating the total outstanding handle allocations for testing purposes. + /// Gets the total outstanding handle allocations for testing purposes. /// internal static int TotalOutstandingHandles => totalOutstandingHandles; + /// + /// Gets the total number -s retried. + /// + internal static long TotalOomRetries => totalOomRetries; + /// public override bool IsInvalid => this.handle == IntPtr.Zero; @@ -50,11 +62,59 @@ namespace SixLabors.ImageSharp.Memory.Internals GC.RemoveMemoryPressure(this.lengthInBytes); } + if (lowMemoryMonitor != null) + { + // We are low on memory. Signal all threads waiting in AllocateHandle(). + Monitor.Enter(lowMemoryMonitor); + Monitor.PulseAll(lowMemoryMonitor); + Monitor.Exit(lowMemoryMonitor); + } + this.handle = IntPtr.Zero; Interlocked.Decrement(ref totalOutstandingHandles); return true; } + internal static UnmanagedMemoryHandle Allocate(int lengthInBytes) + { + IntPtr handle = AllocateHandle(lengthInBytes); + return new UnmanagedMemoryHandle(handle, lengthInBytes); + } + + private static IntPtr AllocateHandle(int lengthInBytes) + { + int counter = 0; + IntPtr handle = IntPtr.Zero; + while (handle == IntPtr.Zero) + { + try + { + handle = Marshal.AllocHGlobal(lengthInBytes); + } + catch (OutOfMemoryException) + { + // We are low on memory, but expect some memory to be freed soon. + // Block the thread & retry to avoid OOM. + if (counter < MaxAllocationAttempts) + { + counter++; + Interlocked.Increment(ref totalOomRetries); + + Interlocked.CompareExchange(ref lowMemoryMonitor, new object(), null); + Monitor.Enter(lowMemoryMonitor); + Monitor.Wait(lowMemoryMonitor, millisecondsTimeout: 1); + Monitor.Exit(lowMemoryMonitor); + } + else + { + throw; + } + } + } + + return handle; + } + /// /// UnmanagedMemoryHandle's finalizer would release the underlying handle returning the memory to the OS. /// We want to prevent this when a finalizable owner (buffer or MemoryGroup) is returning the handle to diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 38dd9f812..4e66b9666 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels); - Console.WriteLine("Total Megapixels: " + stats.TotalMegapixels); + Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}"); Console.WriteLine(stats.GetMarkdown()); if (options?.FileOutput != null) { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs index 5744f81ec..ecc2188eb 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs @@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators public class UnmanagedMemoryHandleTests { [Fact] - public unsafe void Constructor_AllocatesReadWriteMemory() + public unsafe void Allocate_AllocatesReadWriteMemory() { - using var h = new UnmanagedMemoryHandle(128); + using var h = UnmanagedMemoryHandle.Allocate(128); Assert.False(h.IsClosed); Assert.False(h.IsInvalid); byte* ptr = (byte*)h.DangerousGetHandle(); @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [Fact] public void Dispose_ClosesHandle() { - var h = new UnmanagedMemoryHandle(128); + var h = UnmanagedMemoryHandle.Allocate(128); h.Dispose(); Assert.True(h.IsClosed); Assert.True(h.IsInvalid); @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators for (int i = 0; i < countInner; i++) { Assert.Equal(i, UnmanagedMemoryHandle.TotalOutstandingHandles); - var h = new UnmanagedMemoryHandle(42); + var h = UnmanagedMemoryHandle.Allocate(42); Assert.Equal(i + 1, UnmanagedMemoryHandle.TotalOutstandingHandles); l.Add(h); } @@ -92,7 +92,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators var l = new List(); for (int i = 0; i < countInner; i++) { - var h = new UnmanagedMemoryHandle(42); + var h = UnmanagedMemoryHandle.Allocate(42); l.Add(h); } @@ -119,7 +119,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators static void AllocateResurrect() { - var h = new UnmanagedMemoryHandle(42); + var h = UnmanagedMemoryHandle.Allocate(42); h.Resurrect(); } } @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators static void AllocateAndForget() { - _ = new HandleOwner(new UnmanagedMemoryHandle(42)); + _ = new HandleOwner(UnmanagedMemoryHandle.Allocate(42)); } static void VerifyResurrectedHandle(bool reAssign) From f7d9d37a6bf327204c210609cdb2cff3d9e8c774 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 16 Aug 2021 22:49:33 +0200 Subject: [PATCH 015/228] cleanup naming in UniformUnmanagedMemoryPool --- .../Internals/UniformUnmanagedMemoryPool.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index 97599b5ab..cfb0b3eb5 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Memory.Internals return null; } - UnmanagedMemoryHandle array; + UnmanagedMemoryHandle buffer; lock (buffersLocal) { @@ -72,21 +72,21 @@ namespace SixLabors.ImageSharp.Memory.Internals return null; } - array = buffersLocal[this.index]; + buffer = buffersLocal[this.index]; buffersLocal[this.index++] = null; } - if (array == null) + if (buffer == null) { - array = new UnmanagedMemoryHandle(this.BufferLength); + buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); } if (allocationOptions.Has(AllocationOptions.Clean)) { - this.GetSpan(array).Clear(); + this.GetSpan(buffer).Clear(); } - return array; + return buffer; } public UnmanagedMemoryHandle[] Rent(int bufferCount, AllocationOptions allocationOptions = AllocationOptions.None) @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Memory.Internals { if (result[i] == null) { - result[i] = new UnmanagedMemoryHandle(this.BufferLength); + result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength); } if (allocationOptions.Has(AllocationOptions.Clean)) @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Memory.Internals if (this.index == 0) { - ThrowReturnedMoreArraysThanRented(); // DEBUG-only exception + ThrowReturnedMoreBuffersThanRented(); // DEBUG-only exception buffer.Dispose(); return; } @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Memory.Internals if (this.index - buffers.Length + 1 <= 0) { - ThrowReturnedMoreArraysThanRented(); + ThrowReturnedMoreBuffersThanRented(); DisposeAll(buffers); return; } @@ -216,8 +216,8 @@ namespace SixLabors.ImageSharp.Memory.Internals // This indicates a bug in the library, however Return() might be called from a finalizer, // therefore we should never throw here in production. [Conditional("DEBUG")] - private static void ThrowReturnedMoreArraysThanRented() => - throw new InvalidMemoryOperationException("Returned more arrays then rented"); + private static void ThrowReturnedMoreBuffersThanRented() => + throw new InvalidMemoryOperationException("Returned more buffers then rented"); private static void TimerCallback(WeakReference weakPoolRef) { @@ -271,18 +271,18 @@ namespace SixLabors.ImageSharp.Memory.Internals return true; } - private bool TrimLowPressure(UnmanagedMemoryHandle[] arraysLocal) + private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal) { - lock (arraysLocal) + lock (buffersLocal) { if (this.buffers == null) { return false; } - // Count the arrays in the pool: + // Count the buffers in the pool: int retainedCount = 0; - for (int i = this.index; i < arraysLocal.Length && arraysLocal[i] != null; i++) + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i] != null; i++) { retainedCount++; } @@ -293,8 +293,8 @@ namespace SixLabors.ImageSharp.Memory.Internals int trimStop = this.index + retainedCount - trimCount; for (int i = trimStart; i >= trimStop; i--) { - arraysLocal[i].Dispose(); - arraysLocal[i] = null; + buffersLocal[i].Dispose(); + buffersLocal[i] = null; } this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; From a67cb01a5f5c396286c0f40216760ef30b2ec0d6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 16 Oct 2021 13:31:32 +0200 Subject: [PATCH 016/228] fix build after merge --- src/ImageSharp/ImageFrame{TPixel}.cs | 2 -- src/ImageSharp/Memory/Buffer2D{T}.cs | 11 ----------- .../LoadResizeSaveParallelMemoryStress.cs | 1 - .../TestUtilities/ImageProviders/TestImageProvider.cs | 3 --- 4 files changed, 17 deletions(-) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 0174b249b..b29608f62 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -12,8 +12,6 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp { - - /// /// Represents a pixel-specific image frame containing all pixel data and . /// In case of animated formats like gif, it contains the single frame in a animation. diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 09ce14900..c85a1cdb5 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -109,17 +109,6 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); int stride = this.Width + padding; - if (this.cachedMemory.Length > 0) - { - paddedSpan = this.cachedMemory.Span.Slice(y * this.Width); - if (paddedSpan.Length < stride) - { - return false; - } - - paddedSpan = paddedSpan.Slice(0, stride); - return true; - } Memory memory = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index b862fd5c0..873845393 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -133,7 +133,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox lrs.SystemDrawingBenchmarkParallel(); break; case ConsoleKey.D2: - Console.WriteLine($"Images: {lrs.benchmarks.Images.Length}"); lrs.ImageSharpBenchmarkParallel(); break; case ConsoleKey.D3: diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index a4d48488d..a30cdb4c5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -27,9 +27,6 @@ namespace SixLabors.ImageSharp.Tests this.Configuration.MemoryAllocator = Configuration.Default.MemoryAllocator; } - return configuration; - } - public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); public virtual string SourceFileOrDescription => string.Empty; From e233b8136e2be968d60496d75dfe96ed9f06ba30 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 16 Oct 2021 13:43:20 +0200 Subject: [PATCH 017/228] undo unsafe optimizations in ErrorDither --- .../Processing/Processors/Dithering/ErrorDither.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 0fe2d4b2c..2e6cfebe1 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -108,13 +108,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering for (int y = bounds.Top; y < bounds.Bottom; y++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); - ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + // Unsafe optimizations undone temporarily. + // Sporadic local AccessViolationException indicates possible indexing bug. + // ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + // ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); + Span sourceSpan = source.GetPixelRowSpan(y); + Span destSpan = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); for (int x = bounds.Left; x < bounds.Right; x++) { - TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); - Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); + // TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); + // Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); + TPixel sourcePixel = sourceSpan[x]; + destSpan[x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); } } From 5839a5a483ded09081a87f8d6ec105ef457176d9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 28 Oct 2021 21:18:06 +0200 Subject: [PATCH 018/228] PreferContiguousImageBuffers implemented --- src/ImageSharp/Configuration.cs | 8 +++ src/ImageSharp/ImageFrame{TPixel}.cs | 16 ++++-- .../Memory/MemoryAllocatorExtensions.cs | 52 ++++++++++++++++++- .../ImageFrameCollectionTests.Generic.cs | 23 +++++++- .../Image/LargeImageIntegrationTests.cs | 12 ++--- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 19 +++++++ 6 files changed, 118 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 0ac4c29ea..3d9552e42 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -94,6 +94,14 @@ namespace SixLabors.ImageSharp } } + /// + /// Gets or sets a value indicating whether to force image buffers to be contiguous whenever possible. + /// + /// + /// Contiguous allocations are not possible, if the image needs a buffer larger than . + /// + public bool PreferContiguousImageBuffers { get; set; } + /// /// Gets a set of properties for the Configuration. /// diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index b29608f62..949b8999a 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -58,7 +58,11 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height, AllocationOptions.Clean); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers, + AllocationOptions.Clean); } /// @@ -87,7 +91,10 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(width, height); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + width, + height, + configuration.PreferContiguousImageBuffers); this.Clear(backgroundColor); } @@ -131,7 +138,10 @@ namespace SixLabors.ImageSharp Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(source, nameof(source)); - this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D(source.PixelBuffer.Width, source.PixelBuffer.Height); + this.PixelBuffer = this.GetConfiguration().MemoryAllocator.Allocate2D( + source.PixelBuffer.Width, + source.PixelBuffer.Height, + configuration.PreferContiguousImageBuffers); source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs index 9cf1af659..abcf078ac 100644 --- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs +++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs @@ -20,20 +20,68 @@ namespace SixLabors.ImageSharp.Memory /// The memory allocator. /// The buffer width. /// The buffer height. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . /// The allocation options. /// The . public static Buffer2D Allocate2D( this MemoryAllocator memoryAllocator, int width, int height, + bool preferContiguosImageBuffers, AllocationOptions options = AllocationOptions.None) where T : struct { long groupLength = (long)width * height; - MemoryGroup memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + MemoryGroup memoryGroup; + if (preferContiguosImageBuffers && groupLength < int.MaxValue) + { + IMemoryOwner buffer = memoryAllocator.Allocate((int)groupLength, options); + memoryGroup = MemoryGroup.CreateContiguous(buffer, false); + } + else + { + memoryGroup = memoryAllocator.AllocateGroup(groupLength, width, options); + } + return new Buffer2D(memoryGroup, width, height); } + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of x elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer width. + /// The buffer height. + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + int width, + int height, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, width, height, false, options); + + /// + /// Allocates a buffer of value type objects interpreted as a 2D region + /// of width x height elements. + /// + /// The type of buffer items to allocate. + /// The memory allocator. + /// The buffer size. + /// A value indicating whether the allocated buffer should be contiguous, unless bigger than . + /// The allocation options. + /// The . + public static Buffer2D Allocate2D( + this MemoryAllocator memoryAllocator, + Size size, + bool preferContiguosImageBuffers, + AllocationOptions options = AllocationOptions.None) + where T : struct => + Allocate2D(memoryAllocator, size.Width, size.Height, preferContiguosImageBuffers, options); + /// /// Allocates a buffer of value type objects interpreted as a 2D region /// of width x height elements. @@ -48,7 +96,7 @@ namespace SixLabors.ImageSharp.Memory Size size, AllocationOptions options = AllocationOptions.None) where T : struct => - Allocate2D(memoryAllocator, size.Width, size.Height, options); + Allocate2D(memoryAllocator, size.Width, size.Height, false, options); internal static Buffer2D Allocate2DOveraligned( this MemoryAllocator memoryAllocator, diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index dd8275ee8..50b21ba8d 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -2,10 +2,11 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.Linq; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.Memory; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -339,6 +340,26 @@ namespace SixLabors.ImageSharp.Tests Assert.False(this.Image.Frames.Contains(frame)); } + [Fact] + public void PreferContiguousImageBuffers_True_AppliedToAllFrames() + { + Configuration configuration = Configuration.Default.Clone(); + configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = 1000 }; + configuration.PreferContiguousImageBuffers = true; + + using var image = new Image(configuration, 100, 100); + image.Frames.CreateFrame(); + image.Frames.InsertFrame(0, image.Frames[0]); + image.Frames.CreateFrame(Color.Red); + + Assert.Equal(4, image.Frames.Count); + IEnumerable> frames = image.Frames; + foreach (ImageFrame frame in frames) + { + Assert.True(frame.TryGetSinglePixelSpan(out Span span)); + } + } + [Fact] public void DisposeCall_NoThrowIfCalledMultiple() { diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index 09ed2afa5..f6656ec5f 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -22,17 +22,17 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public unsafe void Set_MaximumPoolSizeMegabytes_CreateImage_MaximumPoolSizeMegabytes() + public void PreferContiguousImageBuffers_CreateImage_MaximumPoolSizeMegabytes() { + // Run remotely to avoid large allocation in the test process: RemoteExecutor.Invoke(RunTest).Dispose(); static void RunTest() { - Configuration.Default.MemoryAllocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions() - { - MinimumContiguousBlockSizeBytes = sizeof(Rgba32) * 8192 * 4096 - }); - using Image image = new Image(8192, 4096); + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; + + using var image = new Image(configuration, 8192, 4096); Assert.True(image.TryGetSinglePixelSpan(out Span span)); Assert.Equal(8192 * 4096, span.Length); } diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 845ea9ca4..04abc6585 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -68,6 +68,25 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Construct_PreferContiguousImageBuffers_AllocatesContiguousRegardlessOfCapacity(bool useSizeOverload) + { + this.MemoryAllocator.BufferCapacityInBytes = 10_000; + + using Buffer2D buffer = useSizeOverload ? + this.MemoryAllocator.Allocate2D( + new Size(200, 200), + preferContiguosImageBuffers: true) : + this.MemoryAllocator.Allocate2D( + 200, + 200, + preferContiguosImageBuffers: true); + Assert.Equal(1, buffer.FastMemoryGroup.Count); + Assert.Equal(200 * 200, buffer.FastMemoryGroup.TotalLength); + } + [Theory] [InlineData(50, 10, 20, 4)] public void Allocate2DOveraligned(int bufferCapacity, int width, int height, int alignmentMultiplier) From 2472c4285b57215667c399f9ca122d0fa634da78 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 28 Oct 2021 21:25:44 +0200 Subject: [PATCH 019/228] MemoryAllocatorOptions -> MemoryAllocatorSettings, remove MinimumContiguousBlockSizeBytes --- src/ImageSharp/ImageFrame{TPixel}.cs | 2 +- .../Memory/Allocators/MemoryAllocator.cs | 8 +-- .../Allocators/MemoryAllocatorOptions.cs | 57 ------------------- .../Allocators/MemoryAllocatorSettings.cs | 31 ++++++++++ ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 6 +- ...niformUnmanagedPoolMemoryAllocatorTests.cs | 22 ------- 6 files changed, 39 insertions(+), 87 deletions(-) delete mode 100644 src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs create mode 100644 src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 949b8999a..824fd0ec7 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -217,7 +217,7 @@ namespace SixLabors.ImageSharp /// /// To ensure the memory is contiguous, should be initialized /// with a that enforces larger contiguous buffers. - /// See . + /// See . /// /// WARNING: Disposing or leaking the underlying image while still working with it's /// might lead to memory corruption. diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 6be5c50fa..cc97d3b99 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -22,15 +22,15 @@ namespace SixLabors.ImageSharp.Memory /// /// The . public static MemoryAllocator CreateDefault() => - new UniformUnmanagedMemoryPoolMemoryAllocator(null, null); + new UniformUnmanagedMemoryPoolMemoryAllocator(null); /// /// Creates the default using the provided options. /// - /// The . + /// The . /// The . - public static MemoryAllocator CreateDefault(MemoryAllocatorOptions options) => - new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes, options.MinimumContiguousBlockSizeBytes); + public static MemoryAllocator CreateDefault(MemoryAllocatorSettings settings) => + new UniformUnmanagedMemoryPoolMemoryAllocator(settings.MaximumPoolSizeMegabytes); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs deleted file mode 100644 index acdf36c6a..000000000 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Defines options for creating the default . - /// - public class MemoryAllocatorOptions - { - private int? maximumPoolSizeMegabytes; - private int? minimumContiguousBlockSizeBytes; - - /// - /// Gets or sets a value defining the maximum size of the 's internal memory pool - /// in Megabytes. means platform default. - /// - public int? MaximumPoolSizeMegabytes - { - get => this.maximumPoolSizeMegabytes; - set - { - if (value.HasValue) - { - Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes)); - } - - this.maximumPoolSizeMegabytes = value; - } - } - - /// - /// Gets or sets a value defining the minimum contiguous block size when allocating buffers for - /// , or . - /// means platform default. - /// - /// - /// Overriding this value is useful for interop scenarios - /// ensuring succeeds. - /// - public int? MinimumContiguousBlockSizeBytes - { - get => this.minimumContiguousBlockSizeBytes; - set - { - if (value.HasValue) - { - // It doesn't make sense to set this to small values in practice. - // Defining an arbitrary minimum of 65536. - Guard.MustBeGreaterThanOrEqualTo(value.Value, 65536, nameof(this.MaximumPoolSizeMegabytes)); - } - - this.minimumContiguousBlockSizeBytes = value; - } - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs new file mode 100644 index 000000000..274e1739c --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs @@ -0,0 +1,31 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + /// + /// Defines options for creating the default . + /// + public class MemoryAllocatorSettings + { + private int? maximumPoolSizeMegabytes; + + /// + /// Gets or sets a value defining the maximum size of the 's internal memory pool + /// in Megabytes. means platform default. + /// + public int? MaximumPoolSizeMegabytes + { + get => this.maximumPoolSizeMegabytes; + set + { + if (value.HasValue) + { + Guard.MustBeGreaterThanOrEqualTo(value.Value, 0, nameof(this.MaximumPoolSizeMegabytes)); + } + + this.maximumPoolSizeMegabytes = value; + } + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 8d8e8e3df..3aa2bbf83 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -25,11 +25,11 @@ namespace SixLabors.ImageSharp.Memory private UniformUnmanagedMemoryPool pool; private readonly UnmanagedMemoryAllocator nonPoolAllocator; - public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes, int? minimumContiguousBlockBytes) + public UniformUnmanagedMemoryPoolMemoryAllocator(int? maxPoolSizeMegabytes) : this( - minimumContiguousBlockBytes.HasValue ? minimumContiguousBlockBytes.Value : DefaultContiguousPoolBlockSizeBytes, + DefaultContiguousPoolBlockSizeBytes, maxPoolSizeMegabytes.HasValue ? (long)maxPoolSizeMegabytes.Value * OneMegabyte : GetDefaultMaxPoolSizeBytes(), - minimumContiguousBlockBytes.HasValue ? Math.Max(minimumContiguousBlockBytes.Value, DefaultNonPoolBlockSizeBytes) : DefaultNonPoolBlockSizeBytes) + DefaultNonPoolBlockSizeBytes) { } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 70eba2e6a..9a98345b0 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -145,28 +145,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void MemoryAllocator_CreateDefault_WithOptions_CanForceContiguousAllocation(bool poolAllocation) - { - RemoteExecutor.Invoke(RunTest, poolAllocation.ToString()).Dispose(); - - static void RunTest(string poolAllocationStr) - { - int fortyEightMegabytes = 48 * (1 << 20); - var allocator = MemoryAllocator.CreateDefault(new MemoryAllocatorOptions() - { - MaximumPoolSizeMegabytes = bool.Parse(poolAllocationStr) ? 64 : 0, - MinimumContiguousBlockSizeBytes = fortyEightMegabytes - }); - - MemoryGroup g = allocator.AllocateGroup(fortyEightMegabytes, 1024); - Assert.Equal(1, g.Count); - Assert.Equal(fortyEightMegabytes, g.TotalLength); - } - } - [Theory] [InlineData(true)] [InlineData(false)] From 3740471d7b6dabc5dbd598bf1207accede987d24 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 28 Oct 2021 22:08:54 +0200 Subject: [PATCH 020/228] MemoryAllocator.Create & tests --- src/ImageSharp/Configuration.cs | 2 +- .../Memory/Allocators/MemoryAllocator.cs | 4 +- .../PackBitsTiffCompressionTests.cs | 2 +- .../Formats/Tiff/TiffEncoderHeaderTests.cs | 2 +- ...niformUnmanagedPoolMemoryAllocatorTests.cs | 37 ++++++++++++++++++- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 3d9552e42..818fb8193 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the that is currently in use. /// - public MemoryAllocator MemoryAllocator { get; set; } = MemoryAllocator.CreateDefault(); + public MemoryAllocator MemoryAllocator { get; set; } = MemoryAllocator.Create(); /// /// Gets the maximum header size of all the formats. diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index cc97d3b99..ec0771a48 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Memory /// Creates a default instance of a optimized for the executing platform. /// /// The . - public static MemoryAllocator CreateDefault() => + public static MemoryAllocator Create() => new UniformUnmanagedMemoryPoolMemoryAllocator(null); /// @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Memory /// /// The . /// The . - public static MemoryAllocator CreateDefault(MemoryAllocatorSettings settings) => + public static MemoryAllocator Create(MemoryAllocatorSettings settings) => new UniformUnmanagedMemoryPoolMemoryAllocator(settings.MaximumPoolSizeMegabytes); /// diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs index 0f9e468bd..bbca2610e 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression using var stream = new BufferedReadStream(Configuration.Default, memoryStream); byte[] buffer = new byte[expectedResult.Length]; - using var decompressor = new PackBitsTiffCompression(MemoryAllocator.CreateDefault(), default, default); + using var decompressor = new PackBitsTiffCompression(MemoryAllocator.Create(), default, default); decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer); Assert.Equal(expectedResult, buffer); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs index 613cf3211..b68670f1f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Trait("Format", "Tiff")] public class TiffEncoderHeaderTests { - private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.CreateDefault(); + private static readonly MemoryAllocator MemoryAllocator = MemoryAllocator.Create(); private static readonly Configuration Configuration = Configuration.Default; private static readonly ITiffEncoderOptions Options = new TiffEncoder(); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 9a98345b0..f9376e27c 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; @@ -130,13 +131,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Fact] - public void MemoryAllocator_CreateDefault_WithoutOptions_AllocatesDiscontiguousMemory() + public void MemoryAllocator_Create_WithoutSettings_AllocatesDiscontiguousMemory() { RemoteExecutor.Invoke(RunTest).Dispose(); static void RunTest() { - var allocator = MemoryAllocator.CreateDefault(); + var allocator = MemoryAllocator.Create(); long sixteenMegabytes = 16 * (1 << 20); // Should allocate 4 times 4MB discontiguos blocks: @@ -145,6 +146,38 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } + [Fact] + public void MemoryAllocator_Create_LimitPoolSize() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var allocator = MemoryAllocator.Create(new MemoryAllocatorSettings() + { + MaximumPoolSizeMegabytes = 8 + }); + + MemoryGroup g0 = allocator.AllocateGroup(B(8), 1024); + MemoryGroup g1 = allocator.AllocateGroup(B(8), 1024); + ref byte r0 = ref MemoryMarshal.GetReference(g0[0].Span); + ref byte r1 = ref MemoryMarshal.GetReference(g1[0].Span); + + g0.Dispose(); + g1.Dispose(); + + MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); + MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); + ref byte r2 = ref MemoryMarshal.GetReference(g2[0].Span); + ref byte r3 = ref MemoryMarshal.GetReference(g3[0].Span); + + Assert.True(Unsafe.AreSame(ref r0, ref r2)); + Assert.False(Unsafe.AreSame(ref r1, ref r3)); + } + + static long B(int value) => value << 20; + } + [Theory] [InlineData(true)] [InlineData(false)] From 003e51e98f8820f28f9420648c3087c676c00162 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 28 Oct 2021 22:22:19 +0200 Subject: [PATCH 021/228] MemoryAllocator.Default --- src/ImageSharp/Configuration.cs | 2 +- .../Memory/Allocators/MemoryAllocator.cs | 21 ++++++++++++ tests/ImageSharp.Tests/ConfigurationTests.cs | 32 ++++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 818fb8193..574a6912f 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -126,7 +126,7 @@ namespace SixLabors.ImageSharp /// /// Gets or sets the that is currently in use. /// - public MemoryAllocator MemoryAllocator { get; set; } = MemoryAllocator.Create(); + public MemoryAllocator MemoryAllocator { get; set; } = MemoryAllocator.Default; /// /// Gets the maximum header size of all the formats. diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index ec0771a48..d9ebb7cb9 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -11,6 +11,27 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { + private static MemoryAllocator defaultMemoryAllocator = Create(); + + /// + /// Gets or sets the default global instance for the current process. + /// + /// + /// Since is lazy-initialized, setting the value of + /// will only override 's + /// before the first read of the property. + /// After that, a manual assigment of is necessary. + /// + public static MemoryAllocator Default + { + get => defaultMemoryAllocator; + set + { + Guard.NotNull(value, nameof(Default)); + defaultMemoryAllocator = value; + } + } + /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. /// diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 3ad8ef2f8..cabe15cb6 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -3,10 +3,12 @@ using System; using System.Linq; +using Microsoft.DotNet.RemoteExecutor; using Moq; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.IO; - +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Tests.Memory; using Xunit; // ReSharper disable InconsistentNaming @@ -146,5 +148,33 @@ namespace SixLabors.ImageSharp.Tests Assert.Throws( () => config.StreamProcessingBufferSize = 0); } + + [Fact] + public void InheritsDefaultMemoryAllocatorInstance() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + MemoryAllocator allocator = new TestMemoryAllocator(); + MemoryAllocator.Default = allocator; + + var c1 = new Configuration(); + var c2 = new Configuration(new MockConfigurationModule()); + var c3 = Configuration.CreateDefaultInstance(); + + Assert.Same(allocator, Configuration.Default.MemoryAllocator); + Assert.Same(allocator, c1.MemoryAllocator); + Assert.Same(allocator, c2.MemoryAllocator); + Assert.Same(allocator, c3.MemoryAllocator); + } + } + + private class MockConfigurationModule : IConfigurationModule + { + public void Configure(Configuration configuration) + { + } + } } } From f81008ef548d6d41cae122f320102152b5cda481 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 28 Oct 2021 23:58:44 +0200 Subject: [PATCH 022/228] ProcessPixelRows tests --- src/ImageSharp/PixelAccessor{TPixel}.cs | 5 ++ tests/ImageSharp.Tests/Image/ImageTests.cs | 81 ++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs index 0a7f408af..1f4dbf2eb 100644 --- a/src/ImageSharp/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/PixelAccessor{TPixel}.cs @@ -45,6 +45,11 @@ namespace SixLabors.ImageSharp public ref struct PixelAccessor where TPixel : unmanaged, IPixel { + /// + /// Gets the width of the backing . + /// + public int Width { get; } + /// /// Gets the height of the backing . /// diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 671fc2b90..709cecc97 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -176,6 +176,87 @@ namespace SixLabors.ImageSharp.Tests } } + + public class ProcessPixelRows + { + [Fact] + public void PixelAccessorDimensionsAreCorrect() + { + using var image = new Image(123, 456); + image.ProcessPixelRows(accessor => + { + Assert.Equal(123, accessor.Width); + Assert.Equal(456, accessor.Height); + }); + } + + [Fact] + public void WritesImagePixels() + { + using var image = new Image(256, 256); + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + }); + + Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * y, actual); + } + } + } + + [Fact] + public void CopyImagePixels() + { + using var img1 = new Image(256, 256); + Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + + using var img2 = new Image(256, 256); + + img1.ProcessPixelRows(img2, (accessor1, accessor2) => + { + for (int y = 0; y < accessor1.Height; y++) + { + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + row1.CopyTo(row2); + } + }); + + buffer = img2.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual); + } + } + } + } + public class Dispose { private readonly Configuration configuration = Configuration.CreateDefaultInstance(); From ce1ac2cd70713e23d4da632232047143983e5c0f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 28 Oct 2021 23:59:15 +0200 Subject: [PATCH 023/228] pass pinnable correctly --- .../UniformUnmanagedMemoryPool.Buffer{T}.cs | 2 +- .../Allocators/Internals/UnmanagedBuffer{T}.cs | 2 +- .../Memory/UnmanagedMemoryManager{T}.cs | 2 +- .../Image/ImageTests.WrapMemory.cs | 2 +- .../UniformUnmanagedPoolMemoryAllocatorTests.cs | 17 +++++++++++++++++ .../TestUtilities/TestMemoryAllocator.cs | 4 ++-- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs index 84b93496d..b2a19fb60 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Memory.Internals this.BufferHandle.DangerousAddRef(ref unused); void* pbData = Unsafe.Add(this.Pointer, elementIndex); - return new MemoryHandle(pbData); + return new MemoryHandle(pbData, pinnable: this); } /// diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 9755f3744..14a2f3b5b 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Memory.Internals this.bufferHandle.DangerousAddRef(ref unused); void* pbData = Unsafe.Add(this.Pointer, elementIndex); - return new MemoryHandle(pbData); + return new MemoryHandle(pbData, pinnable: this); } /// diff --git a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs index 58eaee320..8e8d1aa2f 100644 --- a/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs +++ b/src/ImageSharp/Memory/UnmanagedMemoryManager{T}.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Memory /// public override MemoryHandle Pin(int elementIndex = 0) { - return new MemoryHandle(((T*)this.pointer) + elementIndex); + return new MemoryHandle(((T*)this.pointer) + elementIndex, pinnable: this); } /// diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 7fae29a85..c7a9b67a7 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Tests public override unsafe MemoryHandle Pin(int elementIndex = 0) { void* ptr = (void*)this.bmpData.Scan0; - return new MemoryHandle(ptr); + return new MemoryHandle(ptr, pinnable: this); } public override void Unpin() diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index f9376e27c..9a55b3b82 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -130,6 +130,23 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal(1, g.Count); } + [Fact] + public unsafe void Allocate_MemoryIsPinnableMultipleTimes() + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(null); + using IMemoryOwner memoryOwner = allocator.Allocate(100); + + using (MemoryHandle pin = memoryOwner.Memory.Pin()) + { + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); + } + + using (MemoryHandle pin = memoryOwner.Memory.Pin()) + { + Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); + } + } + [Fact] public void MemoryAllocator_Create_WithoutSettings_AllocatesDiscontiguousMemory() { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index d60449493..c644b2fbf 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -146,12 +146,12 @@ namespace SixLabors.ImageSharp.Tests.Memory } void* ptr = (void*)this.pinHandle.AddrOfPinnedObject(); - return new MemoryHandle(ptr, this.pinHandle); + return new MemoryHandle(ptr, pinnable: this); } public override void Unpin() { - throw new NotImplementedException(); + this.pinHandle.Free(); } /// From 1df9e25232f06149f4e7f6bc991a3e880c864a30 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 20:29:22 +0200 Subject: [PATCH 024/228] ProcessPixelRows --- src/ImageSharp/ImageFrame{TPixel}.cs | 85 +++++- src/ImageSharp/Image{TPixel}.cs | 86 +++++- .../UniformUnmanagedMemoryPool.Buffer{T}.cs | 30 +- .../Internals/UnmanagedBuffer{T}.cs | 25 +- .../MemoryGroup{T}.Owned.cs | 25 ++ .../DiscontiguousBuffers/MemoryGroup{T}.cs | 8 + src/ImageSharp/PixelAccessor{TPixel}.cs | 15 +- .../ImageSharp.Tests/Image/ImageFrameTests.cs | 40 +++ tests/ImageSharp.Tests/Image/ImageTests.cs | 97 ++----- .../Image/ProcessPixelRowsTestBase.cs | 260 ++++++++++++++++++ 10 files changed, 549 insertions(+), 122 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 824fd0ec7..8bcdec65b 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -195,21 +195,94 @@ namespace SixLabors.ImageSharp return this.PixelBuffer.GetRowSpan(rowIndex); } - public void ProcessPixelRows(PixelAccessorAction processPixels) => throw new NotImplementedException(); + /// + /// Execute to process image pixels in a safe and efficient manner. + /// + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) + { + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + try + { + var accessor = new PixelAccessor(this.PixelBuffer); + processPixels(accessor); + } + finally + { + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. public void ProcessPixelRows( - Image image2, + ImageFrame frame2, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + processPixels(accessor1, accessor2); + } + finally + { + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + /// + /// Execute to process pixels of multiple image frames in a safe and efficient manner. + /// + /// The second image frame. + /// The third image frame. + /// The defining the pixel operations. + /// The pixel type of the second image frame. + /// The pixel type of the third image frame. public void ProcessPixelRows( - Image image2, - Image image3, + ImageFrame frame2, + ImageFrame frame3, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel where TPixel3 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(frame2, nameof(frame2)); + Guard.NotNull(frame3, nameof(frame3)); + Guard.NotNull(processPixels, nameof(processPixels)); + + this.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + frame3.PixelBuffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(this.PixelBuffer); + var accessor2 = new PixelAccessor(frame2.PixelBuffer); + var accessor3 = new PixelAccessor(frame3.PixelBuffer); + processPixels(accessor1, accessor2, accessor3); + } + finally + { + frame3.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + frame2.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + this.PixelBuffer.FastMemoryGroup.DecreaseRefCounts(); + } + } /// /// Gets the representation of the pixels as a in the source image's pixel format diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index c1a084611..899d69bb4 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -204,21 +204,101 @@ namespace SixLabors.ImageSharp } } - public void ProcessPixelRows(PixelAccessorAction processPixels) => throw new NotImplementedException(); + /// + /// Execute to process image pixels in a safe and efficient manner. + /// + /// The defining the pixel operations. + public void ProcessPixelRows(PixelAccessorAction processPixels) + { + Guard.NotNull(processPixels, nameof(processPixels)); + Buffer2D buffer = this.Frames.RootFrame.PixelBuffer; + buffer.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor = new PixelAccessor(buffer); + processPixels(accessor); + } + finally + { + buffer.FastMemoryGroup.DecreaseRefCounts(); + } + } + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The defining the pixel operations. + /// The pixel type of the second image. public void ProcessPixelRows( Image image2, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(processPixels, nameof(processPixels)); + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + processPixels(accessor1, accessor2); + } + finally + { + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); + } + } + + /// + /// Execute to process pixels of multiple images in a safe and efficient manner. + /// + /// The second image. + /// The third image. + /// The defining the pixel operations. + /// The pixel type of the second image. + /// The pixel type of the third image. public void ProcessPixelRows( Image image2, Image image3, PixelAccessorAction processPixels) where TPixel2 : unmanaged, IPixel where TPixel3 : unmanaged, IPixel - => throw new NotImplementedException(); + { + Guard.NotNull(image2, nameof(image2)); + Guard.NotNull(image3, nameof(image3)); + Guard.NotNull(processPixels, nameof(processPixels)); + + Buffer2D buffer1 = this.Frames.RootFrame.PixelBuffer; + Buffer2D buffer2 = image2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = image3.Frames.RootFrame.PixelBuffer; + + buffer1.FastMemoryGroup.IncreaseRefCounts(); + buffer2.FastMemoryGroup.IncreaseRefCounts(); + buffer3.FastMemoryGroup.IncreaseRefCounts(); + + try + { + var accessor1 = new PixelAccessor(buffer1); + var accessor2 = new PixelAccessor(buffer2); + var accessor3 = new PixelAccessor(buffer3); + processPixels(accessor1, accessor2, accessor3); + } + finally + { + buffer3.FastMemoryGroup.DecreaseRefCounts(); + buffer2.FastMemoryGroup.DecreaseRefCounts(); + buffer1.FastMemoryGroup.DecreaseRefCounts(); + } + } /// /// Gets the representation of the pixels as a of contiguous memory diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs index b2a19fb60..8f2d982ef 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -9,38 +9,14 @@ namespace SixLabors.ImageSharp.Memory.Internals { internal partial class UniformUnmanagedMemoryPool { - public unsafe class Buffer : MemoryManager + public class Buffer : UnmanagedBuffer where T : struct { private UniformUnmanagedMemoryPool pool; - private readonly int length; public Buffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) - { + : base(bufferHandle, length) => this.pool = pool; - this.BufferHandle = bufferHandle; - this.length = length; - } - - private void* Pointer => (void*)this.BufferHandle.DangerousGetHandle(); - - protected UnmanagedMemoryHandle BufferHandle { get; private set; } - - public override Span GetSpan() => new Span(this.Pointer, this.length); - - /// - public override MemoryHandle Pin(int elementIndex = 0) - { - // Will be released in Unpin - bool unused = false; - this.BufferHandle.DangerousAddRef(ref unused); - - void* pbData = Unsafe.Add(this.Pointer, elementIndex); - return new MemoryHandle(pbData, pinnable: this); - } - - /// - public override void Unpin() => this.BufferHandle.DangerousRelease(); /// protected override void Dispose(bool disposing) @@ -62,7 +38,7 @@ namespace SixLabors.ImageSharp.Memory.Internals } } - public class FinalizableBuffer : Buffer + public sealed class FinalizableBuffer : Buffer where T : struct { public FinalizableBuffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 14a2f3b5b..c343e44a8 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -13,10 +13,9 @@ namespace SixLabors.ImageSharp.Memory.Internals /// access to unmanaged buffers allocated by . /// /// The element type. - internal sealed unsafe class UnmanagedBuffer : MemoryManager + internal unsafe class UnmanagedBuffer : MemoryManager where T : struct { - private readonly UnmanagedMemoryHandle bufferHandle; private readonly int lengthInElements; /// @@ -24,41 +23,47 @@ namespace SixLabors.ImageSharp.Memory.Internals /// /// The number of elements to allocate. public UnmanagedBuffer(int lengthInElements) + : this(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()), lengthInElements) + { + } + + protected UnmanagedBuffer(UnmanagedMemoryHandle bufferHandle, int lengthInElements) { this.lengthInElements = lengthInElements; - this.bufferHandle = UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()); + this.BufferHandle = bufferHandle; } - private void* Pointer => (void*)this.bufferHandle.DangerousGetHandle(); + public UnmanagedMemoryHandle BufferHandle { get; protected set; } + + private void* Pointer => (void*)this.BufferHandle.DangerousGetHandle(); - public override Span GetSpan() - => new Span(this.Pointer, this.lengthInElements); + public override Span GetSpan() => new(this.Pointer, this.lengthInElements); /// public override MemoryHandle Pin(int elementIndex = 0) { // Will be released in Unpin bool unused = false; - this.bufferHandle.DangerousAddRef(ref unused); + this.BufferHandle.DangerousAddRef(ref unused); void* pbData = Unsafe.Add(this.Pointer, elementIndex); return new MemoryHandle(pbData, pinnable: this); } /// - public override void Unpin() => this.bufferHandle.DangerousRelease(); + public override void Unpin() => this.BufferHandle.DangerousRelease(); /// protected override void Dispose(bool disposing) { - if (this.bufferHandle.IsInvalid) + if (this.BufferHandle.IsInvalid) { return; } if (disposing) { - this.bufferHandle.Dispose(); + this.BufferHandle.Dispose(); } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 7c0c3b764..b5771f0c7 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -86,6 +86,31 @@ namespace SixLabors.ImageSharp.Memory return new MemoryGroupEnumerator(this); } + public override void IncreaseRefCounts() + { + this.EnsureNotDisposed(); + bool dummy = default; + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + if (memoryOwner is UnmanagedBuffer unmanagedBuffer) + { + unmanagedBuffer.BufferHandle?.DangerousAddRef(ref dummy); + } + } + } + + public override void DecreaseRefCounts() + { + this.EnsureNotDisposed(); + foreach (IMemoryOwner memoryOwner in this.memoryOwners) + { + if (memoryOwner is UnmanagedBuffer unmanagedBuffer) + { + unmanagedBuffer.BufferHandle?.DangerousRelease(); + } + } + } + /// IEnumerator> IEnumerable>.GetEnumerator() { diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 9b00d9cbf..f517482d7 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -270,5 +270,13 @@ namespace SixLabors.ImageSharp.Memory return false; } } + + public virtual void IncreaseRefCounts() + { + } + + public virtual void DecreaseRefCounts() + { + } } } diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs index 1f4dbf2eb..65ab5dbda 100644 --- a/src/ImageSharp/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/PixelAccessor{TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp @@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp public delegate void PixelAccessorAction( PixelAccessor pixelAccessor1, PixelAccessor pixelAccessor2, - PixelAccessor pixelAccessor3) + PixelAccessor pixelAccessor3) where TPixel1 : unmanaged, IPixel where TPixel2 : unmanaged, IPixel where TPixel3 : unmanaged, IPixel; @@ -45,23 +46,27 @@ namespace SixLabors.ImageSharp public ref struct PixelAccessor where TPixel : unmanaged, IPixel { + private Buffer2D buffer; + + internal PixelAccessor(Buffer2D buffer) => this.buffer = buffer; + /// /// Gets the width of the backing . /// - public int Width { get; } + public int Width => this.buffer.Width; /// /// Gets the height of the backing . /// - public int Height { get; } + public int Height => this.buffer.Height; /// /// Gets the representation of the pixels as a of contiguous memory /// at row beginning from the first pixel on that row. /// - /// The row. + /// The row index. /// The . /// Thrown when row index is out of range. - public Span GetRowSpan(int rowIndex) => throw new NotImplementedException(); + public Span GetRowSpan(int rowIndex) => this.buffer.GetRowSpan(rowIndex); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index bbe1a2335..344ebac95 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -96,5 +96,45 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal("y", ex.ParamName); } } + + public class ProcessPixelRows : ProcessPixelRowsTestBase + { + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.Frames.RootFrame.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows(image2.Frames.RootFrame, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.Frames.RootFrame.ProcessPixelRows( + image2.Frames.RootFrame, + image3.Frames.RootFrame, + processPixels); + + [Fact] + public void NullReference_Throws() + { + using var img = new Image(1, 1); + ImageFrame frame = img.Frames.RootFrame; + + Assert.Throws(() => frame.ProcessPixelRows(null)); + + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, (_, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); + + Assert.Throws(() => frame.ProcessPixelRows((ImageFrame)null, frame, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, (ImageFrame)null, (_, _, _) => { })); + Assert.Throws(() => frame.ProcessPixelRows(frame, frame, null)); + } + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 709cecc97..b632f4216 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -176,84 +176,39 @@ namespace SixLabors.ImageSharp.Tests } } - - public class ProcessPixelRows + public class ProcessPixelRows : ProcessPixelRowsTestBase { - [Fact] - public void PixelAccessorDimensionsAreCorrect() - { - using var image = new Image(123, 456); - image.ProcessPixelRows(accessor => - { - Assert.Equal(123, accessor.Width); - Assert.Equal(456, accessor.Height); - }); - } + protected override void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) => + image.ProcessPixelRows(processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, processPixels); + + protected override void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) => + image1.ProcessPixelRows(image2, image3, processPixels); [Fact] - public void WritesImagePixels() + public void NullReference_Throws() { - using var image = new Image(256, 256); - image.ProcessPixelRows(accessor => - { - for (int y = 0; y < accessor.Height; y++) - { - Span row = accessor.GetRowSpan(y); - for (int x = 0; x < row.Length; x++) - { - row[x] = new L16((ushort)(x * y)); - } - } - }); - - Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.GetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * y, actual); - } - } - } + using var img = new Image(1, 1); - [Fact] - public void CopyImagePixels() - { - using var img1 = new Image(256, 256); - Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.GetRowSpan(y); - for (int x = 0; x < 256; x++) - { - row[x] = new L16((ushort)(x * y)); - } - } + Assert.Throws(() => img.ProcessPixelRows(null)); - using var img2 = new Image(256, 256); + Assert.Throws(() => img.ProcessPixelRows((Image)null, (_, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); - img1.ProcessPixelRows(img2, (accessor1, accessor2) => - { - for (int y = 0; y < accessor1.Height; y++) - { - Span row1 = accessor1.GetRowSpan(y); - Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); - row1.CopyTo(row2); - } - }); - - buffer = img2.Frames.RootFrame.PixelBuffer; - for (int y = 0; y < 256; y++) - { - Span row = buffer.GetRowSpan(y); - for (int x = 0; x < 256; x++) - { - int actual = row[x].PackedValue; - Assert.Equal(x * (256 - y - 1), actual); - } - } + Assert.Throws(() => img.ProcessPixelRows((Image)null, img, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, (Image)null, (_, _, _) => { })); + Assert.Throws(() => img.ProcessPixelRows(img, img, null)); } } diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs new file mode 100644 index 000000000..ac36c285c --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -0,0 +1,260 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests +{ + public abstract class ProcessPixelRowsTestBase + { + protected abstract void ProcessPixelRowsImpl( + Image image, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + protected abstract void ProcessPixelRowsImpl( + Image image1, + Image image2, + Image image3, + PixelAccessorAction processPixels) + where TPixel : unmanaged, IPixel; + + [Fact] + public void PixelAccessorDimensionsAreCorrect() + { + using var image = new Image(123, 456); + this.ProcessPixelRowsImpl(image, accessor => + { + Assert.Equal(123, accessor.Width); + Assert.Equal(456, accessor.Height); + }); + } + + [Fact] + public void WriteImagePixels_SingleImage() + { + using var image = new Image(256, 256); + this.ProcessPixelRowsImpl(image, accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + }); + + Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * y, actual); + } + } + } + + [Fact] + public void WriteImagePixels_MultiImage2() + { + using var img1 = new Image(256, 256); + Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + + using var img2 = new Image(256, 256); + + this.ProcessPixelRowsImpl(img1, img2, (accessor1, accessor2) => + { + for (int y = 0; y < accessor1.Height; y++) + { + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + row1.CopyTo(row2); + } + }); + + buffer = img2.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual = row[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual); + } + } + } + + [Fact] + public void WriteImagePixels_MultiImage3() + { + using var img1 = new Image(256, 256); + Buffer2D buffer2 = img1.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row = buffer2.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + row[x] = new L16((ushort)(x * y)); + } + } + + using var img2 = new Image(256, 256); + using var img3 = new Image(256, 256); + + this.ProcessPixelRowsImpl(img1, img2, img3, (accessor1, accessor2, accessor3) => + { + for (int y = 0; y < accessor1.Height; y++) + { + Span row1 = accessor1.GetRowSpan(y); + Span row2 = accessor2.GetRowSpan(accessor2.Height - y - 1); + Span row3 = accessor3.GetRowSpan(y); + row1.CopyTo(row2); + row1.CopyTo(row3); + } + }); + + buffer2 = img2.Frames.RootFrame.PixelBuffer; + Buffer2D buffer3 = img3.Frames.RootFrame.PixelBuffer; + for (int y = 0; y < 256; y++) + { + Span row2 = buffer2.GetRowSpan(y); + Span row3 = buffer3.GetRowSpan(y); + for (int x = 0; x < 256; x++) + { + int actual2 = row2[x].PackedValue; + int actual3 = row3[x].PackedValue; + Assert.Equal(x * (256 - y - 1), actual2); + Assert.Equal(x * y, actual3); + } + } + } + + [Fact] + public void RetainsUnmangedBuffers1() + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + + static void RunTest(string testTypeName) + { + var buffer = new UnmanagedBuffer(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer); + Configuration.Default.MemoryAllocator = allocator; + + var image = new Image(10, 10); + + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => + { + buffer.BufferHandle.Dispose(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + }); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void RetainsUnmangedBuffers2() + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + + static void RunTest(string testTypeName) + { + var buffer1 = new UnmanagedBuffer(100); + var buffer2 = new UnmanagedBuffer(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2); + Configuration.Default.MemoryAllocator = allocator; + + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); + + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => + { + buffer1.BufferHandle.Dispose(); + buffer2.BufferHandle.Dispose(); + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + }); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Fact] + public void RetainsUnmangedBuffers3() + { + RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + + static void RunTest(string testTypeName) + { + var buffer1 = new UnmanagedBuffer(100); + var buffer2 = new UnmanagedBuffer(100); + var buffer3 = new UnmanagedBuffer(100); + var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2, buffer3); + Configuration.Default.MemoryAllocator = allocator; + + var image1 = new Image(10, 10); + var image2 = new Image(10, 10); + var image3 = new Image(10, 10); + + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => + { + buffer1.BufferHandle.Dispose(); + buffer2.BufferHandle.Dispose(); + buffer3.BufferHandle.Dispose(); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + }); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + private static ProcessPixelRowsTestBase GetTest(string testTypeName) + { + Type type = typeof(ProcessPixelRowsTestBase).Assembly.GetType(testTypeName); + return (ProcessPixelRowsTestBase)Activator.CreateInstance(type); + } + + private class MockUnmanagedMemoryAllocator : MemoryAllocator + where T1 : struct + { + private Stack> buffers = new(); + + public MockUnmanagedMemoryAllocator(params UnmanagedBuffer[] buffers) + { + foreach (UnmanagedBuffer buffer in buffers) + { + this.buffers.Push(buffer); + } + } + + protected internal override int GetBufferCapacityInBytes() => int.MaxValue; + + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => + (IMemoryOwner)this.buffers.Pop(); + } + } +} From 409cfb104d84b63438beb125b9506a44c5495bf2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 20:32:11 +0200 Subject: [PATCH 025/228] DangerousGetPixelRowMemory --- src/ImageSharp/Advanced/AdvancedImageExtensions.cs | 4 ++-- .../Advanced/AdvancedImageExtensionsTests.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index 54a773be0..829c6155d 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -143,7 +143,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - public static Memory GetPixelRowMemory(this ImageFrame source, int rowIndex) + public static Memory DangerousGetPixelRowMemory(this ImageFrame source, int rowIndex) where TPixel : unmanaged, IPixel { Guard.NotNull(source, nameof(source)); @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Advanced /// The source. /// The row. /// The - public static Memory GetPixelRowMemory(this Image source, int rowIndex) + public static Memory DangerousGetPixelRowMemory(this Image source, int rowIndex) where TPixel : unmanaged, IPixel { Guard.NotNull(source, nameof(source)); diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 6031227bd..742997b1a 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced for (int y = 0; y < image.Height; y++) { // Act: - Memory rowMemory = image.GetPixelRowMemory(y); + Memory rowMemory = image.DangerousGetPixelRowMemory(y); Span span = rowMemory.Span; // Assert: @@ -134,8 +134,8 @@ namespace SixLabors.ImageSharp.Tests.Advanced { using Image image = provider.GetImage(); - Memory memory3 = image.GetPixelRowMemory(3); - Memory memory10 = image.GetPixelRowMemory(10); + Memory memory3 = image.DangerousGetPixelRowMemory(3); + Memory memory10 = image.DangerousGetPixelRowMemory(10); image.Mutate(c => c.Resize(8, 8)); @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced using Image image = provider.GetImage(); - Memory memory = image.GetPixelRowMemory(image.Height - 1); + Memory memory = image.DangerousGetPixelRowMemory(image.Height - 1); Span span = image.GetPixelRowSpan(image.Height - 1); Assert.True(span == memory.Span); From e1f15bc626122690d7be0e611c8d6435e2878ec2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 21:00:14 +0200 Subject: [PATCH 026/228] TryGetSinglePixelSpan -> DangerousTryGetSinglePixelMemory --- src/ImageSharp/ImageFrame{TPixel}.cs | 19 +++++++------- src/ImageSharp/Image{TPixel}.cs | 26 +++++++++++-------- .../Advanced/AdvancedImageExtensionsTests.cs | 2 +- .../Drawing/DrawImageTests.cs | 4 +-- .../Formats/Gif/GifDecoderTests.cs | 4 +-- .../Formats/Tga/TgaTestUtils.cs | 10 ++++--- .../Formats/Tiff/TiffTestUtils.cs | 10 ++++--- .../ImageFrameCollectionTests.Generic.cs | 24 ++++++++--------- .../ImageFrameCollectionTests.NonGeneric.cs | 10 +++---- .../Image/ImageTests.WrapMemory.cs | 25 +++++++++--------- tests/ImageSharp.Tests/Image/ImageTests.cs | 14 +++++----- .../Image/LargeImageIntegrationTests.cs | 10 +++---- .../Transforms/AffineTransformTests.cs | 4 +-- .../Processors/Transforms/ResizeTests.cs | 4 +-- .../TestUtilities/TestImageExtensions.cs | 19 ++++++++++---- .../TestUtilities/TestUtils.cs | 4 +-- 16 files changed, 102 insertions(+), 87 deletions(-) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 8bcdec65b..ff1e44e6c 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -285,28 +285,27 @@ namespace SixLabors.ImageSharp } /// - /// Gets the representation of the pixels as a in the source image's pixel format + /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. /// - /// To ensure the memory is contiguous, should be initialized - /// with a that enforces larger contiguous buffers. - /// See . + /// To ensure the memory is contiguous, should be set + /// to true, preferably on a non-global configuration instance (not ). /// - /// WARNING: Disposing or leaking the underlying image while still working with it's + /// WARNING: Disposing or leaking the underlying image while still working with the 's /// might lead to memory corruption. /// - /// The . - /// The . - public bool TryGetSinglePixelSpan(out Span span) + /// The referencing the image buffer. + /// The indicating the success. + public bool DangerousTryGetSinglePixelMemory(out Memory memory) { IMemoryGroup mg = this.GetPixelMemoryGroup(); if (mg.Count > 1) { - span = default; + memory = default; return false; } - span = mg.Single().Span; + memory = mg.Single(); return true; } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 899d69bb4..c4f1c278f 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -321,26 +321,30 @@ namespace SixLabors.ImageSharp } /// - /// Gets the representation of the pixels as a in the source image's pixel format + /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. + /// + /// To ensure the memory is contiguous, should be set + /// to true, preferably on a non-global configuration instance (not ). + /// + /// WARNING: Disposing or leaking the underlying image while still working with the 's + /// might lead to memory corruption. /// - /// The . - /// The . - public bool TryGetSinglePixelSpan(out Span span) + /// The referencing the image buffer. + /// The indicating the success. + public bool DangerousTryGetSinglePixelMemory(out Memory memory) { IMemoryGroup mg = this.GetPixelMemoryGroup(); - if (mg.Count == 1) + if (mg.Count > 1) { - span = mg[0].Span; - return true; + memory = default; + return false; } - span = default; - return false; + memory = mg.Single(); + return true; } - public bool DangerousTryGetSinglePixelMemory(out Memory memory) => throw new NotImplementedException(); - /// /// Clones the current image /// diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 742997b1a..35ef5d1c8 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced using Image image0 = provider.GetImage(); var targetBuffer = new TPixel[image0.Width * image0.Height]; - Assert.True(image0.TryGetSinglePixelSpan(out Span sourceBuffer)); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory sourceBuffer)); sourceBuffer.CopyTo(targetBuffer); diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index b426f4404..d10549b40 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -128,8 +128,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing using (Image background = provider.GetImage()) using (var overlay = new Image(50, 50)) { - Assert.True(overlay.TryGetSinglePixelSpan(out Span overlaySpan)); - overlaySpan.Fill(Color.Black); + Assert.True(overlay.DangerousTryGetSinglePixelMemory(out Memory overlayMem)); + overlayMem.Span.Fill(Color.Black); background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index c0df1e400..824ca535b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -165,9 +165,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif ImageFrame first = kumin1.Frames[i]; ImageFrame second = kumin2.Frames[i]; - Assert.True(second.TryGetSinglePixelSpan(out Span secondSpan)); + Assert.True(second.DangerousTryGetSinglePixelMemory(out Memory secondMemory)); - first.ComparePixelBufferTo(secondSpan); + first.ComparePixelBufferTo(secondMemory.Span); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs index c96777031..4de1b9a19 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } var testFile = TestFile.Create(path); - Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); if (useExactComparer) { ImageComparer.Exact.VerifySimilarity(magickImage, image); @@ -37,15 +37,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga } } - public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) + public static Image DecodeWithMagick(FileInfo fileInfo) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; using (var magickImage = new MagickImage(fileInfo)) { magickImage.AutoOrient(); var result = new Image(configuration, magickImage.Width, magickImage.Height); - Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); + Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) { @@ -54,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga PixelOperations.Instance.FromRgba32Bytes( configuration, data, - resultPixels, + resultPixels.Span, resultPixels.Length); } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs index eacadae2b..d5ddd0a40 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { var testFile = TestFile.Create(encodedImagePath); - Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath)); if (useExactComparer) { ImageComparer.Exact.VerifySimilarity(magickImage, image); @@ -34,14 +34,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } } - public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) + public static Image DecodeWithMagick(FileInfo fileInfo) where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel { + Configuration configuration = Configuration.Default.Clone(); + configuration.PreferContiguousImageBuffers = true; using var magickImage = new MagickImage(fileInfo); magickImage.AutoOrient(); var result = new Image(configuration, magickImage.Width, magickImage.Height); - Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); + Assert.True(result.DangerousTryGetSinglePixelMemory(out Memory resultPixels)); using IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe(); byte[] data = pixels.ToByteArray(PixelMapping.RGBA); @@ -49,7 +51,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff PixelOperations.Instance.FromRgba32Bytes( configuration, data, - resultPixels, + resultPixels.Span, resultPixels.Length); return result; diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs index 50b21ba8d..9ed276ebc 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.Generic.cs @@ -211,9 +211,9 @@ namespace SixLabors.ImageSharp.Tests using (Image cloned = img.Frames.CloneFrame(0)) { Assert.Equal(2, img.Frames.Count); - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); - cloned.ComparePixelBufferTo(imgSpan); + cloned.ComparePixelBufferTo(imgMem); } } } @@ -225,15 +225,15 @@ namespace SixLabors.ImageSharp.Tests { using (Image img = provider.GetImage()) { - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); - TPixel[] sourcePixelData = imgSpan.ToArray(); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMemory)); + TPixel[] sourcePixelData = imgMemory.ToArray(); using var imageFrame = new ImageFrame(Configuration.Default, 10, 10); using ImageFrame addedFrame = img.Frames.AddFrame(imageFrame); using (Image cloned = img.Frames.ExportFrame(0)) { Assert.Equal(1, img.Frames.Count); - cloned.ComparePixelBufferTo(sourcePixelData); + cloned.ComparePixelBufferTo(sourcePixelData.AsSpan()); } } } @@ -261,8 +261,8 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void AddFrameFromPixelData() { - Assert.True(this.Image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imgSpan)); - Rgba32[] pixelData = imgSpan.ToArray(); + Assert.True(this.Image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + Rgba32[] pixelData = imgMem.ToArray(); using ImageFrame addedFrame = this.Image.Frames.AddFrame(pixelData); Assert.Equal(2, this.Image.Frames.Count); } @@ -273,8 +273,8 @@ namespace SixLabors.ImageSharp.Tests using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); using ImageFrame addedFrame = this.Image.Frames.AddFrame(otherFrame); - Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); - addedFrame.ComparePixelBufferTo(otherFrameSpan); + Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); + addedFrame.ComparePixelBufferTo(otherFrameMem.Span); Assert.NotEqual(otherFrame, addedFrame); } @@ -284,8 +284,8 @@ namespace SixLabors.ImageSharp.Tests using var otherFrame = new ImageFrame(Configuration.Default, 10, 10); using ImageFrame addedFrame = this.Image.Frames.InsertFrame(0, otherFrame); - Assert.True(otherFrame.TryGetSinglePixelSpan(out Span otherFrameSpan)); - addedFrame.ComparePixelBufferTo(otherFrameSpan); + Assert.True(otherFrame.DangerousTryGetSinglePixelMemory(out Memory otherFrameMem)); + addedFrame.ComparePixelBufferTo(otherFrameMem.Span); Assert.NotEqual(otherFrame, addedFrame); } @@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.Tests IEnumerable> frames = image.Frames; foreach (ImageFrame frame in frames) { - Assert.True(frame.TryGetSinglePixelSpan(out Span span)); + Assert.True(frame.DangerousTryGetSinglePixelMemory(out Memory _)); } } diff --git a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs index b65615121..843546439 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameCollectionTests.NonGeneric.cs @@ -158,8 +158,8 @@ namespace SixLabors.ImageSharp.Tests var expectedClone = (Image)cloned; - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); - expectedClone.ComparePixelBufferTo(imgSpan); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + expectedClone.ComparePixelBufferTo(imgMem); } } } @@ -171,8 +171,8 @@ namespace SixLabors.ImageSharp.Tests { using (Image img = provider.GetImage()) { - Assert.True(img.TryGetSinglePixelSpan(out Span imgSpan)); - var sourcePixelData = imgSpan.ToArray(); + Assert.True(img.DangerousTryGetSinglePixelMemory(out Memory imgMem)); + TPixel[] sourcePixelData = imgMem.ToArray(); ImageFrameCollection nonGenericFrameCollection = img.Frames; @@ -182,7 +182,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(1, img.Frames.Count); var expectedClone = (Image)cloned; - expectedClone.ComparePixelBufferTo(sourcePixelData); + expectedClone.ComparePixelBufferTo(sourcePixelData.AsSpan()); } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index c7a9b67a7..9b0d64cc6 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -9,8 +9,10 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using Xunit; // ReSharper disable InconsistentNaming @@ -132,8 +134,8 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) { - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - ref Rgba32 pixel0 = ref imageSpan[0]; + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + ref Rgba32 pixel0 = ref imageMem.Span[0]; Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); Assert.Equal(cfg, image.GetConfiguration()); @@ -160,8 +162,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memory, bmp.Width, bmp.Height)) { Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - imageSpan.Fill(bg); + image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); @@ -196,8 +197,7 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(memoryManager, bmp.Width, bmp.Height)) { Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - imageSpan.Fill(bg); + image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); @@ -225,8 +225,8 @@ namespace SixLabors.ImageSharp.Tests using (var image = Image.WrapMemory(cfg, memory, 5, 5, metaData)) { - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - ref Rgba32 pixel0 = ref imageSpan[0]; + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + ref Rgba32 pixel0 = ref imageMem.Span[0]; Assert.True(Unsafe.AreSame(ref Unsafe.As(ref array[0]), ref pixel0)); Assert.Equal(cfg, image.GetConfiguration()); @@ -262,8 +262,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(pixelSpan.Length, imageSpan.Length); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); - Assert.True(image.TryGetSinglePixelSpan(out imageSpan)); - imageSpan.Fill(bg); + image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); @@ -293,7 +292,8 @@ namespace SixLabors.ImageSharp.Tests { using (var image = Image.WrapMemory(cfg, ptr, 5, 5, metaData)) { - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Span imageSpan = imageMem.Span; ref Rgba32 pixel0 = ref imageSpan[0]; Assert.True(Unsafe.AreSame(ref array[0], ref pixel0)); ref Rgba32 pixel_1 = ref imageSpan[imageSpan.Length - 1]; @@ -331,8 +331,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(pixelSpan.Length, imageSpan.Length); Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); - Assert.True(image.TryGetSinglePixelSpan(out imageSpan)); - imageSpan.Fill(bg); + image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index b632f4216..469c0249d 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -29,8 +29,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.Equal(11 * 23, imageSpan.Length); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(Configuration.Default, image.GetConfiguration()); @@ -46,8 +46,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.Equal(11 * 23, imageSpan.Length); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(configuration, image.GetConfiguration()); @@ -64,8 +64,8 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); - Assert.True(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.Equal(11 * 23, imageSpan.Length); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(color); Assert.Equal(configuration, image.GetConfiguration()); @@ -272,7 +272,7 @@ namespace SixLabors.ImageSharp.Tests Assert.Throws(() => { var res = image.Clone(this.configuration); }); Assert.Throws(() => { var res = image.CloneAs(this.configuration); }); Assert.Throws(() => { var res = image.GetPixelRowSpan(default); }); - Assert.Throws(() => { var res = image.TryGetSinglePixelSpan(out var _); }); + Assert.Throws(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); // Image Assert.Throws(() => { var res = genericImage.CloneAs(this.configuration); }); diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index f6656ec5f..1f0963aac 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -33,19 +33,19 @@ namespace SixLabors.ImageSharp.Tests configuration.PreferContiguousImageBuffers = true; using var image = new Image(configuration, 8192, 4096); - Assert.True(image.TryGetSinglePixelSpan(out Span span)); - Assert.Equal(8192 * 4096, span.Length); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory mem)); + Assert.Equal(8192 * 4096, mem.Length); } } [Theory] [WithBasicTestPatternImages(width: 10, height: 10, PixelTypes.Rgba32)] - public void TryGetSinglePixelSpan_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) + public void DangerousTryGetSinglePixelMemory_WhenImageTooLarge_ReturnsFalse(TestImageProvider provider) { provider.LimitAllocatorBufferCapacity().InPixels(10); using Image image = provider.GetImage(); - Assert.False(image.TryGetSinglePixelSpan(out Span imageSpan)); - Assert.False(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span imageFrameSpan)); + Assert.False(image.DangerousTryGetSinglePixelMemory(out Memory mem)); + Assert.False(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory _)); } } } diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs index d2d2fcc1f..3cd8cec15 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/AffineTransformTests.cs @@ -239,9 +239,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms private static void VerifyAllPixelsAreWhiteOrTransparent(Image image) where TPixel : unmanaged, IPixel { - Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span data)); + Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory data)); var white = new Rgb24(255, 255, 255); - foreach (TPixel pixel in data) + foreach (TPixel pixel in data.Span) { Rgba32 rgba = default; pixel.ToRgba32(ref rgba); diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 7374302c0..669fb939b 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -269,8 +269,8 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms { using (Image image0 = provider.GetImage()) { - Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); - var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 333807079..89ddbb70a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -396,12 +396,18 @@ namespace SixLabors.ImageSharp.Tests Span expectedPixels) where TPixel : unmanaged, IPixel { - Assert.True(image.TryGetSinglePixelSpan(out Span actualPixels)); - CompareBuffers(expectedPixels, actualPixels); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualPixels)); + CompareBuffers(expectedPixels, actualPixels.Span); return image; } + public static Image ComparePixelBufferTo( + this Image image, + Memory expectedPixels) + where TPixel : unmanaged, IPixel => + ComparePixelBufferTo(image, expectedPixels.Span); + public static void CompareBuffers(Span expected, Span actual) where T : struct, IEquatable { @@ -477,7 +483,8 @@ namespace SixLabors.ImageSharp.Tests public static ImageFrame ComparePixelBufferTo(this ImageFrame imageFrame, TPixel expectedPixel) where TPixel : unmanaged, IPixel { - Assert.True(imageFrame.TryGetSinglePixelSpan(out Span actualPixels)); + Assert.True(imageFrame.DangerousTryGetSinglePixelMemory(out Memory actualPixelMem)); + Span actualPixels = actualPixelMem.Span; for (int i = 0; i < actualPixels.Length; i++) { @@ -492,7 +499,8 @@ namespace SixLabors.ImageSharp.Tests Span expectedPixels) where TPixel : unmanaged, IPixel { - Assert.True(image.TryGetSinglePixelSpan(out Span actual)); + Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory actualMem)); + Span actual = actualMem.Span; Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!"); for (int i = 0; i < expectedPixels.Length; i++) @@ -696,7 +704,8 @@ namespace SixLabors.ImageSharp.Tests { var image = new Image(buffer.Width, buffer.Height); - Assert.True(image.Frames.RootFrame.TryGetSinglePixelSpan(out Span pixels)); + Assert.True(image.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory pixelMem)); + Span pixels = pixelMem.Span; Span bufferSpan = buffer.DangerousGetSingleSpan(); for (int i = 0; i < bufferSpan.Length; i++) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index 8c03e8deb..f3b321f30 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -280,8 +280,8 @@ namespace SixLabors.ImageSharp.Tests using (Image image0 = provider.GetImage()) { - Assert.True(image0.TryGetSinglePixelSpan(out Span imageSpan)); - var mmg = TestMemoryManager.CreateAsCopyOf(imageSpan); + Assert.True(image0.DangerousTryGetSinglePixelMemory(out Memory imageMem)); + var mmg = TestMemoryManager.CreateAsCopyOf(imageMem.Span); using (var image1 = Image.WrapMemory(mmg.Memory, image0.Width, image0.Height)) { From f178416926afda62ad2b5720acff5fcfcced18cc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 21:05:19 +0200 Subject: [PATCH 027/228] test ImageFrame.DangerousGetPixelRowMemory --- .../Advanced/AdvancedImageExtensionsTests.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 35ef5d1c8..24b97401d 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -106,7 +107,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced [WithBasicTestPatternImages(1, 1, PixelTypes.Rgba32)] [WithBasicTestPatternImages(131, 127, PixelTypes.Rgba32)] [WithBasicTestPatternImages(333, 555, PixelTypes.Bgr24)] - public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) + public void DangerousGetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : unmanaged, IPixel { provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); @@ -116,13 +117,18 @@ namespace SixLabors.ImageSharp.Tests.Advanced for (int y = 0; y < image.Height; y++) { // Act: - Memory rowMemory = image.DangerousGetPixelRowMemory(y); - Span span = rowMemory.Span; + Memory rowMemoryFromImage = image.DangerousGetPixelRowMemory(y); + Memory rowMemoryFromFrame = image.Frames.RootFrame.DangerousGetPixelRowMemory(y); + Span spanFromImage = rowMemoryFromImage.Span; + Span spanFromFrame = rowMemoryFromFrame.Span; + + Assert.Equal(spanFromFrame.Length, spanFromImage.Length); + Assert.True(Unsafe.AreSame(ref spanFromFrame[0], ref spanFromImage[0])); // Assert: for (int x = 0; x < image.Width; x++) { - Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), span[x]); + Assert.Equal(provider.GetExpectedBasicTestPatternPixelAt(x, y), spanFromImage[x]); } } } From ff383c9eabae899d5fc2eea3656f00ffdcb15024 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 21:06:26 +0200 Subject: [PATCH 028/228] Buffer2D.GetRowSpan -> DangerousGetRowSpan --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 18 ++++++++--------- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 6 +++--- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/LzwDecoder.cs | 4 ++-- src/ImageSharp/Formats/Gif/LzwEncoder.cs | 2 +- .../ColorConverters/JpegColorConverter.cs | 8 ++++---- .../Components/Decoder/HuffmanScanDecoder.cs | 10 +++++----- .../Decoder/JpegComponentPostProcessor.cs | 6 +++--- .../Decoder/SpectralConverter{TPixel}.cs | 2 +- .../Formats/Jpeg/Components/RowOctet.cs | 2 +- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 20 +++++++++---------- src/ImageSharp/Formats/Tga/TgaEncoderCore.cs | 8 ++++---- .../Decompressors/JpegTiffCompression.cs | 2 +- .../BlackIsZero16TiffColor{TPixel}.cs | 2 +- .../BlackIsZero24TiffColor{TPixel}.cs | 2 +- .../BlackIsZero32FloatTiffColor{TPixel}.cs | 2 +- .../BlackIsZero32TiffColor{TPixel}.cs | 2 +- .../BlackIsZero8TiffColor{TPixel}.cs | 2 +- .../BlackIsZeroTiffColor{TPixel}.cs | 2 +- .../PaletteTiffColor{TPixel}.cs | 2 +- .../Rgb161616TiffColor{TPixel}.cs | 2 +- .../Rgb16PlanarTiffColor{TPixel}.cs | 2 +- .../Rgb242424TiffColor{TPixel}.cs | 2 +- .../Rgb24PlanarTiffColor{TPixel}.cs | 2 +- .../Rgb323232TiffColor{TPixel}.cs | 2 +- .../Rgb32PlanarTiffColor{TPixel}.cs | 2 +- .../Rgb444TiffColor{TPixel}.cs | 2 +- .../Rgb888TiffColor{TPixel}.cs | 2 +- .../RgbFloat323232TiffColor{TPixel}.cs | 2 +- .../RgbPlanarTiffColor{TPixel}.cs | 2 +- .../RgbTiffColor{TPixel}.cs | 2 +- .../WhiteIsZero16TiffColor{TPixel}.cs | 2 +- .../WhiteIsZero24TiffColor{TPixel}.cs | 2 +- .../WhiteIsZero32FloatTiffColor{TPixel}.cs | 2 +- .../WhiteIsZero32TiffColor{TPixel}.cs | 2 +- .../WhiteIsZero8TiffColor{TPixel}.cs | 2 +- .../WhiteIsZeroTiffColor{TPixel}.cs | 2 +- .../YCbCrPlanarTiffColor{TPixel}.cs | 2 +- .../YCbCrTiffColor{TPixel}.cs | 2 +- .../TiffCompositeColorWriter{TPixel}.cs | 2 +- src/ImageSharp/ImageFrame{TPixel}.cs | 2 +- src/ImageSharp/Image{TPixel}.cs | 2 +- src/ImageSharp/IndexedImageFrame{TPixel}.cs | 2 +- src/ImageSharp/Memory/Buffer2DRegion{T}.cs | 4 ++-- src/ImageSharp/Memory/Buffer2D{T}.cs | 4 ++-- src/ImageSharp/PixelAccessor{TPixel}.cs | 2 +- .../ProcessingExtensions.IntegralImage.cs | 4 ++-- .../Convolution/BokehBlurProcessor{TPixel}.cs | 16 +++++++-------- .../Convolution2DRowOperation{TPixel}.cs | 10 +++++----- .../Convolution2PassProcessor{TPixel}.cs | 20 +++++++++---------- .../ConvolutionProcessor{TPixel}.cs | 8 ++++---- .../EdgeDetectorCompassProcessor{TPixel}.cs | 4 ++-- .../Effects/OilPaintingProcessor{TPixel}.cs | 2 +- ...eHistogramEqualizationProcessor{TPixel}.cs | 6 +++--- .../Quantization/OctreeQuantizer{TPixel}.cs | 2 +- .../Quantization/WuQuantizer{TPixel}.cs | 2 +- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- .../Transforms/Resize/ResizeWorker.cs | 4 ++-- .../PixelBlenders/PorterDuffBulkVsPixel.cs | 4 ++-- .../Formats/Jpg/SpectralJpegTests.cs | 2 +- .../Jpg/Utils/LibJpegTools.ComponentData.cs | 4 ++-- .../Image/ProcessPixelRowsTestBase.cs | 12 +++++------ .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 12 +++++------ .../Memory/BufferAreaTests.cs | 4 ++-- .../TestUtilities/TestImageExtensions.cs | 4 ++-- 65 files changed, 141 insertions(+), 141 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 8919befcb..41adc1cff 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -306,7 +306,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); int rowStartIdx = y * width; Span bufferRow = bufferSpan.Slice(rowStartIdx, width); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y]; if (rowHasUndefinedPixels) { @@ -826,7 +826,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp int newY = Invert(y, height, inverted); this.stream.Read(rowSpan); int offset = 0; - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); for (int x = 0; x < arrayWidth; x++) { @@ -878,7 +878,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(bufferSpan); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int offset = 0; for (int x = 0; x < width; x++) @@ -933,7 +933,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgr24Bytes( this.Configuration, rowSpan, @@ -961,7 +961,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( this.Configuration, rowSpan, @@ -1031,7 +1031,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.stream.Read(rowSpan); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); PixelOperations.Instance.FromBgra32Bytes( this.Configuration, @@ -1054,7 +1054,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp width); int newY = Invert(y, height, inverted); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = 0; x < width; x++) { @@ -1109,7 +1109,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp { this.stream.Read(bufferSpan); int newY = Invert(y, height, inverted); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int offset = 0; for (int x = 0; x < width; x++) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index c6ca5b09d..b12866781 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -274,7 +274,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, @@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -326,7 +326,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 482a76153..de9f2bad6 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -445,7 +445,7 @@ namespace SixLabors.ImageSharp.Formats.Gif for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++) { - ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.GetRowSpan(y - descriptorTop)); + ref byte indicesRowRef = ref MemoryMarshal.GetReference(indices.DangerousGetRowSpan(y - descriptorTop)); // Check if this image is interlaced. int writeY; // the target y offset to write to diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 9eaa55566..68227db53 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -115,14 +115,14 @@ namespace SixLabors.ImageSharp.Formats.Gif int y = 0; int x = 0; int rowMax = width; - ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(y)); + ref byte pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(y)); while (xyz < length) { // Reset row reference. if (xyz == rowMax) { x = 0; - pixelsRowRef = ref MemoryMarshal.GetReference(pixels.GetRowSpan(++y)); + pixelsRowRef = ref MemoryMarshal.GetReference(pixels.DangerousGetRowSpan(++y)); rowMax = (y * width) + width; } diff --git a/src/ImageSharp/Formats/Gif/LzwEncoder.cs b/src/ImageSharp/Formats/Gif/LzwEncoder.cs index e9fb7ab00..c52e34f96 100644 --- a/src/ImageSharp/Formats/Gif/LzwEncoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwEncoder.cs @@ -275,7 +275,7 @@ namespace SixLabors.ImageSharp.Formats.Gif for (int y = 0; y < indexedPixels.Height; y++) { - ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.GetRowSpan(y)); + ref byte rowSpanRef = ref MemoryMarshal.GetReference(indexedPixels.DangerousGetRowSpan(y)); int offsetX = y == 0 ? 1 : 0; for (int x = offsetX; x < indexedPixels.Width; x++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 11ea4cda8..535514c88 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -206,12 +206,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { this.ComponentCount = componentBuffers.Count; - this.Component0 = componentBuffers[0].GetRowSpan(row); + this.Component0 = componentBuffers[0].DangerousGetRowSpan(row); // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span.Empty; + this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span.Empty; } internal ComponentValues( diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index bc9a53ea0..bb7b1fe78 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // by the basic H and V specified for the component for (int y = 0; y < v; y++) { - Span blockSpan = component.SpectralBlocks.GetRowSpan(y); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(y); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -253,7 +253,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int j = 0; j < h; j++) { this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -376,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int y = 0; y < v; y++) { int blockRow = (mcuRow * v) + y; - Span blockSpan = component.SpectralBlocks.GetRowSpan(blockRow); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(blockRow); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int x = 0; x < h; x++) @@ -421,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) @@ -449,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.cancellationToken.ThrowIfCancellationRequested(); - Span blockSpan = component.SpectralBlocks.GetRowSpan(j); + Span blockSpan = component.SpectralBlocks.DangerousGetRowSpan(j); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); for (int i = 0; i < w; i++) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 9a659d621..527d2c030 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -95,8 +95,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int yBuffer = y * this.blockAreaSize.Height; - Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); - Span blockRow = spectralBuffer.GetRowSpan(yBlock); + Span colorBufferRow = this.ColorBuffer.DangerousGetRowSpan(yBuffer); + Span blockRow = spectralBuffer.DangerousGetRowSpan(yBlock); // see: https://github.com/SixLabors/ImageSharp/issues/824 int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width); @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder Buffer2D spectralBlocks = this.Component.SpectralBlocks; for (int i = 0; i < spectralBlocks.Height; i++) { - spectralBlocks.GetRowSpan(i).Clear(); + spectralBlocks.DangerousGetRowSpan(i).Clear(); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index ec7f3e5c3..492c00c05 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -162,7 +162,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Span proxyRow = this.paddedProxyPixelRow.GetSpan(); PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow); - proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy)); + proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.DangerousGetRowSpan(yy)); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs index 16d24cf81..d4a4c1cf4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/RowOctet.cs @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components int i = 0; while (y < yEnd) { - this[i++] = buffer.GetRowSpan(y++); + this[i++] = buffer.DangerousGetRowSpan(y++); } } diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 8f9786140..d101ccd94 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -234,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); switch (colorMapPixelSizeInBytes) { @@ -318,7 +318,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { @@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { this.ReadL8Pixel(color, x, pixelSpan); @@ -412,7 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); if (invertX) { @@ -479,7 +479,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelSpan = pixels.GetRowSpan(newY); + Span pixelSpan = pixels.DangerousGetRowSpan(newY); for (int x = width - 1; x >= 0; x--) { this.ReadBgr24Pixel(color, x, pixelSpan); @@ -548,7 +548,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); if (invertX) { for (int x = width - 1; x >= 0; x--) @@ -587,7 +587,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = 0; y < height; y++) { int newY = InvertY(y, height, origin); - Span pixelRow = pixels.GetRowSpan(newY); + Span pixelRow = pixels.DangerousGetRowSpan(newY); int rowStartIdx = y * width * bytesPerPixel; for (int x = 0; x < width; x++) { @@ -654,7 +654,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width); } @@ -681,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width); } @@ -700,7 +700,7 @@ namespace SixLabors.ImageSharp.Formats.Tga where TPixel : unmanaged, IPixel { this.currentStream.Read(row); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width); } diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs index 4bf4ca60a..1a1260a58 100644 --- a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8Bytes( this.configuration, pixelSpan, @@ -300,7 +300,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra5551Bytes( this.configuration, pixelSpan, @@ -324,7 +324,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgr24Bytes( this.configuration, pixelSpan, @@ -348,7 +348,7 @@ namespace SixLabors.ImageSharp.Formats.Tga for (int y = pixels.Height - 1; y >= 0; y--) { - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.ToBgra32Bytes( this.configuration, pixelSpan, diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index 9a0607584..2bc606eaf 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors int offset = 0; for (int y = 0; y < pixelBuffer.Height; y++) { - Span pixelRowSpan = pixelBuffer.GetRowSpan(y); + Span pixelRowSpan = pixelBuffer.DangerousGetRowSpan(y); Span rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan); rgbBytes.CopyTo(buffer.Slice(offset)); offset += rgbBytes.Length; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs index 459506843..5d910d16e 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs index ec07abd5c..4cda95480 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs index ff34a29eb..ee9bf8a9c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs index f54a79484..7367a78e3 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs index f62cf2952..c06239a4d 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); int byteCount = pixelRow.Length; PixelOperations.Instance.FromL8Bytes( this.configuration, diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs index 9956db523..a40fa7667 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { int value = bitReader.ReadBits(this.bitsPerSample0); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs index b392fe1a3..29e03c6c6 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { int index = bitReader.ReadBits(this.bitsPerSample0); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs index e5d8c8da2..a4d725bcf 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs index 9a6d4631a..1c61b0991 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs index 3be0540a0..985ffeb18 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation Span bufferSpan = buffer.AsSpan(bufferStartIdx); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs index 9c3e57e2a..ac4435db6 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs index e2ba085e1..bf1e65e1c 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs index a7432549c..cdc6942bd 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs index daad50e98..3dfffe0ce 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb444TiffColor{TPixel}.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var bgra = default(Bgra4444); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.DangerousGetRowSpan(y); for (int x = left; x < left + width; x += 2) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs index 2a86eb2ee..1b5432c28 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); int byteCount = pixelRow.Length * 3; PixelOperations.Instance.FromRgb24Bytes( this.configuration, diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs index f3f27d5c4..4dc3295a4 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs index b442c4ae4..54466e05b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs index 1377598cc..4a887c426 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor; diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs index 18b5300b2..038281c99 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs index 10182f250..807023b6b 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs index d532247fe..71323c7ba 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs index ef62b4f44..e433956f0 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation int offset = 0; for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); if (this.isBigEndian) { for (int x = 0; x < pixelRow.Length; x++) diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs index 15ebed58f..8945e55f2 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs @@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation var l8 = default(L8); for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { byte intensity = (byte)(byte.MaxValue - data[offset++]); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs index 912955964..d692fc789 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { int value = bitReader.ReadBits(this.bitsPerSample0); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs index 70578a744..465c8fba3 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrPlanarTiffColor{TPixel}.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { Rgba32 rgba = this.converter.ConvertToRgba32(yData[offset], cbData[offset], crData[offset]); diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs index e31b4984d..52cc1f0f1 100644 --- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/YCbCrTiffColor{TPixel}.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation for (int y = top; y < top + height; y++) { - Span pixelRow = pixels.GetRowSpan(y).Slice(left, width); + Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); for (int x = 0; x < pixelRow.Length; x++) { Rgba32 rgba = this.converter.ConvertToRgba32(ycbcrData[offset], ycbcrData[offset + 1], ycbcrData[offset + 2]); diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs index 88c5f33dd..5d190e0af 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int stripPixelsRowIdx = 0; for (int row = y; row < lastRow; row++) { - Span stripPixelsRow = this.Image.PixelBuffer.GetRowSpan(row); + Span stripPixelsRow = this.Image.PixelBuffer.DangerousGetRowSpan(row); stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width)); stripPixelsRowIdx++; } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index ff1e44e6c..31bce4415 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); - return this.PixelBuffer.GetRowSpan(rowIndex); + return this.PixelBuffer.DangerousGetRowSpan(rowIndex); } /// diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index c4f1c278f..66bf24e51 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp this.EnsureNotDisposed(); - return this.PixelSourceUnsafe.PixelBuffer.GetRowSpan(rowIndex); + return this.PixelSourceUnsafe.PixelBuffer.DangerousGetRowSpan(rowIndex); } /// diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs index d841cbea9..3d7e1f31f 100644 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp /// The pixel row as a . [MethodImpl(InliningOptions.ShortMethod)] public Span GetWritablePixelRowSpanUnsafe(int rowIndex) - => this.pixelBuffer.GetRowSpan(rowIndex); + => this.pixelBuffer.DangerousGetRowSpan(rowIndex); /// public void Dispose() diff --git a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs index 8c5988944..d1c39ccbf 100644 --- a/src/ImageSharp/Memory/Buffer2DRegion{T}.cs +++ b/src/ImageSharp/Memory/Buffer2DRegion{T}.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Memory int xx = this.Rectangle.X; int width = this.Rectangle.Width; - return this.Buffer.GetRowSpan(yy).Slice(xx, width); + return this.Buffer.DangerousGetRowSpan(yy).Slice(xx, width); } /// @@ -138,7 +138,7 @@ namespace SixLabors.ImageSharp.Memory { int y = this.Rectangle.Y; int x = this.Rectangle.X; - return ref this.Buffer.GetRowSpan(y)[x]; + return ref this.Buffer.DangerousGetRowSpan(y)[x]; } internal void Clear() diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index c85a1cdb5..ba39a924e 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Memory DebugGuard.MustBeLessThan(x, this.Width, nameof(x)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return ref this.GetRowSpan(y)[x]; + return ref this.DangerousGetRowSpan(y)[x]; } } @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Memory /// The of the pixels in the row. /// Thrown when row index is out of range. [MethodImpl(InliningOptions.ShortMethod)] - public Span GetRowSpan(int y) + public Span DangerousGetRowSpan(int y) { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); diff --git a/src/ImageSharp/PixelAccessor{TPixel}.cs b/src/ImageSharp/PixelAccessor{TPixel}.cs index 65ab5dbda..36671439e 100644 --- a/src/ImageSharp/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/PixelAccessor{TPixel}.cs @@ -67,6 +67,6 @@ namespace SixLabors.ImageSharp /// The row index. /// The . /// Thrown when row index is out of range. - public Span GetRowSpan(int rowIndex) => this.buffer.GetRowSpan(rowIndex); + public Span GetRowSpan(int rowIndex) => this.buffer.DangerousGetRowSpan(rowIndex); } } diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs index af6d32b21..8c5f915b4 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing { Span tempSpan = tempRow.GetSpan(); Span sourceRow = source.GetPixelRowSpan(0); - Span destRow = intImage.GetRowSpan(0); + Span destRow = intImage.DangerousGetRowSpan(0); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing for (int y = 1; y < endY; y++) { sourceRow = source.GetPixelRowSpan(y); - destRow = intImage.GetRowSpan(y); + destRow = intImage.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); diff --git a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs index 241ff9db2..a55ce91e3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BokehBlurProcessor{TPixel}.cs @@ -231,11 +231,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution int kernelSize = this.kernel.Length; // Clear the target buffer for each row run - Span targetBuffer = this.targetValues.GetRowSpan(y); + Span targetBuffer = this.targetValues.DangerousGetRowSpan(y); targetBuffer.Clear(); // Execute the bulk pixel format conversion for the current row - Span sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, span); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(span); @@ -295,7 +295,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); @@ -335,7 +335,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Premultiply); @@ -378,8 +378,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Vector4 low = Vector4.Zero; var high = new Vector4(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity); - Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); - Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X); + Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X); ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); for (int x = 0; x < this.bounds.Width; x++) @@ -422,13 +422,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public unsafe void Invoke(int y) { - Span sourceRowSpan = this.sourceValues.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span sourceRowSpan = this.sourceValues.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); ref Vector4 sourceRef = ref MemoryMarshal.GetReference(sourceRowSpan); Numerics.Clamp(MemoryMarshal.Cast(sourceRowSpan), 0, float.PositiveInfinity); Numerics.CubeRootOnXYZ(sourceRowSpan); - Span targetPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X); + Span targetPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.FromVector4Destructive(this.configuration, sourceRowSpan.Slice(0, this.bounds.Width), targetPixelSpan, PixelConversionModifiers.Premultiply); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs index 802d1809f..01288e80f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DRowOperation{TPixel}.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int sampleY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution // Now we need to combine the values and copy the original alpha values // from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); for (int x = 0; x < sourceRow.Length; x++) @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution target.W = Unsafe.Add(ref MemoryMarshal.GetReference(sourceBuffer), x).W; } - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRowSpan); } @@ -152,7 +152,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int sampleY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.GetRowSpan(sampleY).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Numerics.UnPremultiply(targetYBuffer); - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetYBuffer, targetRow); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 365b2e2df..b0be2dfd0 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); targetStart = ref MemoryMarshal.GetReference(targetBuffer); @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution sourceBase = ref Unsafe.Add(ref sourceBase, 1); } - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution targetBuffer.Clear(); // Get the precalculated source sample row for this kernel row and copy to our buffer. - Span sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); @@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } } @@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.GetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); @@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); { ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -364,7 +364,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } } - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } @@ -392,7 +392,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution while (Unsafe.IsAddressLessThan(ref kernelStart, ref kernelEnd)) { // Get the precalculated source sample row for this kernel row and copy to our buffer. - sourceRow = this.sourcePixels.GetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(sampleRowBase).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); @@ -415,7 +415,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Numerics.UnPremultiply(targetBuffer); - Span targetRow = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRow = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetBuffer, targetRow); } } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs index 924a1125b..82b731277 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor{TPixel}.cs @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution Span targetBuffer = span.Slice(this.bounds.Width); ref Vector4 targetRowRef = ref MemoryMarshal.GetReference(span); - Span targetRowSpan = this.targetPixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + Span targetRowSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); var state = new ConvolutionState(in this.kernel, this.map); int row = y - this.bounds.Y; @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); - sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution } // Now we need to copy the original alpha values from the source row. - sourceRow = this.sourcePixels.GetRowSpan(y).Slice(boundsX, boundsWidth); + sourceRow = this.sourcePixels.DangerousGetRowSpan(y).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); for (int x = 0; x < sourceRow.Length; x++) @@ -174,7 +174,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution { // Get the precalculated source sample row for this kernel row and copy to our buffer. int offsetY = Unsafe.Add(ref sampleRowBase, kY); - Span sourceRow = this.sourcePixels.GetRowSpan(offsetY).Slice(boundsX, boundsWidth); + Span sourceRow = this.sourcePixels.DangerousGetRowSpan(offsetY).Slice(boundsX, boundsWidth); PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); Numerics.Premultiply(sourceBuffer); diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs index 27963613e..360b496c3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetectorCompassProcessor{TPixel}.cs @@ -117,8 +117,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.GetRowSpan(y)); - ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.GetRowSpan(y)); + ref TPixel passPixelsBase = ref MemoryMarshal.GetReference(this.passPixels.DangerousGetRowSpan(y)); + ref TPixel targetPixelsBase = ref MemoryMarshal.GetReference(this.targetPixels.DangerousGetRowSpan(y)); for (int x = this.minX; x < this.maxX; x++) { diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 42216417e..64a4d9a96 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -176,7 +176,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects } } - Span targetRowAreaPixelSpan = this.targetPixels.GetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span targetRowAreaPixelSpan = this.targetPixels.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); PixelOperations.Instance.FromVector4Destructive(this.configuration, targetRowAreaVector4Span, targetRowAreaPixelSpan); } diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 883f85be3..14e27af02 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -525,7 +525,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization } [MethodImpl(InliningOptions.ShortMethod)] - public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.GetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); + public Span GetCdfLutSpan(int tileX, int tileY) => this.cdfLutBuffer2D.DangerousGetRowSpan(tileY).Slice(tileX * this.luminanceLevels, this.luminanceLevels); /// /// Remaps the grey value with the cdf. @@ -599,7 +599,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int cdfY = this.tileYStartPositions[index].CdfY; int y = this.tileYStartPositions[index].Y; int endY = Math.Min(y + this.tileHeight, this.sourceHeight); - Span cdfMinSpan = this.cdfMinBuffer2D.GetRowSpan(cdfY); + Span cdfMinSpan = this.cdfMinBuffer2D.DangerousGetRowSpan(cdfY); cdfMinSpan.Clear(); using IMemoryOwner histogramBuffer = this.allocator.Allocate(this.luminanceLevels); @@ -609,7 +609,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization for (int x = 0; x < this.sourceWidth; x += this.tileWidth) { histogram.Clear(); - Span cdfLutSpan = this.cdfLutBuffer2D.GetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); + Span cdfLutSpan = this.cdfLutBuffer2D.DangerousGetRowSpan(index).Slice(cdfX * this.luminanceLevels, this.luminanceLevels); ref int cdfBase = ref MemoryMarshal.GetReference(cdfLutSpan); int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth); diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs index 311a8aa2e..e28de54c2 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization // Loop through each row for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); + Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); for (int x = 0; x < bufferSpan.Length; x++) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index bf4a5ca41..fae9a1c29 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -384,7 +384,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); + Span row = source.DangerousGetRowSpan(y).Slice(bounds.Left, bounds.Width); PixelOperations.Instance.ToRgba32(this.Configuration, row, bufferSpan); for (int x = 0; x < bufferSpan.Length; x++) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 220d85250..a9f99f003 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -252,7 +252,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int length = right - left + 1; this.ValidateSizesForCreateKernel(length, dataRowIndex, left, right); - Span rowSpan = this.data.GetRowSpan(dataRowIndex); + Span rowSpan = this.data.DangerousGetRowSpan(dataRowIndex); ref float rowReference = ref MemoryMarshal.GetReference(rowSpan); float* rowPtr = (float*)Unsafe.AsPointer(ref rowReference); diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs index 7ade3aeee..4e3a08c39 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeWorker.cs @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public Span GetColumnSpan(int x, int startY) - => this.transposedFirstPassBuffer.GetRowSpan(x).Slice(startY - this.currentWindow.Min); + => this.transposedFirstPassBuffer.DangerousGetRowSpan(x).Slice(startY - this.currentWindow.Min); public void Initialize() => this.CalculateFirstPassValues(this.currentWindow); @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Unsafe.Add(ref tempRowBase, x) = kernel.ConvolveCore(ref firstPassColumnBase); } - Span targetRowSpan = destination.GetRowSpan(y); + Span targetRowSpan = destination.DangerousGetRowSpan(y); PixelOperations.Instance.FromVector4Destructive(this.configuration, tempColSpan, targetRowSpan, this.conversionModifiers); } diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs index cf7807837..76d077c76 100644 --- a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Benchmarks Buffer2D pixels = image.GetRootFramePixelBuffer(); for (int y = 0; y < image.Height; y++) { - Span span = pixels.GetRowSpan(y); + Span span = pixels.DangerousGetRowSpan(y); this.BulkVectorConvert(span, span, span, amounts.GetSpan()); } @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Benchmarks Buffer2D pixels = image.GetRootFramePixelBuffer(); for (int y = 0; y < image.Height; y++) { - Span span = pixels.GetRowSpan(y); + Span span = pixels.DangerousGetRowSpan(y); this.BulkPixelConvert(span, span, span, amounts.GetSpan()); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs index 72f21b215..35113f14f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Buffer2D spectralBlocks = component.SpectralBlocks; for (int i = 0; i < spectralBlocks.Height; i++) { - spectralBlocks.GetRowSpan(i).Clear(); + spectralBlocks.DangerousGetRowSpan(i).Clear(); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index adbd695c0..601a0644f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils for (int y = startIndex; y < endIndex; y++) { - Span blockRow = data.GetRowSpan(y - startIndex); + Span blockRow = data.DangerousGetRowSpan(y - startIndex); for (int x = 0; x < this.WidthInBlocks; x++) { short[] block = blockRow[x].ToArray(); @@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Buffer2D data = c.SpectralBlocks; for (int y = 0; y < this.HeightInBlocks; y++) { - Span blockRow = data.GetRowSpan(y); + Span blockRow = data.DangerousGetRowSpan(y); for (int x = 0; x < this.WidthInBlocks; x++) { short[] block = blockRow[x].ToArray(); diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs index ac36c285c..e0626395d 100644 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests Buffer2D buffer = image.Frames.RootFrame.PixelBuffer; for (int y = 0; y < 256; y++) { - Span row = buffer.GetRowSpan(y); + Span row = buffer.DangerousGetRowSpan(y); for (int x = 0; x < 256; x++) { int actual = row[x].PackedValue; @@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests Buffer2D buffer = img1.Frames.RootFrame.PixelBuffer; for (int y = 0; y < 256; y++) { - Span row = buffer.GetRowSpan(y); + Span row = buffer.DangerousGetRowSpan(y); for (int x = 0; x < 256; x++) { row[x] = new L16((ushort)(x * y)); @@ -100,7 +100,7 @@ namespace SixLabors.ImageSharp.Tests buffer = img2.Frames.RootFrame.PixelBuffer; for (int y = 0; y < 256; y++) { - Span row = buffer.GetRowSpan(y); + Span row = buffer.DangerousGetRowSpan(y); for (int x = 0; x < 256; x++) { int actual = row[x].PackedValue; @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests Buffer2D buffer2 = img1.Frames.RootFrame.PixelBuffer; for (int y = 0; y < 256; y++) { - Span row = buffer2.GetRowSpan(y); + Span row = buffer2.DangerousGetRowSpan(y); for (int x = 0; x < 256; x++) { row[x] = new L16((ushort)(x * y)); @@ -142,8 +142,8 @@ namespace SixLabors.ImageSharp.Tests Buffer2D buffer3 = img3.Frames.RootFrame.PixelBuffer; for (int y = 0; y < 256; y++) { - Span row2 = buffer2.GetRowSpan(y); - Span row3 = buffer3.GetRowSpan(y); + Span row2 = buffer2.DangerousGetRowSpan(y); + Span row3 = buffer3.DangerousGetRowSpan(y); for (int x = 0; x < 256; x++) { int actual2 = row2[x].PackedValue; diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 04abc6585..73a0f4d60 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using (Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height)) { - Span span = buffer.GetRowSpan(y); + Span span = buffer.DangerousGetRowSpan(y); Assert.Equal(width, span.Length); @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp.Tests.Memory this.MemoryAllocator.BufferCapacityInBytes = bufferCapacity; using Buffer2D buffer = this.MemoryAllocator.Allocate2D(width, height); - Exception ex = Assert.ThrowsAny(() => buffer.GetRowSpan(y)); + Exception ex = Assert.ThrowsAny(() => buffer.DangerousGetRowSpan(y)); Assert.True(ex is ArgumentOutOfRangeException || ex is IndexOutOfRangeException); } @@ -268,8 +268,8 @@ namespace SixLabors.ImageSharp.Tests.Memory Buffer2D.SwapOrCopyContent(dest, source); } - int actual1 = dest.GetRowSpan(0)[0]; - int actual2 = dest.GetRowSpan(0)[0]; + int actual1 = dest.DangerousGetRowSpan(0)[0]; + int actual2 = dest.DangerousGetRowSpan(0)[0]; int actual3 = dest.GetSafeRowMemory(0).Span[0]; int actual5 = dest[0, 0]; @@ -297,7 +297,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpan(y); + Span row = b.DangerousGetRowSpan(y); Span s = row.Slice(startIndex, columnCount); Span d = row.Slice(destIndex, columnCount); @@ -320,7 +320,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < b.Height; y++) { - Span row = b.GetRowSpan(y); + Span row = b.DangerousGetRowSpan(y); Span s = row.Slice(0, 22); Span d = row.Slice(50, 22); diff --git a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs index 0dfc5f36b..76e55aa3a 100644 --- a/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs +++ b/tests/ImageSharp.Tests/Memory/BufferAreaTests.cs @@ -127,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = 0; y < 13; y++) { - Span row = buffer.GetRowSpan(y); + Span row = buffer.DangerousGetRowSpan(y); Assert.True(row.SequenceEqual(emptyRow)); } } @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Memory for (int y = region.Rectangle.Y; y < region.Rectangle.Bottom; y++) { - Span span = buffer.GetRowSpan(y).Slice(region.Rectangle.X, region.Width); + Span span = buffer.DangerousGetRowSpan(y).Slice(region.Rectangle.X, region.Width); Assert.True(span.SequenceEqual(new int[region.Width])); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 89ddbb70a..5ebd349c8 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -429,8 +429,8 @@ namespace SixLabors.ImageSharp.Tests for (int y = 0; y < expected.Height; y++) { - Span expectedRow = expected.GetRowSpan(y); - Span actualRow = actual.GetRowSpan(y); + Span expectedRow = expected.DangerousGetRowSpan(y); + Span actualRow = actual.DangerousGetRowSpan(y); for (int x = 0; x < expectedRow.Length; x++) { T expectedVal = expectedRow[x]; From 64a9d25e963bfdefe5935093069bf3f9bc1a0685 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 21:08:32 +0200 Subject: [PATCH 029/228] update webp code --- src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs | 2 +- src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 768365e44..3e06a5642 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int y = 0; y < height; y++) { Span rowAsBytes = pixelDataAsBytes.Slice(y * bytesPerRow, bytesPerRow); - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgra32Bytes( this.configuration, rowAsBytes.Slice(0, bytesPerRow), diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index ebb0b0aa4..d6f5f7103 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int y = 0; y < height; y++) { Span row = pixelData.Slice(y * widthMul3, widthMul3); - Span decodedPixelRow = decodedPixels.GetRowSpan(y); + Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromBgr24Bytes( this.configuration, row, @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (int y = 0; y < height; y++) { int yMulWidth = y * width; - Span decodedPixelRow = decodedPixels.GetRowSpan(y); + Span decodedPixelRow = decodedPixels.DangerousGetRowSpan(y); for (int x = 0; x < width; x++) { int offset = yMulWidth + x; From 9047fc971d14cf5ef48ef55a4ad6dda542e60592 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 21:56:41 +0200 Subject: [PATCH 030/228] remove ImageFrame.GetPixelRowSpan --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 14 +++--- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 5 ++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 12 ++--- .../Tiff/Writers/TiffBiColorWriter{TPixel}.cs | 4 +- .../Tiff/Writers/TiffPaletteWriter{TPixel}.cs | 4 +- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 2 +- .../Formats/Webp/Lossy/YuvConversion.cs | 6 +-- src/ImageSharp/ImageFrame{TPixel}.cs | 32 +++----------- src/ImageSharp/Image{TPixel}.cs | 2 +- src/ImageSharp/IndexedImageFrame{TPixel}.cs | 2 +- ...oolMemoryAllocator.CommonFactoryMethods.cs | 2 +- .../Internals/SharedArrayPoolBuffer{T}.cs | 2 +- .../UniformUnmanagedMemoryPool.Buffer{T}.cs | 2 +- src/ImageSharp/Memory/Buffer2DExtensions.cs | 14 ++++-- .../ProcessingExtensions.IntegralImage.cs | 4 +- .../AdaptiveThresholdProcessor{TPixel}.cs | 12 ++--- .../BinaryThresholdProcessor{TPixel}.cs | 9 ++-- .../Processors/Dithering/ErrorDither.cs | 12 +++-- .../Processors/Dithering/OrderedDither.cs | 7 ++- .../DrawImageProcessor{TPixelBg,TPixelFg}.cs | 18 ++++---- .../Effects/OilPaintingProcessor{TPixel}.cs | 10 ++--- ...lRowDelegateProcessor{TPixel,TDelegate}.cs | 8 ++-- .../Effects/PixelateProcessor{TPixel}.cs | 9 ++-- .../Filters/FilterProcessor{TPixel}.cs | 8 ++-- .../Filters/OpaqueProcessor{TPixel}.cs | 9 ++-- ...eHistogramEqualizationProcessor{TPixel}.cs | 44 +++++++++---------- ...alizationSlidingWindowProcessor{TPixel}.cs | 14 +++--- ...lHistogramEqualizationProcessor{TPixel}.cs | 16 +++---- .../BackgroundColorProcessor{TPixel}.cs | 8 ++-- .../Overlays/GlowProcessor{TPixel}.cs | 8 ++-- .../Overlays/VignetteProcessor{TPixel}.cs | 8 ++-- .../Quantization/QuantizeProcessor{TPixel}.cs | 5 ++- .../Quantization/QuantizerUtilities.cs | 3 +- .../Transforms/CropProcessor{TPixel}.cs | 12 ++--- .../AffineTransformProcessor{TPixel}.cs | 39 +++++++--------- .../Linear/FlipProcessor{TPixel}.cs | 17 +++---- .../ProjectiveTransformProcessor{TPixel}.cs | 39 +++++++--------- .../Linear/RotateProcessor{TPixel}.cs | 38 ++++++++-------- .../Resize/ResizeProcessor{TPixel}.cs | 16 +++---- .../SwizzleProcessor{TSwizzler,TPixel}.cs | 4 +- .../Advanced/AdvancedImageExtensionsTests.cs | 4 +- .../Formats/Png/PngEncoderTests.cs | 4 +- .../Formats/WebP/PredictorEncoderTests.cs | 2 +- .../ImageSharp.Tests/Image/ImageCloneTests.cs | 16 +++---- .../Image/ImageTests.WrapMemory.cs | 12 ++--- tests/ImageSharp.Tests/Image/ImageTests.cs | 2 +- .../Quantization/QuantizedImageTests.cs | 4 +- .../Quantization/WuQuantizerTests.cs | 16 +++---- .../ImageComparison/ExactImageComparer.cs | 7 ++- .../ImageComparison/TolerantImageComparer.cs | 7 ++- .../BasicTestPatternProvider.cs | 4 +- .../ReferenceCodecs/SystemDrawingBridge.cs | 20 ++++----- .../TestUtilities/TestImageExtensions.cs | 8 ++-- 54 files changed, 295 insertions(+), 293 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index b12866781..6384074df 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -379,7 +379,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp for (int y = image.Height - 1; y >= 0; y--) { - ReadOnlySpan pixelSpan = quantized.GetPixelRowSpan(y); + ReadOnlySpan pixelSpan = quantized.DangerousGetRowSpan(y); stream.Write(pixelSpan); for (int i = 0; i < this.padding; i++) @@ -413,10 +413,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp } stream.Write(colorPalette); - + Buffer2D imageBuffer = image.PixelBuffer; for (int y = image.Height - 1; y >= 0; y--) { - ReadOnlySpan inputPixelRow = image.GetPixelRowSpan(y); + ReadOnlySpan inputPixelRow = imageBuffer.DangerousGetRowSpan(y); ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow); stream.Write(outputPixelRow); @@ -447,11 +447,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); - ReadOnlySpan pixelRowSpan = quantized.GetPixelRowSpan(0); + ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0); int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { - pixelRowSpan = quantized.GetPixelRowSpan(y); + pixelRowSpan = quantized.DangerousGetRowSpan(y); int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; for (int i = 0; i < endIdx; i += 2) @@ -491,11 +491,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); - ReadOnlySpan quantizedPixelRow = quantized.GetPixelRowSpan(0); + ReadOnlySpan quantizedPixelRow = quantized.DangerousGetRowSpan(0); int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; for (int y = image.Height - 1; y >= 0; y--) { - quantizedPixelRow = quantized.GetPixelRowSpan(y); + quantizedPixelRow = quantized.DangerousGetRowSpan(y); int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; for (int i = 0; i < endIdx; i += 8) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index de9f2bad6..3e33a6e37 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -481,7 +481,7 @@ namespace SixLabors.ImageSharp.Formats.Gif writeY = y; } - ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.GetPixelRowSpan(writeY)); + ref TPixel rowRef = ref MemoryMarshal.GetReference(imageFrame.PixelBuffer.DangerousGetRowSpan(writeY)); if (!transFlag) { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 987dc150c..c8c135981 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -565,6 +565,7 @@ namespace SixLabors.ImageSharp.Formats.Png { int pass = 0; int width = this.header.Width; + Buffer2D imageBuffer = image.PixelBuffer; while (true) { int numColumns = Adam7.ComputeColumns(width, pass); @@ -623,7 +624,7 @@ namespace SixLabors.ImageSharp.Formats.Png break; } - Span rowSpan = image.GetPixelRowSpan(this.currentRow); + Span rowSpan = imageBuffer.DangerousGetRowSpan(this.currentRow); this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]); this.SwapScanlineBuffers(); @@ -656,7 +657,7 @@ namespace SixLabors.ImageSharp.Formats.Png private void ProcessDefilteredScanline(ReadOnlySpan defilteredScanline, ImageFrame pixels, PngMetadata pngMetadata) where TPixel : unmanaged, IPixel { - Span rowSpan = pixels.GetPixelRowSpan(this.currentRow); + Span rowSpan = pixels.PixelBuffer.DangerousGetRowSpan(this.currentRow); // Trim the first marker byte from the buffer ReadOnlySpan trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1); diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index f10db7a6c..3f1b256fc 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Png Rgba32 rgba32 = default; for (int y = 0; y < image.Height; y++) { - Span span = image.GetPixelRowSpan(y); + Span span = image.DangerousGetRowSpan(y); for (int x = 0; x < image.Width; x++) { span[x].ToRgba32(ref rgba32); @@ -391,11 +391,11 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.bitDepth < 8) { - PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.GetPixelRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); + PngEncoderHelpers.ScaleDownFrom8BitArray(quantized.DangerousGetRowSpan(row), this.currentScanline.GetSpan(), this.bitDepth); } else { - quantized.GetPixelRowSpan(row).CopyTo(this.currentScanline.GetSpan()); + quantized.DangerousGetRowSpan(row).CopyTo(this.currentScanline.GetSpan()); } break; @@ -918,7 +918,7 @@ namespace SixLabors.ImageSharp.Formats.Png Span attempt = attemptBuffer.GetSpan(); for (int y = 0; y < this.height; y++) { - this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y); + this.CollectAndFilterPixelRow(pixels.DangerousGetRowSpan(y), ref filter, ref attempt, quantized, y); deflateStream.Write(filter); this.SwapScanlineBuffers(); } @@ -959,7 +959,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) { // Collect pixel data - Span srcRow = pixels.GetPixelRowSpan(row); + Span srcRow = pixels.DangerousGetRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { block[i++] = srcRow[col]; @@ -1014,7 +1014,7 @@ namespace SixLabors.ImageSharp.Formats.Png row += Adam7.RowIncrement[pass]) { // Collect data - ReadOnlySpan srcRow = quantized.GetPixelRowSpan(row); + ReadOnlySpan srcRow = quantized.DangerousGetRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index bd20d644f..a26fefe15 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int grayRowIdx = 0; for (int row = y; row < lastRow; row++) { - Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); + Span pixelsBlackWhiteRow = this.imageBlackWhite.DangerousGetRowSpan(row); Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); grayRowIdx++; @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int bitIndex = 0; int byteIndex = 0; Span outputRow = rows.Slice(outputRowIdx * this.BytesPerRow); - Span pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row); + Span pixelsBlackWhiteRow = this.imageBlackWhite.DangerousGetRowSpan(row); PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); for (int x = 0; x < this.Image.Width; x++) { diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs index 6d517294d..900969a6c 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int lastRow = y + height; for (int row = y; row < lastRow; row++) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); int idxPixels = 0; for (int x = 0; x < halfWidth; x++) { @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int indexedPixelsRowIdx = 0; for (int row = y; row < lastRow; row++) { - ReadOnlySpan indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row); + ReadOnlySpan indexedPixelRow = this.quantizedImage.DangerousGetRowSpan(row); indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width)); indexedPixelsRowIdx++; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 2fb3fbc6a..4cdcb3e86 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -395,7 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int widthBytes = width * 4; for (int y = 0; y < height; y++) { - Span rowSpan = image.GetPixelRowSpan(y); + Span rowSpan = image.DangerousGetRowSpan(y); Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); if (!nonOpaque) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index ed03c2e71..254107682 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -46,8 +46,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int rowIndex; for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { - Span rowSpan = image.GetPixelRowSpan(rowIndex); - Span nextRowSpan = image.GetPixelRowSpan(rowIndex + 1); + Span rowSpan = image.DangerousGetRowSpan(rowIndex); + Span nextRowSpan = image.DangerousGetRowSpan(rowIndex + 1); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Extra last row. if ((height & 1) != 0) { - Span rowSpan = image.GetPixelRowSpan(rowIndex); + Span rowSpan = image.DangerousGetRowSpan(rowIndex); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width); diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 31bce4415..e8268d381 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -177,24 +177,6 @@ namespace SixLabors.ImageSharp } } - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// WARNING: Disposing or leaking the underlying image while still working with it's - /// might lead to memory corruption. - /// - /// The row. - /// The - /// Thrown when row index is out of range. - public Span GetPixelRowSpan(int rowIndex) - { - Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); - Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); - - return this.PixelBuffer.DangerousGetRowSpan(rowIndex); - } - /// /// Execute to process image pixels in a safe and efficient manner. /// @@ -418,7 +400,7 @@ namespace SixLabors.ImageSharp } var target = new ImageFrame(configuration, this.Width, this.Height, this.Metadata.DeepClone()); - var operation = new RowIntervalOperation(this, target, configuration); + var operation = new RowIntervalOperation(this.PixelBuffer, target.PixelBuffer, configuration); ParallelRowIterator.IterateRowIntervals( configuration, @@ -472,14 +454,14 @@ namespace SixLabors.ImageSharp private readonly struct RowIntervalOperation : IRowIntervalOperation where TPixel2 : unmanaged, IPixel { - private readonly ImageFrame source; - private readonly ImageFrame target; + private readonly Buffer2D source; + private readonly Buffer2D target; private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] public RowIntervalOperation( - ImageFrame source, - ImageFrame target, + Buffer2D source, + Buffer2D target, Configuration configuration) { this.source = source; @@ -493,8 +475,8 @@ namespace SixLabors.ImageSharp { for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.target.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan(y); + Span targetRow = this.target.DangerousGetRowSpan(y); PixelOperations.Instance.To(this.configuration, sourceRow, targetRow); } } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 66bf24e51..aa3281fbf 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -310,7 +310,7 @@ namespace SixLabors.ImageSharp /// The row. /// The /// Thrown when row index is out of range. - public Span GetPixelRowSpan(int rowIndex) + public Span DangerousGetRowSpan(int rowIndex) { Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); diff --git a/src/ImageSharp/IndexedImageFrame{TPixel}.cs b/src/ImageSharp/IndexedImageFrame{TPixel}.cs index 3d7e1f31f..18b44de82 100644 --- a/src/ImageSharp/IndexedImageFrame{TPixel}.cs +++ b/src/ImageSharp/IndexedImageFrame{TPixel}.cs @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp /// The row index in the pixel buffer. /// The pixel row as a . [MethodImpl(InliningOptions.ShortMethod)] - public ReadOnlySpan GetPixelRowSpan(int rowIndex) + public ReadOnlySpan DangerousGetRowSpan(int rowIndex) => this.GetWritablePixelRowSpanUnsafe(rowIndex); /// diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs index 4b06f537b..8aa1b9063 100644 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs +++ b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Memory /// This is the default. Should be good for most use cases. /// /// The memory manager. - public static new ArrayPoolMemoryAllocator CreateDefault() + public static ArrayPoolMemoryAllocator CreateDefault() { return new ArrayPoolMemoryAllocator( DefaultMaxPooledBufferSizeInBytes, diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 7edd9f5a7..6cb2a24fb 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Memory.Internals // The worst thing that could happen is that a VERY poorly written user code holding a Span on the stack, // while loosing the reference to Image (or disposing it) may write to an unrelated ArrayPool array. - // This is an unlikely scenario we mitigate by a warning in GetPixelRowSpan(i) APIs. + // This is an unlikely scenario we mitigate by a warning in DangerousGetRowSpan(i) APIs. #pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span ~SharedArrayPoolBuffer() => this.Dispose(false); #pragma warning restore diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs index 8f2d982ef..56209d343 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Memory.Internals // A VERY poorly written user code holding a Span on the stack, // while loosing the reference to Image (or disposing it) may write to (now unrelated) pool buffer, // or cause memory corruption if the underlying UmnanagedMemoryHandle has been released. - // This is an unlikely scenario we mitigate a warning in GetPixelRowSpan(i) APIs. + // This is an unlikely scenario we mitigate a warning in DangerousGetRowSpan(i) APIs. #pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span ~FinalizableBuffer() => this.Dispose(false); #pragma warning restore diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 6458ad7e4..58143de4e 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -111,10 +111,16 @@ namespace SixLabors.ImageSharp.Memory /// The /// The of the buffer internal static Size Size(this Buffer2D buffer) - where T : struct - { - return new Size(buffer.Width, buffer.Height); - } + where T : struct => + new(buffer.Width, buffer.Height); + + /// + /// Gets the bounds of the buffer. + /// + /// The + internal static Rectangle Bounds(this Buffer2D buffer) + where T : struct => + new(0, 0, buffer.Width, buffer.Height); [Conditional("DEBUG")] private static void CheckColumnRegionsDoNotOverlap( diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs index 8c5f915b4..fc6aa83a5 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(source.Width)) { Span tempSpan = tempRow.GetSpan(); - Span sourceRow = source.GetPixelRowSpan(0); + Span sourceRow = source.DangerousGetRowSpan(0); Span destRow = intImage.DangerousGetRowSpan(0); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing // All other rows for (int y = 1; y < endY; y++) { - sourceRow = source.GetPixelRowSpan(y); + sourceRow = source.DangerousGetRowSpan(y); destRow = intImage.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); diff --git a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs index 254ba5a7e..bf6690dcf 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/AdaptiveThresholdProcessor{TPixel}.cs @@ -52,6 +52,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization // ClusterSize defines the size of cluster to used to check for average. Tweaked to support up to 4k wide pixels and not more. 4096 / 16 is 256 thus the '-1' byte clusterSize = (byte)Math.Truncate((width / 16f) - 1); + Buffer2D sourceBuffer = source.PixelBuffer; + // Using pooled 2d buffer for integer image table and temp memory to hold Rgb24 converted pixel data. using (Buffer2D intImage = this.Configuration.MemoryAllocator.Allocate2D(width, height)) { @@ -61,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization ulong sum = 0; for (int y = startY; y < endY; y++) { - Span row = source.GetPixelRowSpan(y); + Span row = sourceBuffer.DangerousGetRowSpan(y); ref TPixel rowRef = ref MemoryMarshal.GetReference(row); ref TPixel color = ref Unsafe.Add(ref rowRef, x); color.ToRgba32(ref rgb); @@ -79,7 +81,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization } } - var operation = new RowOperation(intersect, source, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); + var operation = new RowOperation(intersect, source.PixelBuffer, intImage, upper, lower, thresholdLimit, clusterSize, startX, endX, startY); ParallelRowIterator.IterateRows( configuration, intersect, @@ -90,7 +92,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly Buffer2D intImage; private readonly TPixel upper; private readonly TPixel lower; @@ -103,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( Rectangle bounds, - ImageFrame source, + Buffer2D source, Buffer2D intImage, TPixel upper, TPixel lower, @@ -130,7 +132,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization public void Invoke(int y) { Rgba32 rgb = default; - Span pixelRow = this.source.GetPixelRowSpan(y); + Span pixelRow = this.source.DangerousGetRowSpan(y); for (int x = this.startX; x < this.endX; x++) { diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs index 5942c7164..00cb015bc 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor{TPixel}.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Binarization @@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization var interest = Rectangle.Intersect(sourceRectangle, source.Bounds()); var operation = new RowOperation( interest.X, - source, + source.PixelBuffer, upper, lower, threshold, @@ -59,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization /// private readonly struct RowOperation : IRowOperation { - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly TPixel upper; private readonly TPixel lower; private readonly byte threshold; @@ -70,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( int startX, - ImageFrame source, + Buffer2D source, TPixel upper, TPixel lower, byte threshold, @@ -93,7 +94,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Binarization TPixel upper = this.upper; TPixel lower = this.lower; - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); PixelOperations.Instance.ToRgb24(this.configuration, rowSpan, span); switch (this.mode) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 2e6cfebe1..27bb660e9 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -5,6 +5,7 @@ using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -105,14 +106,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int offsetY = bounds.Top; int offsetX = bounds.Left; float scale = quantizer.Options.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = bounds.Top; y < bounds.Bottom; y++) { // Unsafe optimizations undone temporarily. // Sporadic local AccessViolationException indicates possible indexing bug. - // ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + // ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.DangerousGetRowSpan(y)); // ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); - Span sourceSpan = source.GetPixelRowSpan(y); + Span sourceSpan = sourceBuffer.DangerousGetRowSpan(y); Span destSpan = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); for (int x = bounds.Left; x < bounds.Right; x++) @@ -140,10 +142,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering ThrowDefaultInstance(); } + Buffer2D sourceBuffer = source.PixelBuffer; float scale = processor.DitherScale; for (int y = bounds.Top; y < bounds.Bottom; y++) { - ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.GetPixelRowSpan(y)); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); for (int x = bounds.Left; x < bounds.Right; x++) { ref TPixel sourcePixel = ref Unsafe.Add(ref sourceRowRef, x); @@ -177,6 +180,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int offset = this.offset; DenseMatrix matrix = this.matrix; + Buffer2D imageBuffer = image.PixelBuffer; // Loop through and distribute the error amongst neighboring pixels. for (int row = 0, targetY = y; row < matrix.Rows; row++, targetY++) @@ -186,7 +190,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering continue; } - Span rowSpan = image.GetPixelRowSpan(targetY); + Span rowSpan = imageBuffer.DangerousGetRowSpan(targetY); for (int col = 0; col < matrix.Columns; col++) { diff --git a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs index 2f5a5cf85..da0a852b8 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -118,10 +119,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int spread = CalculatePaletteSpread(destination.Palette.Length); float scale = quantizer.Options.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = bounds.Top; y < bounds.Bottom; y++) { - ReadOnlySpan sourceRow = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width); + ReadOnlySpan sourceRow = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); Span destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y).Slice(0, sourceRow.Length); for (int x = 0; x < sourceRow.Length; x++) @@ -148,10 +150,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering int spread = CalculatePaletteSpread(processor.Palette.Length); float scale = processor.DitherScale; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = bounds.Top; y < bounds.Bottom; y++) { - Span row = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width); + Span row = sourceBuffer.DangerousGetRowSpan(y).Slice(bounds.X, bounds.Width); for (int x = 0; x < row.Length; x++) { diff --git a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs index 9b3dbcaa3..86009b9d9 100644 --- a/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs +++ b/src/ImageSharp/Processing/Processors/Drawing/DrawImageProcessor{TPixelBg,TPixelFg}.cs @@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing "Cannot draw image because the source image does not overlap the target image."); } - var operation = new RowOperation(source, targetImage, blender, configuration, minX, width, locationY, targetX, this.Opacity); + var operation = new RowOperation(source.PixelBuffer, targetImage.Frames.RootFrame.PixelBuffer, blender, configuration, minX, width, locationY, targetX, this.Opacity); ParallelRowIterator.IterateRows( configuration, workingRect, @@ -111,8 +111,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing /// private readonly struct RowOperation : IRowOperation { - private readonly ImageFrame sourceFrame; - private readonly Image targetImage; + private readonly Buffer2D source; + private readonly Buffer2D target; private readonly PixelBlender blender; private readonly Configuration configuration; private readonly int minX; @@ -123,8 +123,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( - ImageFrame sourceFrame, - Image targetImage, + Buffer2D source, + Buffer2D target, PixelBlender blender, Configuration configuration, int minX, @@ -133,8 +133,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing int targetX, float opacity) { - this.sourceFrame = sourceFrame; - this.targetImage = targetImage; + this.source = source; + this.target = target; this.blender = blender; this.configuration = configuration; this.minX = minX; @@ -148,8 +148,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span background = this.sourceFrame.GetPixelRowSpan(y).Slice(this.minX, this.width); - Span foreground = this.targetImage.GetPixelRowSpan(y - this.locationY).Slice(this.targetX, this.width); + Span background = this.source.DangerousGetRowSpan(y).Slice(this.minX, this.width); + Span foreground = this.target.DangerousGetRowSpan(y - this.locationY).Slice(this.targetX, this.width); this.blender.Blend(this.configuration, background, background, foreground, this.opacity); } } diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs index 64a4d9a96..eb18c10f4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor{TPixel}.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects source.CopyTo(targetPixels); - var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source, this.Configuration, brushSize >> 1, this.definition.Levels); + var operation = new RowIntervalOperation(this.SourceRectangle, targetPixels, source.PixelBuffer, this.Configuration, brushSize >> 1, this.definition.Levels); ParallelRowIterator.IterateRowIntervals( this.Configuration, this.SourceRectangle, @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects { private readonly Rectangle bounds; private readonly Buffer2D targetPixels; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly Configuration configuration; private readonly int radius; private readonly int levels; @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects public RowIntervalOperation( Rectangle bounds, Buffer2D targetPixels, - ImageFrame source, + Buffer2D source, Configuration configuration, int radius, int levels) @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRowPixelSpan = this.source.GetPixelRowSpan(y); + Span sourceRowPixelSpan = this.source.DangerousGetRowSpan(y); Span sourceRowAreaPixelSpan = sourceRowPixelSpan.Slice(this.bounds.X, this.bounds.Width); PixelOperations.Instance.ToVector4(this.configuration, sourceRowAreaPixelSpan, sourceRowAreaVector4Span); @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects int offsetY = y + fyr; offsetY = Numerics.Clamp(offsetY, 0, maxY); - Span sourceOffsetRow = this.source.GetPixelRowSpan(offsetY); + Span sourceOffsetRow = this.source.DangerousGetRowSpan(offsetY); for (int fx = 0; fx <= this.radius; fx++) { diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs index 6b63c885a..bc1445d89 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelRowDelegateProcessor{TPixel,TDelegate}.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest.X, source, this.Configuration, this.modifiers, this.rowDelegate); + var operation = new RowOperation(interest.X, source.PixelBuffer, this.Configuration, this.modifiers, this.rowDelegate); ParallelRowIterator.IterateRows( this.Configuration, @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly struct RowOperation : IRowOperation { private readonly int startX; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly Configuration configuration; private readonly PixelConversionModifiers modifiers; private readonly TDelegate rowProcessor; @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( int startX, - ImageFrame source, + Buffer2D source, Configuration configuration, PixelConversionModifiers modifiers, in TDelegate rowProcessor) @@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, this.modifiers); // Run the user defined pixel shader to the current row of pixels diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs index 0f307f8f1..f6aecabe1 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor{TPixel}.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Effects @@ -48,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects Parallel.ForEach( range, this.Configuration.GetParallelOptions(), - new RowOperation(interest, size, source).Invoke); + new RowOperation(interest, size, source.PixelBuffer).Invoke); } private readonly struct RowOperation @@ -60,13 +61,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects private readonly int maxYIndex; private readonly int size; private readonly int radius; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( Rectangle bounds, int size, - ImageFrame source) + Buffer2D source) { this.minX = bounds.X; this.maxX = bounds.Right; @@ -81,7 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Effects [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span rowSpan = this.source.GetPixelRowSpan(Math.Min(y + this.radius, this.maxYIndex)); + Span rowSpan = this.source.DangerousGetRowSpan(Math.Min(y + this.radius, this.maxYIndex)); for (int x = this.minX; x < this.maxX; x += this.size) { diff --git a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs index d0c8ff40d..e3323dd6c 100644 --- a/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/FilterProcessor{TPixel}.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters protected override void OnFrameApply(ImageFrame source) { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new RowOperation(interest.X, source, this.definition.Matrix, this.Configuration); + var operation = new RowOperation(interest.X, source.PixelBuffer, this.definition.Matrix, this.Configuration); ParallelRowIterator.IterateRows( this.Configuration, @@ -50,14 +50,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters private readonly struct RowOperation : IRowOperation { private readonly int startX; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly ColorMatrix matrix; private readonly Configuration configuration; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( int startX, - ImageFrame source, + Buffer2D source, ColorMatrix matrix, Configuration configuration) { @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.startX, span.Length); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.startX, span.Length); PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); ColorNumerics.Transform(span, ref Unsafe.AsRef(this.matrix)); diff --git a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs index 9bb364476..a230fc761 100644 --- a/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Filters/OpaqueProcessor{TPixel}.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Filters @@ -25,20 +26,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters { var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds()); - var operation = new OpaqueRowOperation(this.Configuration, source, interest); + var operation = new OpaqueRowOperation(this.Configuration, source.PixelBuffer, interest); ParallelRowIterator.IterateRows(this.Configuration, interest, in operation); } private readonly struct OpaqueRowOperation : IRowOperation { private readonly Configuration configuration; - private readonly ImageFrame target; + private readonly Buffer2D target; private readonly Rectangle bounds; [MethodImpl(InliningOptions.ShortMethod)] public OpaqueRowOperation( Configuration configuration, - ImageFrame target, + Buffer2D target, Rectangle bounds) { this.configuration = configuration; @@ -50,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Filters [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y, Span span) { - Span targetRowSpan = this.target.GetPixelRowSpan(y).Slice(this.bounds.X); + Span targetRowSpan = this.target.DangerousGetRowSpan(y).Slice(this.bounds.X); PixelOperations.Instance.ToVector4(this.configuration, targetRowSpan.Slice(0, span.Length), span, PixelConversionModifiers.Scale); ref Vector4 baseRef = ref MemoryMarshal.GetReference(span); diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs index 14e27af02..0afdef905 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationProcessor{TPixel}.cs @@ -80,37 +80,37 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization yStart += tileHeight; } - var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source); + var operation = new RowIntervalOperation(cdfData, tileYStartPositions, tileWidth, tileHeight, tileCount, halfTileWidth, luminanceLevels, source.PixelBuffer); ParallelRowIterator.IterateRowIntervals( this.Configuration, new Rectangle(0, 0, sourceWidth, tileYStartPositions.Count), in operation); // Fix left column - ProcessBorderColumn(source, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); + ProcessBorderColumn(source.PixelBuffer, cdfData, 0, sourceHeight, this.Tiles, tileHeight, xStart: 0, xEnd: halfTileWidth, luminanceLevels); // Fix right column int rightBorderStartX = ((this.Tiles - 1) * tileWidth) + halfTileWidth; - ProcessBorderColumn(source, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); + ProcessBorderColumn(source.PixelBuffer, cdfData, this.Tiles - 1, sourceHeight, this.Tiles, tileHeight, xStart: rightBorderStartX, xEnd: sourceWidth, luminanceLevels); // Fix top row - ProcessBorderRow(source, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + ProcessBorderRow(source.PixelBuffer, cdfData, 0, sourceWidth, this.Tiles, tileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Fix bottom row int bottomBorderStartY = ((this.Tiles - 1) * tileHeight) + halfTileHeight; - ProcessBorderRow(source, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + ProcessBorderRow(source.PixelBuffer, cdfData, this.Tiles - 1, sourceWidth, this.Tiles, tileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); // Left top corner - ProcessCornerTile(source, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, 0, 0, xStart: 0, xEnd: halfTileWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Left bottom corner - ProcessCornerTile(source, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, 0, this.Tiles - 1, xStart: 0, xEnd: halfTileWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); // Right top corner - ProcessCornerTile(source, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, 0, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: 0, yEnd: halfTileHeight, luminanceLevels); // Right bottom corner - ProcessCornerTile(source, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); + ProcessCornerTile(source.PixelBuffer, cdfData, this.Tiles - 1, this.Tiles - 1, xStart: rightBorderStartX, xEnd: sourceWidth, yStart: bottomBorderStartY, yEnd: sourceHeight, luminanceLevels); } } @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// or 65536 for 16-bit grayscale images. /// private static void ProcessCornerTile( - ImageFrame source, + Buffer2D source, CdfTileData cdfData, int cdfX, int cdfY, @@ -142,7 +142,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { for (int dy = yStart; dy < yEnd; dy++) { - Span rowSpan = source.GetPixelRowSpan(dy); + Span rowSpan = source.DangerousGetRowSpan(dy); for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; @@ -168,7 +168,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// or 65536 for 16-bit grayscale images. /// private static void ProcessBorderColumn( - ImageFrame source, + Buffer2D source, CdfTileData cdfData, int cdfX, int sourceHeight, @@ -188,7 +188,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int tileY = 0; for (int dy = y; dy < yLimit; dy++) { - Span rowSpan = source.GetPixelRowSpan(dy); + Span rowSpan = source.DangerousGetRowSpan(dy); for (int dx = xStart; dx < xEnd; dx++) { ref TPixel pixel = ref rowSpan[dx]; @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// or 65536 for 16-bit grayscale images. /// private static void ProcessBorderRow( - ImageFrame source, + Buffer2D source, CdfTileData cdfData, int cdfY, int sourceWidth, @@ -238,7 +238,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { for (int dy = yStart; dy < yEnd; dy++) { - Span rowSpan = source.GetPixelRowSpan(dy); + Span rowSpan = source.DangerousGetRowSpan(dy); int tileX = 0; int xLimit = Math.Min(x + tileWidth, sourceWidth - 1); for (int dx = x; dx < xLimit; dx++) @@ -373,7 +373,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int tileCount; private readonly int halfTileWidth; private readonly int luminanceLevels; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int sourceWidth; private readonly int sourceHeight; @@ -386,7 +386,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int tileCount, int halfTileWidth, int luminanceLevels, - ImageFrame source) + Buffer2D source) { this.cdfData = cdfData; this.tileYStartPositions = tileYStartPositions; @@ -419,7 +419,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int xEnd = Math.Min(x + this.tileWidth, this.sourceWidth); for (int dy = y; dy < yEnd; dy++) { - Span rowSpan = this.source.GetPixelRowSpan(dy); + Span rowSpan = this.source.DangerousGetRowSpan(dy); int tileX = 0; for (int dx = x; dx < xEnd; dx++) { @@ -516,7 +516,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization this.tileWidth, this.tileHeight, this.luminanceLevels, - source); + source.PixelBuffer); ParallelRowIterator.IterateRowIntervals( this.configuration, @@ -560,7 +560,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization private readonly int tileWidth; private readonly int tileHeight; private readonly int luminanceLevels; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int sourceWidth; private readonly int sourceHeight; @@ -574,7 +574,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int tileWidth, int tileHeight, int luminanceLevels, - ImageFrame source) + Buffer2D source) { this.processor = processor; this.allocator = allocator; @@ -615,7 +615,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization int xlimit = Math.Min(x + this.tileWidth, this.sourceWidth); for (int dy = y; dy < endY; dy++) { - Span rowSpan = this.source.GetPixelRowSpan(dy); + Span rowSpan = this.source.DangerousGetRowSpan(dy); for (int dx = x; dx < xlimit; dx++) { int luminance = GetLuminance(rowSpan[dx], this.luminanceLevels); diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs index f17e0d1e4..5e1e016ee 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization y = source.Height - diff - 1; } - // Special cases for the left and the right border where GetPixelRowSpan can not be used. + // Special cases for the left and the right border where DangerousGetRowSpan can not be used. if (x < 0) { rowPixels.Clear(); @@ -224,7 +224,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization return; } - this.CopyPixelRowFast(source, rowPixels, x, y, tileWidth, configuration); + this.CopyPixelRowFast(source.PixelBuffer, rowPixels, x, y, tileWidth, configuration); } /// @@ -238,13 +238,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// The configuration. [MethodImpl(InliningOptions.ShortMethod)] private void CopyPixelRowFast( - ImageFrame source, + Buffer2D source, Span rowPixels, int x, int y, int tileWidth, Configuration configuration) - => PixelOperations.Instance.ToVector4(configuration, source.GetPixelRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); + => PixelOperations.Instance.ToVector4(configuration, source.DangerousGetRowSpan(y).Slice(start: x, length: tileWidth), rowPixels); /// /// Adds a column of grey values to the histogram. @@ -356,7 +356,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { if (this.useFastPath) { - this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); + this.processor.CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, dy, this.swInfos.TileWidth, this.configuration); } else { @@ -390,7 +390,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Remove top most row from the histogram, mirroring rows which exceeds the borders. if (this.useFastPath) { - this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + this.processor.CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y - this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); } else { @@ -402,7 +402,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization // Add new bottom row to the histogram, mirroring rows which exceeds the borders. if (this.useFastPath) { - this.processor.CopyPixelRowFast(this.source, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); + this.processor.CopyPixelRowFast(this.source.PixelBuffer, pixelRow, x - this.swInfos.HalfTileWidth, y + this.swInfos.HalfTileWidth, this.swInfos.TileWidth, this.configuration); } else { diff --git a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs index 70d3e075d..67970821c 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/GlobalHistogramEqualizationProcessor{TPixel}.cs @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization using IMemoryOwner histogramBuffer = memoryAllocator.Allocate(this.LuminanceLevels, AllocationOptions.Clean); // Build the histogram of the grayscale levels. - var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source, this.LuminanceLevels); + var grayscaleOperation = new GrayscaleLevelsRowOperation(interest, histogramBuffer, source.PixelBuffer, this.LuminanceLevels); ParallelRowIterator.IterateRows( this.Configuration, interest, @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization float numberOfPixelsMinusCdfMin = numberOfPixels - cdfMin; // Apply the cdf to each pixel of the image - var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source, this.LuminanceLevels, numberOfPixelsMinusCdfMin); + var cdfOperation = new CdfApplicationRowOperation(interest, cdfBuffer, source.PixelBuffer, this.LuminanceLevels, numberOfPixelsMinusCdfMin); ParallelRowIterator.IterateRows( this.Configuration, interest, @@ -90,14 +90,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { private readonly Rectangle bounds; private readonly IMemoryOwner histogramBuffer; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int luminanceLevels; [MethodImpl(InliningOptions.ShortMethod)] public GrayscaleLevelsRowOperation( Rectangle bounds, IMemoryOwner histogramBuffer, - ImageFrame source, + Buffer2D source, int luminanceLevels) { this.bounds = bounds; @@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void Invoke(int y) { ref int histogramBase = ref MemoryMarshal.GetReference(this.histogramBuffer.GetSpan()); - Span pixelRow = this.source.GetPixelRowSpan(y); + Span pixelRow = this.source.DangerousGetRowSpan(y); int levels = this.luminanceLevels; for (int x = 0; x < this.bounds.Width; x++) @@ -136,7 +136,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization { private readonly Rectangle bounds; private readonly IMemoryOwner cdfBuffer; - private readonly ImageFrame source; + private readonly Buffer2D source; private readonly int luminanceLevels; private readonly float numberOfPixelsMinusCdfMin; @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public CdfApplicationRowOperation( Rectangle bounds, IMemoryOwner cdfBuffer, - ImageFrame source, + Buffer2D source, int luminanceLevels, float numberOfPixelsMinusCdfMin) { @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization public void Invoke(int y) { ref int cdfBase = ref MemoryMarshal.GetReference(this.cdfBuffer.GetSpan()); - Span pixelRow = this.source.GetPixelRowSpan(y); + Span pixelRow = this.source.DangerousGetRowSpan(y); int levels = this.luminanceLevels; float noOfPixelsMinusCdfMin = this.numberOfPixelsMinusCdfMin; diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs index 76dcc2194..636738ca7 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor{TPixel}.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender = PixelOperations.Instance.GetPixelBlender(graphicsOptions); - var operation = new RowOperation(configuration, interest, blender, amount, colors, source); + var operation = new RowOperation(configuration, interest, blender, amount, colors, source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, interest, @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly PixelBlender blender; private readonly IMemoryOwner amount; private readonly IMemoryOwner colors; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays PixelBlender blender, IMemoryOwner amount, IMemoryOwner colors, - ImageFrame source) + Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays public void Invoke(int y) { Span destination = - this.source.GetPixelRowSpan(y) + this.source.DangerousGetRowSpan(y) .Slice(this.bounds.X, this.bounds.Width); // Switch color & destination in the 2nd and 3rd places because we are diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs index 78cf7f3c6..331609089 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor{TPixel}.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(glowColor); - var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, interest, @@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly float maxDistance; private readonly float blendPercent; private readonly IMemoryOwner colors; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( @@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays Vector2 center, float maxDistance, float blendPercent, - ImageFrame source) + Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -105,7 +105,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays span[i] = Numerics.Clamp(this.blendPercent * (1 - (.95F * (distance / this.maxDistance))), 0, 1F); } - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); this.blender.Blend( this.configuration, diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs index c853377ad..800613eca 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor{TPixel}.cs @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays using IMemoryOwner rowColors = allocator.Allocate(interest.Width); rowColors.GetSpan().Fill(vignetteColor); - var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source); + var operation = new RowOperation(configuration, interest, rowColors, this.blender, center, maxDistance, blendPercent, source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, interest, @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays private readonly float maxDistance; private readonly float blendPercent; private readonly IMemoryOwner colors; - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] public RowOperation( @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays Vector2 center, float maxDistance, float blendPercent, - ImageFrame source) + Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays span[i] = Numerics.Clamp(this.blendPercent * (.9F * (distance / this.maxDistance)), 0, 1F); } - Span destination = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width); + Span destination = this.source.DangerousGetRowSpan(y).Slice(this.bounds.X, this.bounds.Width); this.blender.Blend( this.configuration, diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs index 93bca6075..574b27475 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs @@ -44,11 +44,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization ReadOnlySpan paletteSpan = quantized.Palette.Span; int offsetY = interest.Top; int offsetX = interest.Left; + Buffer2D sourceBuffer = source.PixelBuffer; for (int y = interest.Y; y < interest.Height; y++) { - Span row = source.GetPixelRowSpan(y); - ReadOnlySpan quantizedRow = quantized.GetPixelRowSpan(y - offsetY); + Span row = sourceBuffer.DangerousGetRowSpan(y); + ReadOnlySpan quantizedRow = quantized.DangerousGetRowSpan(y - offsetY); for (int x = interest.Left; x < interest.Right; x++) { diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs index 6c963bfab..5aa79d732 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs @@ -122,6 +122,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : unmanaged, IPixel { IDither dither = quantizer.Options.Dither; + Buffer2D sourceBuffer = source.PixelBuffer; if (dither is null) { @@ -130,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int y = bounds.Y; y < bounds.Height; y++) { - Span sourceRow = source.GetPixelRowSpan(y); + Span sourceRow = sourceBuffer.DangerousGetRowSpan(y); Span destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); for (int x = bounds.Left; x < bounds.Right; x++) diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs index df9c1146b..dfc6ba1c1 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor{TPixel}.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(this.Configuration).MultiplyMinimumPixelsPerTask(4); - var operation = new RowOperation(bounds, source, destination); + var operation = new RowOperation(bounds, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRows( bounds, @@ -65,8 +65,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct RowOperation : IRowOperation { private readonly Rectangle bounds; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; /// /// Initializes a new instance of the struct. @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The source for the current instance. /// The destination for the current instance. [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation(Rectangle bounds, ImageFrame source, ImageFrame destination) + public RowOperation(Rectangle bounds, Buffer2D source, Buffer2D destination) { this.bounds = bounds; this.source = source; @@ -86,8 +86,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left); - Span targetRow = this.destination.GetPixelRowSpan(y - this.bounds.Top); + Span sourceRow = this.source.DangerousGetRowSpan(y).Slice(this.bounds.Left); + Span targetRow = this.destination.DangerousGetRowSpan(y - this.bounds.Top); sourceRow.Slice(0, this.bounds.Width).CopyTo(targetRow); } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs index 5f04918e0..640527fe7 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { - var nnOperation = new NNAffineOperation(source, destination, matrix); + var nnOperation = new NNAffineOperation(source.PixelBuffer, destination.PixelBuffer, matrix); ParallelRowIterator.IterateRows( configuration, destination.Bounds(), @@ -84,8 +84,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var operation = new AffineOperation( configuration, - source, - destination, + source.PixelBuffer, + destination.PixelBuffer, in sampler, matrix); @@ -97,15 +97,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct NNAffineOperation : IRowOperation { - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly Rectangle bounds; private readonly Matrix3x2 matrix; [MethodImpl(InliningOptions.ShortMethod)] public NNAffineOperation( - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, Matrix3x2 matrix) { this.source = source; @@ -117,8 +117,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Buffer2D sourceBuffer = this.source.PixelBuffer; - Span destRow = this.destination.GetPixelRowSpan(y); + Span destRow = this.destination.DangerousGetRowSpan(y); for (int x = 0; x < destRow.Length; x++) { @@ -128,7 +127,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.bounds.Contains(px, py)) { - destRow[x] = sourceBuffer.GetElementUnsafe(px, py); + destRow[x] = this.source.GetElementUnsafe(px, py); } } } @@ -138,8 +137,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms where TResampler : struct, IResampler { private readonly Configuration configuration; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly TResampler sampler; private readonly Matrix3x2 matrix; private readonly float yRadius; @@ -148,8 +147,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public AffineOperation( Configuration configuration, - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, in TResampler sampler, Matrix3x2 matrix) { @@ -186,11 +185,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -222,7 +219,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -251,11 +248,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -287,7 +282,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs index 840881b14..8d15a79e5 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -38,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { // No default needed as we have already set the pixels. case FlipMode.Vertical: - this.FlipX(source, this.Configuration); + this.FlipX(source.PixelBuffer, this.Configuration); break; case FlipMode.Horizontal: this.FlipY(source, this.Configuration); @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// /// The source image to apply the process to. /// The configuration. - private void FlipX(ImageFrame source, Configuration configuration) + private void FlipX(Buffer2D source, Configuration configuration) { int height = source.Height; using IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(source.Width); @@ -60,8 +61,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms for (int yTop = 0; yTop < height / 2; yTop++) { int yBottom = height - yTop - 1; - Span topRow = source.GetPixelRowSpan(yBottom); - Span bottomRow = source.GetPixelRowSpan(yTop); + Span topRow = source.DangerousGetRowSpan(yBottom); + Span bottomRow = source.DangerousGetRowSpan(yTop); topRow.CopyTo(temp); bottomRow.CopyTo(topRow); temp.CopyTo(bottomRow); @@ -75,7 +76,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void FlipY(ImageFrame source, Configuration configuration) { - var operation = new RowOperation(source); + var operation = new RowOperation(source.PixelBuffer); ParallelRowIterator.IterateRows( configuration, source.Bounds(), @@ -84,13 +85,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct RowOperation : IRowOperation { - private readonly ImageFrame source; + private readonly Buffer2D source; [MethodImpl(InliningOptions.ShortMethod)] - public RowOperation(ImageFrame source) => this.source = source; + public RowOperation(Buffer2D source) => this.source = source; [MethodImpl(InliningOptions.ShortMethod)] - public void Invoke(int y) => this.source.GetPixelRowSpan(y).Reverse(); + public void Invoke(int y) => this.source.DangerousGetRowSpan(y).Reverse(); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs index 9396a018d..cf6567629 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs @@ -72,7 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (sampler is NearestNeighborResampler) { - var nnOperation = new NNProjectiveOperation(source, destination, matrix); + var nnOperation = new NNProjectiveOperation(source.PixelBuffer, destination.PixelBuffer, matrix); ParallelRowIterator.IterateRows( configuration, destination.Bounds(), @@ -83,8 +83,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms var operation = new ProjectiveOperation( configuration, - source, - destination, + source.PixelBuffer, + destination.PixelBuffer, in sampler, matrix); @@ -96,15 +96,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly struct NNProjectiveOperation : IRowOperation { - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly Rectangle bounds; private readonly Matrix4x4 matrix; [MethodImpl(InliningOptions.ShortMethod)] public NNProjectiveOperation( - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, Matrix4x4 matrix) { this.source = source; @@ -116,8 +116,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Buffer2D sourceBuffer = this.source.PixelBuffer; - Span destRow = this.destination.GetPixelRowSpan(y); + Span destRow = this.destination.DangerousGetRowSpan(y); for (int x = 0; x < destRow.Length; x++) { @@ -127,7 +126,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms if (this.bounds.Contains(px, py)) { - destRow[x] = sourceBuffer.GetElementUnsafe(px, py); + destRow[x] = this.source.GetElementUnsafe(px, py); } } } @@ -137,8 +136,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms where TResampler : struct, IResampler { private readonly Configuration configuration; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; private readonly TResampler sampler; private readonly Matrix4x4 matrix; private readonly float yRadius; @@ -147,8 +146,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public ProjectiveOperation( Configuration configuration, - ImageFrame source, - ImageFrame destination, + Buffer2D source, + Buffer2D destination, in TResampler sampler, Matrix4x4 matrix) { @@ -185,11 +184,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -221,7 +218,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } @@ -250,11 +247,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int maxY = this.source.Height - 1; int maxX = this.source.Width - 1; - Buffer2D sourceBuffer = this.source.PixelBuffer; - for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.destination.GetPixelRowSpan(y); + Span rowSpan = this.destination.DangerousGetRowSpan(y); PixelOperations.Instance.ToVector4( this.configuration, rowSpan, @@ -286,7 +281,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { float xWeight = sampler.GetValue(xK - pX); - Vector4 current = sourceBuffer.GetElementUnsafe(xK, yK).ToScaledVector4(); + Vector4 current = this.source.GetElementUnsafe(xK, yK).ToScaledVector4(); Numerics.Premultiply(ref current); sum += current * xWeight * yWeight; } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index cce6d6860..234f89a71 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate180(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate180RowOperation(source.Width, source.Height, source, destination); + var operation = new Rotate180RowOperation(source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRows( configuration, source.Bounds(), @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate270(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source, destination); + var operation = new Rotate270RowIntervalOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRowIntervals( configuration, source.Bounds(), @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms /// The configuration. private void Rotate90(ImageFrame source, ImageFrame destination, Configuration configuration) { - var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source, destination); + var operation = new Rotate90RowOperation(destination.Bounds(), source.Width, source.Height, source.PixelBuffer, destination.PixelBuffer); ParallelRowIterator.IterateRows( configuration, source.Bounds(), @@ -172,15 +172,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { private readonly int width; private readonly int height; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public Rotate180RowOperation( int width, int height, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.width = width; this.height = height; @@ -191,8 +191,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span sourceRow = this.source.GetPixelRowSpan(y); - Span targetRow = this.destination.GetPixelRowSpan(this.height - y - 1); + Span sourceRow = this.source.DangerousGetRowSpan(y); + Span targetRow = this.destination.DangerousGetRowSpan(this.height - y - 1); for (int x = 0; x < this.width; x++) { @@ -206,16 +206,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly Rectangle bounds; private readonly int width; private readonly int height; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public Rotate270RowIntervalOperation( Rectangle bounds, int width, int height, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.bounds = bounds; this.width = width; @@ -229,7 +229,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { for (int y = rows.Min; y < rows.Max; y++) { - Span sourceRow = this.source.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan(y); for (int x = 0; x < this.width; x++) { int newX = this.height - y - 1; @@ -250,16 +250,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly Rectangle bounds; private readonly int width; private readonly int height; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public Rotate90RowOperation( Rectangle bounds, int width, int height, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.bounds = bounds; this.width = width; @@ -271,7 +271,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms [MethodImpl(InliningOptions.ShortMethod)] public void Invoke(int y) { - Span sourceRow = this.source.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan(y); int newX = this.height - y - 1; for (int x = 0; x < this.width; x++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs index 1b93d01a1..b486e4225 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeProcessor{TPixel}.cs @@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms interest, widthFactor, heightFactor, - source, - destination); + source.PixelBuffer, + destination.PixelBuffer); ParallelRowIterator.IterateRows( configuration, @@ -223,8 +223,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms private readonly Rectangle interest; private readonly float widthFactor; private readonly float heightFactor; - private readonly ImageFrame source; - private readonly ImageFrame destination; + private readonly Buffer2D source; + private readonly Buffer2D destination; [MethodImpl(InliningOptions.ShortMethod)] public NNRowOperation( @@ -233,8 +233,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms Rectangle interest, float widthFactor, float heightFactor, - ImageFrame source, - ImageFrame destination) + Buffer2D source, + Buffer2D destination) { this.sourceBounds = sourceBounds; this.destinationBounds = destinationBounds; @@ -256,8 +256,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms int destRight = this.interest.Right; // Y coordinates of source points - Span sourceRow = this.source.GetPixelRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY)); - Span targetRow = this.destination.GetPixelRowSpan(y); + Span sourceRow = this.source.DangerousGetRowSpan((int)(((y - destOriginY) * this.heightFactor) + sourceY)); + Span targetRow = this.destination.DangerousGetRowSpan(y); for (int x = destLeft; x < destRight; x++) { diff --git a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs index aab17d292..191b6fc3a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SwizzleProcessor{TSwizzler,TPixel}.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Processing.Processors.Transforms @@ -27,9 +28,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms { Point p = default; Point newPoint; + Buffer2D sourceBuffer = source.PixelBuffer; for (p.Y = 0; p.Y < source.Height; p.Y++) { - Span rowSpan = source.GetPixelRowSpan(p.Y); + Span rowSpan = sourceBuffer.DangerousGetRowSpan(p.Y); for (p.X = 0; p.X < source.Width; p.X++) { newPoint = this.swizzler.Transform(p); diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index 24b97401d..fe64811fb 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced [WithBlankImages(1, 1, PixelTypes.Rgba32)] [WithBlankImages(100, 111, PixelTypes.Rgba32)] [WithBlankImages(400, 600, PixelTypes.Rgba32)] - public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) + public void DangerousGetRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) where TPixel : unmanaged, IPixel { provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced using Image image = provider.GetImage(); Memory memory = image.DangerousGetPixelRowMemory(image.Height - 1); - Span span = image.GetPixelRowSpan(image.Height - 1); + Span span = image.DangerousGetRowSpan(image.Height - 1); Assert.True(span == memory.Span); } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 50bacfba4..60c796188 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -413,7 +413,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png Rgba32 rgba32 = Color.Blue; for (int y = 0; y < image.Height; y++) { - System.Span rowSpan = image.GetPixelRowSpan(y); + System.Span rowSpan = image.DangerousGetRowSpan(y); // Half of the test image should be transparent. if (y > 25) @@ -443,7 +443,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png for (int y = 0; y < actual.Height; y++) { - System.Span rowSpan = actual.GetPixelRowSpan(y); + System.Span rowSpan = actual.DangerousGetRowSpan(y); if (y > 25) { diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index b48020198..715ae58d7 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp int idx = 0; for (int y = 0; y < image.Height; y++) { - Span rowSpan = image.GetPixelRowSpan(y); + Span rowSpan = image.DangerousGetRowSpan(y); for (int x = 0; x < rowSpan.Length; x++) { bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 5efbe2cba..6d002dfee 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -37,8 +37,8 @@ namespace SixLabors.ImageSharp.Tests { for (int y = 0; y < image.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = image.DangerousGetRowSpan(y); + Span rowClone = clone.DangerousGetRowSpan(y); for (int x = 0; x < image.Width; x++) { @@ -63,8 +63,8 @@ namespace SixLabors.ImageSharp.Tests { for (int y = 0; y < image.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = image.DangerousGetRowSpan(y); + Span rowClone = clone.DangerousGetRowSpan(y); for (int x = 0; x < image.Width; x++) { @@ -88,8 +88,8 @@ namespace SixLabors.ImageSharp.Tests { for (int y = 0; y < image.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = image.DangerousGetRowSpan(y); + Span rowClone = clone.DangerousGetRowSpan(y); for (int x = 0; x < image.Width; x++) { @@ -114,8 +114,8 @@ namespace SixLabors.ImageSharp.Tests { for (int y = 0; y < image.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = image.DangerousGetRowSpan(y); + Span rowClone = clone.DangerousGetRowSpan(y); for (int x = 0; x < image.Width; x++) { diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 9b0d64cc6..9478fd949 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); } } @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Tests image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); } } @@ -265,7 +265,7 @@ namespace SixLabors.ImageSharp.Tests image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); } } @@ -334,7 +334,7 @@ namespace SixLabors.ImageSharp.Tests image.GetPixelMemoryGroup().Fill(bg); for (var i = 10; i < 20; i++) { - image.GetPixelRowSpan(i).Slice(10, 10).Fill(fg); + image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); } } @@ -417,7 +417,7 @@ namespace SixLabors.ImageSharp.Tests { var arrayIndex = width * i; - Span rowSpan = img.GetPixelRowSpan(i); + Span rowSpan = img.DangerousGetRowSpan(i); ref Rgba32 r0 = ref rowSpan[0]; ref Rgba32 r1 = ref array[arrayIndex]; @@ -461,7 +461,7 @@ namespace SixLabors.ImageSharp.Tests { var arrayIndex = pixelSize * width * i; - Span rowSpan = img.GetPixelRowSpan(i); + Span rowSpan = img.DangerousGetRowSpan(i); ref Rgba32 r0 = ref rowSpan[0]; ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 469c0249d..639e2f63c 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -271,7 +271,7 @@ namespace SixLabors.ImageSharp.Tests // Image Assert.Throws(() => { var res = image.Clone(this.configuration); }); Assert.Throws(() => { var res = image.CloneAs(this.configuration); }); - Assert.Throws(() => { var res = image.GetPixelRowSpan(default); }); + Assert.Throws(() => { var res = image.DangerousGetRowSpan(default); }); Assert.Throws(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); // Image diff --git a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs index c9e5d3aa7..77b114fd2 100644 --- a/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs +++ b/tests/ImageSharp.Tests/Quantization/QuantizedImageTests.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); } } } @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Tests using (IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(frame, frame.Bounds())) { int index = this.GetTransparentIndex(quantized); - Assert.Equal(index, quantized.GetPixelRowSpan(0)[0]); + Assert.Equal(index, quantized.DangerousGetRowSpan(0)[0]); } } } diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index 71a8702c7..a9b3f1f36 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Height); Assert.Equal(Color.Black, (Color)result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelRowSpan(0)[0]); + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); } [Fact] @@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Height); Assert.Equal(default, result.Palette.Span[0]); - Assert.Equal(0, result.GetPixelRowSpan(0)[0]); + Assert.Equal(0, result.DangerousGetRowSpan(0)[0]); } [Fact] @@ -99,8 +99,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { - Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); + Span row = actualImage.DangerousGetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); for (int x = 0; x < actualImage.Width; x++) { @@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization for (int y = 0; y < image.Height; y++) { - Assert.True(image.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); + Assert.True(image.DangerousGetRowSpan(y).SequenceEqual(actualImage.DangerousGetRowSpan(y))); } } @@ -166,8 +166,8 @@ namespace SixLabors.ImageSharp.Tests.Quantization int paletteCount = paletteSpan.Length - 1; for (int y = 0; y < actualImage.Height; y++) { - Span row = actualImage.GetPixelRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.GetPixelRowSpan(y); + Span row = actualImage.DangerousGetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); for (int x = 0; x < actualImage.Width; x++) { @@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.Tests.Quantization for (int y = 0; y < expectedImage.Height; y++) { - Assert.True(expectedImage.GetPixelRowSpan(y).SequenceEqual(actualImage.GetPixelRowSpan(y))); + Assert.True(expectedImage.DangerousGetRowSpan(y).SequenceEqual(actualImage.DangerousGetRowSpan(y))); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs index c6bcef461..5c5392260 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison @@ -29,11 +30,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison var differences = new List(); Configuration configuration = expected.GetConfiguration(); + Buffer2D expectedBuffer = expected.PixelBuffer; + Buffer2D actualBuffer = actual.PixelBuffer; for (int y = 0; y < actual.Height; y++) { - Span aSpan = expected.GetPixelRowSpan(y); - Span bSpan = actual.GetPixelRowSpan(y); + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs index 38fb4026d..771d4baf9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison @@ -74,11 +75,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison var differences = new List(); Configuration configuration = expected.GetConfiguration(); + Buffer2D expectedBuffer = expected.PixelBuffer; + Buffer2D actualBuffer = actual.PixelBuffer; for (int y = 0; y < actual.Height; y++) { - Span aSpan = expected.GetPixelRowSpan(y); - Span bSpan = actual.GetPixelRowSpan(y); + Span aSpan = expectedBuffer.DangerousGetRowSpan(y); + Span bSpan = actualBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgba64(configuration, aSpan, aBuffer); PixelOperations.Instance.ToRgba64(configuration, bSpan, bBuffer); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index f5e1f238e..eb310239b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Tests for (int y = 0; y < midY; y++) { - Span row = result.GetPixelRowSpan(y); + Span row = result.DangerousGetRowSpan(y); row.Slice(0, midX).Fill(TopLeftColor); row.Slice(midX, this.Width - midX).Fill(TopRightColor); @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests for (int y = midY; y < this.Height; y++) { - Span row = result.GetPixelRowSpan(y); + Span row = result.DangerousGetRowSpan(y); row.Slice(0, midX).Fill(BottomLeftColor); row.Slice(midX, this.Width - midX).Fill(BottomRightColor); diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs index 157748bdd..28f0dba06 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingBridge.cs @@ -47,14 +47,14 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs long destRowByteCount = w * sizeof(Bgra32); Configuration configuration = image.GetConfiguration(); - - using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) + image.ProcessPixelRows(accessor => { + using IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w); fixed (Bgra32* destPtr = &workBuffer.GetReference()) { for (int y = 0; y < h; y++) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + Span row = accessor.GetRowSpan(y); byte* sourcePtr = sourcePtrBase + (data.Stride * y); @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs row); } } - } + }); } finally { @@ -106,6 +106,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs long destRowByteCount = w * sizeof(Bgr24); Configuration configuration = image.GetConfiguration(); + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; using (IMemoryOwner workBuffer = Configuration.Default.MemoryAllocator.Allocate(w)) { @@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs { for (int y = 0; y < h; y++) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + Span row = imageBuffer.DangerousGetRowSpan(y); byte* sourcePtr = sourcePtrBase + (data.Stride * y); @@ -144,24 +145,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs try { byte* destPtrBase = (byte*)data.Scan0; - long destRowByteCount = data.Stride; long sourceRowByteCount = w * sizeof(Bgra32); - - using (IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w)) + image.ProcessPixelRows(accessor => { + using IMemoryOwner workBuffer = image.GetConfiguration().MemoryAllocator.Allocate(w); fixed (Bgra32* sourcePtr = &workBuffer.GetReference()) { for (int y = 0; y < h; y++) { - Span row = image.Frames.RootFrame.GetPixelRowSpan(y); + Span row = accessor.GetRowSpan(y); PixelOperations.Instance.ToBgra32(configuration, row, workBuffer.GetSpan()); byte* destPtr = destPtrBase + (data.Stride * y); Buffer.MemoryCopy(sourcePtr, destPtr, destRowByteCount, sourceRowByteCount); } } - } + }); } finally { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index 5ebd349c8..d6c62645a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -738,7 +738,7 @@ namespace SixLabors.ImageSharp.Tests Rectangle sourceRectangle = this.SourceRectangle; Configuration configuration = this.Configuration; - var operation = new RowOperation(configuration, sourceRectangle, source); + var operation = new RowOperation(configuration, sourceRectangle, source.PixelBuffer); ParallelRowIterator.IterateRowIntervals( configuration, @@ -750,9 +750,9 @@ namespace SixLabors.ImageSharp.Tests { private readonly Configuration configuration; private readonly Rectangle bounds; - private readonly ImageFrame source; + private readonly Buffer2D source; - public RowOperation(Configuration configuration, Rectangle bounds, ImageFrame source) + public RowOperation(Configuration configuration, Rectangle bounds, Buffer2D source) { this.configuration = configuration; this.bounds = bounds; @@ -763,7 +763,7 @@ namespace SixLabors.ImageSharp.Tests { for (int y = rows.Min; y < rows.Max; y++) { - Span rowSpan = this.source.GetPixelRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); + Span rowSpan = this.source.DangerousGetRowSpan(y).Slice(this.bounds.Left, this.bounds.Width); PixelOperations.Instance.ToVector4(this.configuration, rowSpan, span, PixelConversionModifiers.Scale); for (int i = 0; i < span.Length; i++) { From ebe7b9c51693c223e5bcfec0513f476fc31a931b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 22:28:17 +0200 Subject: [PATCH 031/228] Remove Image.DangerousGetRowSpan --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 50 +++++++------ .../Tiff/Writers/TiffBiColorWriter{TPixel}.cs | 26 ++++--- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 3 +- .../Formats/Webp/Lossy/YuvConversion.cs | 11 +-- src/ImageSharp/Image{TPixel}.cs | 20 ------ .../ProcessingExtensions.IntegralImage.cs | 5 +- .../Advanced/AdvancedImageExtensionsTests.cs | 17 ----- .../Formats/Png/PngEncoderTests.cs | 48 +++++++------ .../Formats/WebP/PredictorEncoderTests.cs | 15 ++-- .../ImageSharp.Tests/Image/ImageCloneTests.cs | 63 ++++++++-------- .../Image/ImageTests.WrapMemory.cs | 71 ++++++++++++------- tests/ImageSharp.Tests/Image/ImageTests.cs | 1 - .../Image/ProcessPixelRowsTestBase.cs | 17 +++++ .../Quantization/WuQuantizerTests.cs | 62 +++++++++------- .../BasicTestPatternProvider.cs | 32 +++++---- 15 files changed, 243 insertions(+), 198 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 3f1b256fc..4645f22e3 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -165,20 +165,24 @@ namespace SixLabors.ImageSharp.Formats.Png private static void ClearTransparentPixels(Image image) where TPixel : unmanaged, IPixel { - Rgba32 rgba32 = default; - for (int y = 0; y < image.Height; y++) + image.ProcessPixelRows(accessor => { - Span span = image.DangerousGetRowSpan(y); - for (int x = 0; x < image.Width; x++) + Rgba32 rgba32 = default; + for (int y = 0; y < accessor.Height; y++) { - span[x].ToRgba32(ref rgba32); - - if (rgba32.A == 0) + Span span = accessor.GetRowSpan(y); + for (int x = 0; x < accessor.Width; x++) { - span[x].FromRgba32(Color.Transparent); + span[x].ToRgba32(ref rgba32); + + if (rgba32.A == 0) + { + span[x].FromRgba32(Color.Transparent); + } } } - } + }); + } /// @@ -914,27 +918,31 @@ namespace SixLabors.ImageSharp.Formats.Png using IMemoryOwner filterBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); using IMemoryOwner attemptBuffer = this.memoryAllocator.Allocate(filterLength, AllocationOptions.Clean); - Span filter = filterBuffer.GetSpan(); - Span attempt = attemptBuffer.GetSpan(); - for (int y = 0; y < this.height; y++) + pixels.ProcessPixelRows(accessor => { - this.CollectAndFilterPixelRow(pixels.DangerousGetRowSpan(y), ref filter, ref attempt, quantized, y); - deflateStream.Write(filter); - this.SwapScanlineBuffers(); - } + Span filter = filterBuffer.GetSpan(); + Span attempt = attemptBuffer.GetSpan(); + for (int y = 0; y < this.height; y++) + { + this.CollectAndFilterPixelRow(accessor.GetRowSpan(y), ref filter, ref attempt, quantized, y); + deflateStream.Write(filter); + this.SwapScanlineBuffers(); + } + }); } /// /// Interlaced encoding the pixels. /// /// The type of the pixel. - /// The pixels. + /// The image. /// The deflate stream. - private void EncodeAdam7Pixels(Image pixels, ZlibDeflateStream deflateStream) + private void EncodeAdam7Pixels(Image image, ZlibDeflateStream deflateStream) where TPixel : unmanaged, IPixel { - int width = pixels.Width; - int height = pixels.Height; + int width = image.Width; + int height = image.Height; + Buffer2D pixelBuffer = image.Frames.RootFrame.PixelBuffer; for (int pass = 0; pass < 7; pass++) { int startRow = Adam7.FirstRow[pass]; @@ -959,7 +967,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int row = startRow; row < height; row += Adam7.RowIncrement[pass]) { // Collect pixel data - Span srcRow = pixels.DangerousGetRowSpan(row); + Span srcRow = pixelBuffer.DangerousGetRowSpan(row); for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass]) { block[i++] = srcRow[col]; diff --git a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs index a26fefe15..5fec09ef1 100644 --- a/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs +++ b/src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs @@ -42,18 +42,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers // Special case for T4BitCompressor. int stripPixels = width * height; this.pixelsAsGray ??= this.MemoryAllocator.Allocate(stripPixels); - Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); - int lastRow = y + height; - int grayRowIdx = 0; - for (int row = y; row < lastRow; row++) + this.imageBlackWhite.ProcessPixelRows(accessor => { - Span pixelsBlackWhiteRow = this.imageBlackWhite.DangerousGetRowSpan(row); - Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); - PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); - grayRowIdx++; - } + Span pixelAsGraySpan = this.pixelsAsGray.GetSpan(); + int lastRow = y + height; + int grayRowIdx = 0; + for (int row = y; row < lastRow; row++) + { + Span pixelsBlackWhiteRow = accessor.GetRowSpan(row); + Span pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width); + PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width); + grayRowIdx++; + } - compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height); + compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height); + }); } else { @@ -65,6 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers Span rows = this.bitStrip.Slice(0, bytesPerStrip); rows.Clear(); + Buffer2D blackWhiteBuffer = this.imageBlackWhite.Frames.RootFrame.PixelBuffer; int outputRowIdx = 0; int lastRow = y + height; @@ -73,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers int bitIndex = 0; int byteIndex = 0; Span outputRow = rows.Slice(outputRowIdx * this.BytesPerRow); - Span pixelsBlackWhiteRow = this.imageBlackWhite.DangerousGetRowSpan(row); + Span pixelsBlackWhiteRow = blackWhiteBuffer.DangerousGetRowSpan(row); PixelOperations.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width); for (int x = 0; x < this.Image.Width; x++) { diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 4cdcb3e86..5e5443a2b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -389,13 +389,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private bool ConvertPixelsToBgra(Image image, int width, int height) where TPixel : unmanaged, IPixel { + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; bool nonOpaque = false; Span bgra = this.Bgra.GetSpan(); Span bgraBytes = MemoryMarshal.Cast(bgra); int widthBytes = width * 4; for (int y = 0; y < height; y++) { - Span rowSpan = image.DangerousGetRowSpan(y); + Span rowSpan = imageBuffer.DangerousGetRowSpan(y); Span rowBytes = bgraBytes.Slice(y * widthBytes, widthBytes); PixelOperations.Instance.ToBgra32Bytes(this.configuration, rowSpan, rowBytes, width); if (!nonOpaque) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 254107682..adf39ee2f 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -31,8 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void ConvertRgbToYuv(Image image, Configuration configuration, MemoryAllocator memoryAllocator, Span y, Span u, Span v) where TPixel : unmanaged, IPixel { - int width = image.Width; - int height = image.Height; + Buffer2D imageBuffer = image.Frames.RootFrame.PixelBuffer; + int width = imageBuffer.Width; + int height = imageBuffer.Height; int uvWidth = (width + 1) >> 1; // Temporary storage for accumulated R/G/B values during conversion to U/V. @@ -46,8 +47,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int rowIndex; for (rowIndex = 0; rowIndex < height - 1; rowIndex += 2) { - Span rowSpan = image.DangerousGetRowSpan(rowIndex); - Span nextRowSpan = image.DangerousGetRowSpan(rowIndex + 1); + Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); + Span nextRowSpan = imageBuffer.DangerousGetRowSpan(rowIndex + 1); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); PixelOperations.Instance.ToBgra32(configuration, nextRowSpan, bgraRow1); @@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Extra last row. if ((height & 1) != 0) { - Span rowSpan = image.DangerousGetRowSpan(rowIndex); + Span rowSpan = imageBuffer.DangerousGetRowSpan(rowIndex); PixelOperations.Instance.ToBgra32(configuration, rowSpan, bgraRow0); ConvertRgbaToY(bgraRow0, y.Slice(rowIndex * width), width); diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index aa3281fbf..710b4fedf 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -300,26 +300,6 @@ namespace SixLabors.ImageSharp } } - /// - /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the first pixel on that row. - /// - /// WARNING: Disposing or leaking the underlying image while still working with it's - /// might lead to memory corruption. - /// - /// The row. - /// The - /// Thrown when row index is out of range. - public Span DangerousGetRowSpan(int rowIndex) - { - Guard.MustBeGreaterThanOrEqualTo(rowIndex, 0, nameof(rowIndex)); - Guard.MustBeLessThan(rowIndex, this.Height, nameof(rowIndex)); - - this.EnsureNotDisposed(); - - return this.PixelSourceUnsafe.PixelBuffer.DangerousGetRowSpan(rowIndex); - } - /// /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. diff --git a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs index fc6aa83a5..a336cfec3 100644 --- a/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs +++ b/src/ImageSharp/Processing/Extensions/ProcessingExtensions.IntegralImage.cs @@ -30,11 +30,12 @@ namespace SixLabors.ImageSharp.Processing Buffer2D intImage = configuration.MemoryAllocator.Allocate2D(source.Width, source.Height); ulong sumX0 = 0; + Buffer2D sourceBuffer = source.Frames.RootFrame.PixelBuffer; using (IMemoryOwner tempRow = configuration.MemoryAllocator.Allocate(source.Width)) { Span tempSpan = tempRow.GetSpan(); - Span sourceRow = source.DangerousGetRowSpan(0); + Span sourceRow = sourceBuffer.DangerousGetRowSpan(0); Span destRow = intImage.DangerousGetRowSpan(0); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); @@ -51,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing // All other rows for (int y = 1; y < endY; y++) { - sourceRow = source.DangerousGetRowSpan(y); + sourceRow = sourceBuffer.DangerousGetRowSpan(y); destRow = intImage.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8(configuration, sourceRow, tempSpan); diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index fe64811fb..2da0cbf83 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -148,22 +148,5 @@ namespace SixLabors.ImageSharp.Tests.Advanced Assert.ThrowsAny(() => _ = memory3.Span); Assert.ThrowsAny(() => _ = memory10.Span); } - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - [WithBlankImages(100, 111, PixelTypes.Rgba32)] - [WithBlankImages(400, 600, PixelTypes.Rgba32)] - public void DangerousGetRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200); - - using Image image = provider.GetImage(); - - Memory memory = image.DangerousGetPixelRowMemory(image.Height - 1); - Span span = image.DangerousGetRowSpan(image.Height - 1); - - Assert.True(span == memory.Span); - } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 60c796188..9e99dded8 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -411,21 +411,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png ColorType = colorType }; Rgba32 rgba32 = Color.Blue; - for (int y = 0; y < image.Height; y++) + image.ProcessPixelRows(accessor => { - System.Span rowSpan = image.DangerousGetRowSpan(y); - - // Half of the test image should be transparent. - if (y > 25) + for (int y = 0; y < image.Height; y++) { - rgba32.A = 0; - } + System.Span rowSpan = accessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) - { - rowSpan[x].FromRgba32(rgba32); + // Half of the test image should be transparent. + if (y > 25) + { + rgba32.A = 0; + } + + for (int x = 0; x < image.Width; x++) + { + rowSpan[x].FromRgba32(rgba32); + } } - } + }); // act using var memStream = new MemoryStream(); @@ -441,20 +444,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png expectedColor = new Rgba32(luminance, luminance, luminance); } - for (int y = 0; y < actual.Height; y++) + actual.ProcessPixelRows(accessor => { - System.Span rowSpan = actual.DangerousGetRowSpan(y); - - if (y > 25) + for (int y = 0; y < accessor.Height; y++) { - expectedColor = Color.Transparent; - } + System.Span rowSpan = accessor.GetRowSpan(y); - for (int x = 0; x < actual.Width; x++) - { - Assert.Equal(expectedColor, rowSpan[x]); + if (y > 25) + { + expectedColor = Color.Transparent; + } + + for (int x = 0; x < accessor.Width; x++) + { + Assert.Equal(expectedColor, rowSpan[x]); + } } - } + }); } [Theory] diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index 715ae58d7..87d665474 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -131,15 +131,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp where TPixel : unmanaged, IPixel { uint[] bgra = new uint[image.Width * image.Height]; - int idx = 0; - for (int y = 0; y < image.Height; y++) + image.ProcessPixelRows(accessor => { - Span rowSpan = image.DangerousGetRowSpan(y); - for (int x = 0; x < rowSpan.Length; x++) + int idx = 0; + for (int y = 0; y < accessor.Height; y++) { - bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + Span rowSpan = accessor.GetRowSpan(y); + for (int x = 0; x < rowSpan.Length; x++) + { + bgra[idx++] = ToBgra32(rowSpan[x]).PackedValue; + } } - } + }); return bgra; } diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 6d002dfee..8cca00a73 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -32,15 +32,17 @@ namespace SixLabors.ImageSharp.Tests [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToBgra32(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.DangerousGetRowSpan(y); - Span rowClone = clone.DangerousGetRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < imageAccessor.Width; x++) { Rgba32 expected = row[x]; Bgra32 actual = rowClone[x]; @@ -51,22 +53,24 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.A, actual.A); } } - } + }); } [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToBgr24(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.DangerousGetRowSpan(y); - Span rowClone = clone.DangerousGetRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < cloneAccessor.Width; x++) { Rgba32 expected = row[x]; Bgr24 actual = rowClone[x]; @@ -76,22 +80,23 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.B, actual.B); } } - } + }); } [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToArgb32(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.DangerousGetRowSpan(y); - Span rowClone = clone.DangerousGetRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < cloneAccessor.Width; x++) { Rgba32 expected = row[x]; Argb32 actual = rowClone[x]; @@ -102,22 +107,23 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.A, actual.A); } } - } + }); } [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToRgb24(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.DangerousGetRowSpan(y); - Span rowClone = clone.DangerousGetRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < imageAccessor.Width; x++) { Rgba32 expected = row[x]; Rgb24 actual = rowClone[x]; @@ -127,7 +133,8 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.B, actual.B); } } - } + }); + } } } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index 9478fd949..dd1f5b701 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -163,10 +163,14 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); image.GetPixelMemoryGroup().Fill(bg); - for (var i = 10; i < 20; i++) + + image.ProcessPixelRows(accessor => { - image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.False(memoryManager.IsDisposed); @@ -198,10 +202,13 @@ namespace SixLabors.ImageSharp.Tests { Assert.Equal(memoryManager.Memory, image.GetRootFramePixelBuffer().DangerousGetSingleMemory()); image.GetPixelMemoryGroup().Fill(bg); - for (var i = 10; i < 20; i++) + image.ProcessPixelRows(accessor => { - image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.True(memoryManager.IsDisposed); @@ -263,10 +270,13 @@ namespace SixLabors.ImageSharp.Tests Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); image.GetPixelMemoryGroup().Fill(bg); - for (var i = 10; i < 20; i++) + image.ProcessPixelRows(accessor => { - image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.False(memoryManager.IsDisposed); @@ -332,10 +342,13 @@ namespace SixLabors.ImageSharp.Tests Assert.True(Unsafe.AreSame(ref pixelSpan.GetPinnableReference(), ref imageSpan.GetPinnableReference())); image.GetPixelMemoryGroup().Fill(bg); - for (var i = 10; i < 20; i++) + image.ProcessPixelRows(accessor => { - image.DangerousGetRowSpan(i).Slice(10, 10).Fill(fg); - } + for (var i = 10; i < 20; i++) + { + accessor.GetRowSpan(i).Slice(10, 10).Fill(fg); + } + }); } Assert.False(memoryManager.IsDisposed); @@ -413,16 +426,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(width, img.Width); Assert.Equal(height, img.Height); - for (int i = 0; i < height; ++i) + img.ProcessPixelRows(accessor => { - var arrayIndex = width * i; + for (int i = 0; i < height; ++i) + { + var arrayIndex = width * i; - Span rowSpan = img.DangerousGetRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref array[arrayIndex]; + Span rowSpan = accessor.GetRowSpan(i); + ref Rgba32 r0 = ref rowSpan[0]; + ref Rgba32 r1 = ref array[arrayIndex]; - Assert.True(Unsafe.AreSame(ref r0, ref r1)); - } + Assert.True(Unsafe.AreSame(ref r0, ref r1)); + } + }); } Assert.True(memory.Disposed); @@ -457,16 +473,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(width, img.Width); Assert.Equal(height, img.Height); - for (int i = 0; i < height; ++i) + img.ProcessPixelRows(acccessor => { - var arrayIndex = pixelSize * width * i; + for (int i = 0; i < height; ++i) + { + var arrayIndex = pixelSize * width * i; - Span rowSpan = img.DangerousGetRowSpan(i); - ref Rgba32 r0 = ref rowSpan[0]; - ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); + Span rowSpan = acccessor.GetRowSpan(i); + ref Rgba32 r0 = ref rowSpan[0]; + ref Rgba32 r1 = ref Unsafe.As(ref array[arrayIndex]); - Assert.True(Unsafe.AreSame(ref r0, ref r1)); - } + Assert.True(Unsafe.AreSame(ref r0, ref r1)); + } + }); } Assert.True(memory.Disposed); diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 639e2f63c..7d6963a04 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -271,7 +271,6 @@ namespace SixLabors.ImageSharp.Tests // Image Assert.Throws(() => { var res = image.Clone(this.configuration); }); Assert.Throws(() => { var res = image.CloneAs(this.configuration); }); - Assert.Throws(() => { var res = image.DangerousGetRowSpan(default); }); Assert.Throws(() => { var res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); // Image diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs index e0626395d..53eb1da14 100644 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -154,6 +154,23 @@ namespace SixLabors.ImageSharp.Tests } } + [Fact] + public void Disposed_ThrowsObjectDisposedException() + { + using var nonDisposed = new Image(1, 1); + var disposed = new Image(1, 1); + disposed.Dispose(); + + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, _ => { })); + + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, (_, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, (_, _) => { })); + + Assert.Throws(() => this.ProcessPixelRowsImpl(disposed, nonDisposed, nonDisposed, (_, _, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, disposed, nonDisposed, (_, _, _) => { })); + Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, nonDisposed, disposed, (_, _, _) => { })); + } + [Fact] public void RetainsUnmangedBuffers1() { diff --git a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs index a9b3f1f36..b835aa63e 100644 --- a/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Quantization/WuQuantizerTests.cs @@ -93,25 +93,31 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Width); Assert.Equal(256, result.Height); - var actualImage = new Image(1, 256); + using var actualImage = new Image(1, 256); - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < actualImage.Height; y++) + actualImage.ProcessPixelRows(accessor => { - Span row = actualImage.DangerousGetRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); - - for (int x = 0; x < actualImage.Width; x++) + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < accessor.Height; y++) { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + Span row = accessor.GetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); + + for (int x = 0; x < accessor.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + } } - } + }); - for (int y = 0; y < image.Height; y++) + image.ProcessPixelRows(actualImage, static (imageAccessor, actualImageAccessor) => { - Assert.True(image.DangerousGetRowSpan(y).SequenceEqual(actualImage.DangerousGetRowSpan(y))); - } + for (int y = 0; y < imageAccessor.Height; y++) + { + Assert.True(imageAccessor.GetRowSpan(y).SequenceEqual(actualImageAccessor.GetRowSpan(y))); + } + }); } [Theory] @@ -162,24 +168,30 @@ namespace SixLabors.ImageSharp.Tests.Quantization Assert.Equal(1, result.Width); Assert.Equal(256, result.Height); - ReadOnlySpan paletteSpan = result.Palette.Span; - int paletteCount = paletteSpan.Length - 1; - for (int y = 0; y < actualImage.Height; y++) + actualImage.ProcessPixelRows(accessor => { - Span row = actualImage.DangerousGetRowSpan(y); - ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); - - for (int x = 0; x < actualImage.Width; x++) + ReadOnlySpan paletteSpan = result.Palette.Span; + int paletteCount = paletteSpan.Length - 1; + for (int y = 0; y < accessor.Height; y++) { - row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + Span row = accessor.GetRowSpan(y); + ReadOnlySpan quantizedPixelSpan = result.DangerousGetRowSpan(y); + + for (int x = 0; x < accessor.Width; x++) + { + row[x] = paletteSpan[Math.Min(paletteCount, quantizedPixelSpan[x])]; + } } - } + }); } - for (int y = 0; y < expectedImage.Height; y++) + expectedImage.ProcessPixelRows(actualImage, static (expectedAccessor, actualAccessor) => { - Assert.True(expectedImage.DangerousGetRowSpan(y).SequenceEqual(actualImage.DangerousGetRowSpan(y))); - } + for (int y = 0; y < expectedAccessor.Height; y++) + { + Assert.True(expectedAccessor.GetRowSpan(y).SequenceEqual(actualAccessor.GetRowSpan(y))); + } + }); } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index eb310239b..3b8d0073e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -39,25 +39,29 @@ namespace SixLabors.ImageSharp.Tests public override Image GetImage() { var result = new Image(this.Configuration, this.Width, this.Height); + result.ProcessPixelRows(accessor => + { + int midY = this.Height / 2; + int midX = this.Width / 2; - int midY = this.Height / 2; - int midX = this.Width / 2; + for (int y = 0; y < midY; y++) + { + Span row = accessor.GetRowSpan(y); - for (int y = 0; y < midY; y++) - { - Span row = result.DangerousGetRowSpan(y); + row.Slice(0, midX).Fill(TopLeftColor); + row.Slice(midX, this.Width - midX).Fill(TopRightColor); + } - row.Slice(0, midX).Fill(TopLeftColor); - row.Slice(midX, this.Width - midX).Fill(TopRightColor); - } + for (int y = midY; y < this.Height; y++) + { + Span row = accessor.GetRowSpan(y); + + row.Slice(0, midX).Fill(BottomLeftColor); + row.Slice(midX, this.Width - midX).Fill(BottomRightColor); + } + }); - for (int y = midY; y < this.Height; y++) - { - Span row = result.DangerousGetRowSpan(y); - row.Slice(0, midX).Fill(BottomLeftColor); - row.Slice(midX, this.Width - midX).Fill(BottomRightColor); - } return result; } From f40fc3a76deb46a02e20eca0263654e8f033fc9e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 30 Oct 2021 22:41:36 +0200 Subject: [PATCH 032/228] test exception safety of ProcessPixelRows --- .../Image/ProcessPixelRowsTestBase.cs | 100 +++++++++++++----- 1 file changed, 73 insertions(+), 27 deletions(-) diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs index 53eb1da14..255e1a9a4 100644 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -171,13 +171,16 @@ namespace SixLabors.ImageSharp.Tests Assert.Throws(() => this.ProcessPixelRowsImpl(nonDisposed, nonDisposed, disposed, (_, _, _) => { })); } - [Fact] - public void RetainsUnmangedBuffers1() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers1(bool throwException) { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - static void RunTest(string testTypeName) + static void RunTest(string testTypeName, string throwExceptionStr) { + bool throwExceptionInner = bool.Parse(throwExceptionStr); var buffer = new UnmanagedBuffer(100); var allocator = new MockUnmanagedMemoryAllocator(buffer); Configuration.Default.MemoryAllocator = allocator; @@ -185,22 +188,36 @@ namespace SixLabors.ImageSharp.Tests var image = new Image(10, 10); Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => + try + { + GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => + { + buffer.BufferHandle.Dispose(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) + { + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) { - buffer.BufferHandle.Dispose(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - }); + } + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } } - [Fact] - public void RetainsUnmangedBuffers2() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers2(bool throwException) { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - static void RunTest(string testTypeName) + static void RunTest(string testTypeName, string throwExceptionStr) { + bool throwExceptionInner = bool.Parse(throwExceptionStr); var buffer1 = new UnmanagedBuffer(100); var buffer2 = new UnmanagedBuffer(100); var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2); @@ -210,23 +227,37 @@ namespace SixLabors.ImageSharp.Tests var image2 = new Image(10, 10); Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); - GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => + try { - buffer1.BufferHandle.Dispose(); - buffer2.BufferHandle.Dispose(); - Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); - }); + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => + { + buffer1.BufferHandle.Dispose(); + buffer2.BufferHandle.Dispose(); + Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) + { + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) + { + } + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } } - [Fact] - public void RetainsUnmangedBuffers3() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void RetainsUnmangedBuffers3(bool throwException) { - RemoteExecutor.Invoke(RunTest, this.GetType().FullName).Dispose(); + RemoteExecutor.Invoke(RunTest, this.GetType().FullName, throwException.ToString()).Dispose(); - static void RunTest(string testTypeName) + static void RunTest(string testTypeName, string throwExceptionStr) { + bool throwExceptionInner = bool.Parse(throwExceptionStr); var buffer1 = new UnmanagedBuffer(100); var buffer2 = new UnmanagedBuffer(100); var buffer3 = new UnmanagedBuffer(100); @@ -238,13 +269,24 @@ namespace SixLabors.ImageSharp.Tests var image3 = new Image(10, 10); Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => + try + { + GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => + { + buffer1.BufferHandle.Dispose(); + buffer2.BufferHandle.Dispose(); + buffer3.BufferHandle.Dispose(); + Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); + if (throwExceptionInner) + { + throw new NonFatalException(); + } + }); + } + catch (NonFatalException) { - buffer1.BufferHandle.Dispose(); - buffer2.BufferHandle.Dispose(); - buffer3.BufferHandle.Dispose(); - Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); - }); + } + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } } @@ -255,6 +297,10 @@ namespace SixLabors.ImageSharp.Tests return (ProcessPixelRowsTestBase)Activator.CreateInstance(type); } + private class NonFatalException : Exception + { + } + private class MockUnmanagedMemoryAllocator : MemoryAllocator where T1 : struct { From 58a23cdf1d221470e52e499377e904c34c5f4fa0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Nov 2021 16:34:12 +0100 Subject: [PATCH 033/228] remove ArrayPoolMemoryAllocator from tests --- .../ImageSharp.Tests/Image/ImageFrameTests.cs | 6 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 11 +- .../ArrayPoolMemoryAllocatorTests.cs | 276 ------------------ .../ImageProviders/FileProvider.cs | 22 +- .../ImageProviders/TestImageProvider.cs | 8 +- .../TestUtilities/TestImageExtensions.cs | 10 +- .../TestUtilities/TestUtils.cs | 6 +- 7 files changed, 18 insertions(+), 321 deletions(-) delete mode 100644 tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index 344ebac95..9e3aed2b0 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -4,6 +4,7 @@ using System; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Memory; using Xunit; namespace SixLabors.ImageSharp.Tests @@ -16,10 +17,7 @@ namespace SixLabors.ImageSharp.Tests private void LimitBufferCapacity(int bufferCapacityInBytes) { - // TODO: Create a test-only MemoryAllocator for this -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); -#pragma warning restore CS0618 + var allocator = new TestMemoryAllocator(); allocator.BufferCapacityInBytes = bufferCapacityInBytes; this.configuration.MemoryAllocator = allocator; } diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 7d6963a04..4f68453a9 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -97,15 +97,8 @@ namespace SixLabors.ImageSharp.Tests { private readonly Configuration configuration = Configuration.CreateDefaultInstance(); - private void LimitBufferCapacity(int bufferCapacityInBytes) - { - // TODO: Create a test-only MemoryAllocator for this -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); -#pragma warning restore CS0618 - allocator.BufferCapacityInBytes = bufferCapacityInBytes; - this.configuration.MemoryAllocator = allocator; - } + private void LimitBufferCapacity(int bufferCapacityInBytes) => + this.configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = bufferCapacityInBytes }; [Theory] [InlineData(false)] diff --git a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs deleted file mode 100644 index 909617bac..000000000 --- a/tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Microsoft.DotNet.RemoteExecutor; -using SixLabors.ImageSharp.Memory; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Memory.Allocators -{ -#pragma warning disable CS0618 - public class ArrayPoolMemoryAllocatorTests - { - private const int MaxPooledBufferSizeInBytes = 2048; - - private const int PoolSelectorThresholdInBytes = MaxPooledBufferSizeInBytes / 2; - - /// - /// Gets the SUT for in-process tests. - /// - private MemoryAllocatorFixture LocalFixture { get; } = new MemoryAllocatorFixture(); - - /// - /// Gets the SUT for tests executed by , - /// recreated in each external process. - /// - private static MemoryAllocatorFixture StaticFixture { get; } = new MemoryAllocatorFixture(); - - public class BufferTests : BufferTestSuite - { - public BufferTests() - : base(new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes)) - { - } - } - - public class Constructor - { - [Fact] - public void WhenBothParametersPassedByUser() - { - var mgr = new ArrayPoolMemoryAllocator(1111, 666); - Assert.Equal(1111, mgr.MaxPoolSizeInBytes); - Assert.Equal(666, mgr.PoolSelectorThresholdInBytes); - } - - [Fact] - public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdValueIsAutoCalculated() - { - var mgr = new ArrayPoolMemoryAllocator(5000); - Assert.Equal(5000, mgr.MaxPoolSizeInBytes); - Assert.True(mgr.PoolSelectorThresholdInBytes < mgr.MaxPoolSizeInBytes); - } - - [Fact] - public void When_PoolSelectorThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_ExceptionIsThrown() - => Assert.ThrowsAny(() => new ArrayPoolMemoryAllocator(100, 200)); - } - - [Theory] - [InlineData(32)] - [InlineData(512)] - [InlineData(MaxPooledBufferSizeInBytes - 1)] - public void SmallBuffersArePooled_OfByte(int size) => Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(size)); - - [Theory] - [InlineData(128 * 1024 * 1024)] - [InlineData(MaxPooledBufferSizeInBytes + 1)] - public void LargeBuffersAreNotPooled_OfByte(int size) - { - static void RunTest(string sizeStr) - { - int size = int.Parse(sizeStr); - StaticFixture.CheckIsRentingPooledBuffer(size); - } - - RemoteExecutor.Invoke(RunTest, size.ToString()).Dispose(); - } - - [Fact] - public unsafe void SmallBuffersArePooled_OfBigValueType() - { - int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) - 1; - - Assert.True(this.LocalFixture.CheckIsRentingPooledBuffer(count)); - } - - [Fact] - public unsafe void LaregeBuffersAreNotPooled_OfBigValueType() - { - int count = (MaxPooledBufferSizeInBytes / sizeof(LargeStruct)) + 1; - - Assert.False(this.LocalFixture.CheckIsRentingPooledBuffer(count)); - } - - [Theory] - [InlineData(AllocationOptions.None)] - [InlineData(AllocationOptions.Clean)] - public void CleaningRequests_AreControlledByAllocationParameter_Clean(AllocationOptions options) - { - MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; - using (IMemoryOwner firstAlloc = memoryAllocator.Allocate(42)) - { - BufferExtensions.GetSpan(firstAlloc).Fill(666); - } - - using (IMemoryOwner secondAlloc = memoryAllocator.Allocate(42, options)) - { - int expected = options == AllocationOptions.Clean ? 0 : 666; - Assert.Equal(expected, BufferExtensions.GetSpan(secondAlloc)[0]); - } - } - - [Fact] - public unsafe void Allocate_MemoryIsPinnableMultipleTimes() - { - ArrayPoolMemoryAllocator allocator = this.LocalFixture.MemoryAllocator; - using IMemoryOwner memoryOwner = allocator.Allocate(100); - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - - using (MemoryHandle pin = memoryOwner.Memory.Pin()) - { - Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void ReleaseRetainedResources_ReplacesInnerArrayPool(bool keepBufferAlive) - { - MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; - IMemoryOwner buffer = memoryAllocator.Allocate(32); - ref int ptrToPrev0 = ref MemoryMarshal.GetReference(BufferExtensions.GetSpan(buffer)); - - if (!keepBufferAlive) - { - buffer.Dispose(); - } - - memoryAllocator.ReleaseRetainedResources(); - - buffer = memoryAllocator.Allocate(32); - - Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref BufferExtensions.GetReference(buffer))); - } - - [Fact] - public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed() - { - MemoryAllocator memoryAllocator = this.LocalFixture.MemoryAllocator; - IMemoryOwner buffer = memoryAllocator.Allocate(32); - memoryAllocator.ReleaseRetainedResources(); - buffer.Dispose(); - } - - [Fact] - public void AllocationOverLargeArrayThreshold_UsesDifferentPool() - { - static void RunTest() - { - const int ArrayLengthThreshold = PoolSelectorThresholdInBytes / sizeof(int); - - IMemoryOwner small = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold - 1); - ref int ptr2Small = ref BufferExtensions.GetReference(small); - small.Dispose(); - - IMemoryOwner large = StaticFixture.MemoryAllocator.Allocate(ArrayLengthThreshold + 1); - - Assert.False(Unsafe.AreSame(ref ptr2Small, ref BufferExtensions.GetReference(large))); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Fact] - public void CreateWithAggressivePooling() - { - static void RunTest() - { - StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithAggressivePooling(); - Assert.True(StaticFixture.CheckIsRentingPooledBuffer(4096 * 4096)); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Fact] - public void CreateDefault() - { - static void RunTest() - { - StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); - - Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2 * 4096 * 4096)); - Assert.True(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Fact] - public void CreateWithModeratePooling() - { - static void RunTest() - { - StaticFixture.MemoryAllocator = ArrayPoolMemoryAllocator.CreateWithModeratePooling(); - Assert.False(StaticFixture.CheckIsRentingPooledBuffer(2048 * 2048)); - Assert.True(StaticFixture.CheckIsRentingPooledBuffer(1024 * 16)); - } - - RemoteExecutor.Invoke(RunTest).Dispose(); - } - - [Theory] - [InlineData(-1)] - [InlineData(-111)] - public void Allocate_Negative_Throws_ArgumentOutOfRangeException(int length) - { - ArgumentOutOfRangeException ex = Assert.Throws(() => - this.LocalFixture.MemoryAllocator.Allocate(length)); - Assert.Equal("length", ex.ParamName); - } - - [Fact] - public void AllocateZero() - { - using IMemoryOwner buffer = this.LocalFixture.MemoryAllocator.Allocate(0); - Assert.Equal(0, buffer.Memory.Length); - } - - private class MemoryAllocatorFixture - { - public ArrayPoolMemoryAllocator MemoryAllocator { get; set; } = - new ArrayPoolMemoryAllocator(MaxPooledBufferSizeInBytes, PoolSelectorThresholdInBytes); - - /// - /// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location. - /// - public bool CheckIsRentingPooledBuffer(int length) - where T : struct - { - IMemoryOwner buffer = this.MemoryAllocator.Allocate(length); - ref T ptrToPrevPosition0 = ref BufferExtensions.GetReference(buffer); - buffer.Dispose(); - - buffer = this.MemoryAllocator.Allocate(length); - bool sameBuffers = Unsafe.AreSame(ref ptrToPrevPosition0, ref BufferExtensions.GetReference(buffer)); - buffer.Dispose(); - - return sameBuffers; - } - } - - [StructLayout(LayoutKind.Sequential)] - private struct SmallStruct - { - private readonly uint dummy; - } - - private const int SizeOfLargeStruct = MaxPooledBufferSizeInBytes / 5; - - [StructLayout(LayoutKind.Explicit, Size = SizeOfLargeStruct)] - private struct LargeStruct - { - } - } -#pragma warning restore CS0618 -} diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 4b2736009..e2e7d73bc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -24,18 +24,17 @@ namespace SixLabors.ImageSharp.Tests // are shared between PixelTypes.Color & PixelTypes.Rgba32 private class Key : IEquatable { - private readonly Tuple commonValues; + private readonly Tuple commonValues; private readonly Dictionary decoderParameters; - public Key(PixelTypes pixelType, string filePath, int allocatorBufferCapacity, IImageDecoder customDecoder) + public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) { Type customType = customDecoder?.GetType(); - this.commonValues = new Tuple( + this.commonValues = new Tuple( pixelType, filePath, - customType, - allocatorBufferCapacity); + customType); this.decoderParameters = GetDecoderParameters(customDecoder); } @@ -153,20 +152,13 @@ namespace SixLabors.ImageSharp.Tests { Guard.NotNull(decoder, nameof(decoder)); - if (!TestEnvironment.Is64BitProcess) + // Do not cache with 64 bits or if image has been created with non-default MemoryAllocator + if (!TestEnvironment.Is64BitProcess || this.Configuration.MemoryAllocator != MemoryAllocator.Default) { return this.LoadImage(decoder); } - int bufferCapacity = -1; -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - if (this.Configuration.MemoryAllocator is ArrayPoolMemoryAllocator arrayPoolMemoryAllocator) -#pragma warning restore CS0618 - { - bufferCapacity = arrayPoolMemoryAllocator.BufferCapacityInBytes; - } - - var key = new Key(this.PixelType, this.FilePath, bufferCapacity, decoder); + var key = new Key(this.PixelType, this.FilePath, decoder); Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder)); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index a30cdb4c5..700c40b72 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -21,17 +21,11 @@ namespace SixLabors.ImageSharp.Tests public abstract partial class TestImageProvider : ITestImageProvider, IXunitSerializable where TPixel : unmanaged, IPixel { - public TestImageProvider() - { - this.Configuration = Configuration.CreateDefaultInstance(); - this.Configuration.MemoryAllocator = Configuration.Default.MemoryAllocator; - } - public PixelTypes PixelType { get; private set; } = typeof(TPixel).GetPixelType(); public virtual string SourceFileOrDescription => string.Empty; - public Configuration Configuration { get; set; } + public Configuration Configuration { get; set; } = Configuration.CreateDefaultInstance(); /// /// Gets the utility instance to provide information about the test image & manage input/output. diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index d6c62645a..719e52946 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors; +using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -692,10 +693,7 @@ namespace SixLabors.ImageSharp.Tests this TestImageProvider provider) where TPixel : unmanaged, IPixel { - // TODO: Use a test-only allocator for this. -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); -#pragma warning restore + var allocator = new TestMemoryAllocator(); provider.Configuration.MemoryAllocator = allocator; return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); } @@ -781,10 +779,10 @@ namespace SixLabors.ImageSharp.Tests internal class AllocatorBufferCapacityConfigurator { #pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - private readonly ArrayPoolMemoryAllocator allocator; + private readonly TestMemoryAllocator allocator; private readonly int pixelSizeInBytes; - public AllocatorBufferCapacityConfigurator(ArrayPoolMemoryAllocator allocator, int pixelSizeInBytes) + public AllocatorBufferCapacityConfigurator(TestMemoryAllocator allocator, int pixelSizeInBytes) { this.allocator = allocator; this.pixelSizeInBytes = pixelSizeInBytes; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index f3b321f30..e24fb7624 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Transforms; +using SixLabors.ImageSharp.Tests.Memory; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -165,10 +166,7 @@ namespace SixLabors.ImageSharp.Tests int width = expected.Width; expected.Mutate(process); - // TODO: Use a test-only allocator for this -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - var allocator = ArrayPoolMemoryAllocator.CreateDefault(); -#pragma warning restore CS0618 + var allocator = new TestMemoryAllocator(); provider.Configuration.MemoryAllocator = allocator; allocator.BufferCapacityInBytes = bufferCapacityInPixelRows * width * Unsafe.SizeOf(); From 74d8be625631f46e7af1777c7e71fa8180db70b3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Nov 2021 16:35:43 +0100 Subject: [PATCH 034/228] fix warning & simplify ClearTransparentPixels --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 4645f22e3..5e067aba5 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -163,11 +163,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// The type of the pixel. /// The cloned image where the transparent pixels will be changed. private static void ClearTransparentPixels(Image image) - where TPixel : unmanaged, IPixel - { + where TPixel : unmanaged, IPixel => image.ProcessPixelRows(accessor => { Rgba32 rgba32 = default; + Rgba32 transparent = Color.Transparent; for (int y = 0; y < accessor.Height; y++) { Span span = accessor.GetRowSpan(y); @@ -177,14 +177,12 @@ namespace SixLabors.ImageSharp.Formats.Png if (rgba32.A == 0) { - span[x].FromRgba32(Color.Transparent); + span[x].FromRgba32(transparent); } } } }); - } - /// /// Creates the quantized image and sets calculates and sets the bit depth. /// From 412ebfb8c68a1154fc5d6103cf3867b8550b15b6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Nov 2021 16:52:20 +0100 Subject: [PATCH 035/228] Remove ArrayPoolMemoryAllocator --- .../ArrayPoolMemoryAllocator.Buffer{T}.cs | 91 ---------- ...oolMemoryAllocator.CommonFactoryMethods.cs | 76 -------- .../Allocators/ArrayPoolMemoryAllocator.cs | 166 ------------------ .../LoadResizeSaveStressBenchmarks.cs | 21 +-- .../LoadResizeSaveParallelMemoryStress.cs | 56 ++---- 5 files changed, 19 insertions(+), 391 deletions(-) delete mode 100644 src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs delete mode 100644 src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs delete mode 100644 src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs deleted file mode 100644 index 5200a2793..000000000 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory.Internals; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Contains . - /// - public partial class ArrayPoolMemoryAllocator - { - /// - /// The buffer implementation of . - /// - private class Buffer : ManagedBufferBase - where T : struct - { - /// - /// The length of the buffer. - /// - private readonly int length; - - /// - /// A weak reference to the source pool. - /// - /// - /// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed - /// after a call to , regardless of having buffer instances still being in use. - /// - private WeakReference> sourcePoolReference; - - public Buffer(byte[] data, int length, ArrayPool sourcePool) - { - this.Data = data; - this.length = length; - this.sourcePoolReference = new WeakReference>(sourcePool); - } - - /// - /// Gets the buffer as a byte array. - /// - protected byte[] Data { get; private set; } - - /// - public override Span GetSpan() - { - if (this.Data is null) - { - ThrowObjectDisposedException(); - } -#if SUPPORTS_CREATESPAN - ref byte r0 = ref MemoryMarshal.GetReference(this.Data); - return MemoryMarshal.CreateSpan(ref Unsafe.As(ref r0), this.length); -#else - return MemoryMarshal.Cast(this.Data.AsSpan()).Slice(0, this.length); -#endif - - } - - /// - protected override void Dispose(bool disposing) - { - if (!disposing || this.Data is null || this.sourcePoolReference is null) - { - return; - } - - if (this.sourcePoolReference.TryGetTarget(out ArrayPool pool)) - { - pool.Return(this.Data); - } - - this.sourcePoolReference = null; - this.Data = null; - } - - protected override object GetPinnableObject() => this.Data; - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowObjectDisposedException() - { - throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer"); - } - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs deleted file mode 100644 index 8aa1b9063..000000000 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Contains common factory methods and configuration constants. - /// - public partial class ArrayPoolMemoryAllocator - { - /// - /// The default value for: maximum size of pooled arrays in bytes. - /// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data. - /// - internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024; - - /// - /// The value for: The threshold to pool arrays in which has less buckets for memory safety. - /// - private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024; - - /// - /// The default bucket count for . - /// - private const int DefaultLargePoolBucketCount = 6; - - /// - /// The default bucket count for . - /// - private const int DefaultNormalPoolBucketCount = 16; - - // TODO: This value should be determined by benchmarking - private const int DefaultBufferCapacityInBytes = int.MaxValue / 4; - - /// - /// This is the default. Should be good for most use cases. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateDefault() - { - return new ArrayPoolMemoryAllocator( - DefaultMaxPooledBufferSizeInBytes, - DefaultBufferSelectorThresholdInBytes, - DefaultLargePoolBucketCount, - DefaultNormalPoolBucketCount, - DefaultBufferCapacityInBytes); - } - - /// - /// For environments with very limited memory capabilities, only small buffers like image rows are pooled. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateWithMinimalPooling() - { - return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24); - } - - /// - /// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateWithModeratePooling() - { - return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24); - } - - /// - /// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput. - /// - /// The memory manager. - public static ArrayPoolMemoryAllocator CreateWithAggressivePooling() - { - return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32); - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs deleted file mode 100644 index eb34b813f..000000000 --- a/src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory -{ - /// - /// Implements by allocating memory from . - /// - [Obsolete("ArrayPoolMemoryAllocator is obsolete. Use MemoryAllocator.CreateDefault() instead.")] - public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator - { - private readonly int maxArraysPerBucketNormalPool; - - private readonly int maxArraysPerBucketLargePool; - - /// - /// The for small-to-medium buffers which is not kept clean. - /// - private ArrayPool normalArrayPool; - - /// - /// The for huge buffers, which is not kept clean. - /// - private ArrayPool largeArrayPool; - - /// - /// Initializes a new instance of the class. - /// - public ArrayPoolMemoryAllocator() - : this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes) - : this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - /// Arrays over this threshold will be pooled in which has less buckets for memory safety. - public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes) - : this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - /// The threshold to pool arrays in which has less buckets for memory safety. - /// Max arrays per bucket for the large array pool. - /// Max arrays per bucket for the normal array pool. - public ArrayPoolMemoryAllocator( - int maxPoolSizeInBytes, - int poolSelectorThresholdInBytes, - int maxArraysPerBucketLargePool, - int maxArraysPerBucketNormalPool) - : this( - maxPoolSizeInBytes, - poolSelectorThresholdInBytes, - maxArraysPerBucketLargePool, - maxArraysPerBucketNormalPool, - DefaultBufferCapacityInBytes) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated. - /// The threshold to pool arrays in which has less buckets for memory safety. - /// Max arrays per bucket for the large array pool. - /// Max arrays per bucket for the normal array pool. - /// The length of the largest contiguous buffer that can be handled by this allocator instance. - public ArrayPoolMemoryAllocator( - int maxPoolSizeInBytes, - int poolSelectorThresholdInBytes, - int maxArraysPerBucketLargePool, - int maxArraysPerBucketNormalPool, - int bufferCapacityInBytes) - { - Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes)); - Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes)); - - this.MaxPoolSizeInBytes = maxPoolSizeInBytes; - this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes; - this.BufferCapacityInBytes = bufferCapacityInBytes; - this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool; - this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool; - - this.InitArrayPools(); - } - - /// - /// Gets the maximum size of pooled arrays in bytes. - /// - public int MaxPoolSizeInBytes { get; } - - /// - /// Gets the threshold to pool arrays in which has less buckets for memory safety. - /// - public int PoolSelectorThresholdInBytes { get; } - - /// - /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance. - /// - public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests - - /// - public override void ReleaseRetainedResources() - { - this.InitArrayPools(); - } - - /// - protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; - - /// - public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) - { - Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); - int itemSizeBytes = Unsafe.SizeOf(); - int bufferSizeInBytes = length * itemSizeBytes; - - ArrayPool pool = this.GetArrayPool(bufferSizeInBytes); - byte[] byteArray = pool.Rent(bufferSizeInBytes); - - var buffer = new Buffer(byteArray, length, pool); - if (options.Has(AllocationOptions.Clean)) - { - buffer.GetSpan().Clear(); - } - - return buffer; - } - - private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) => maxPoolSizeInBytes / 4; - - [MethodImpl(InliningOptions.ColdPath)] - private static void ThrowInvalidAllocationException(int length, int max) => - throw new InvalidMemoryOperationException( - $"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator."); - - private ArrayPool GetArrayPool(int bufferSizeInBytes) - { - return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool; - } - - private void InitArrayPools() - { - this.largeArrayPool = ArrayPool.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool); - this.normalArrayPool = ArrayPool.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool); - } - } -} diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs index 9d6804e64..b998863e8 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs @@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // private const JpegKind Filter = JpegKind.Progressive; private const JpegKind Filter = JpegKind.Any; -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - private ArrayPoolMemoryAllocator arrayPoolMemoryAllocator; -#pragma warning restore CS0618 - private MemoryAllocator defaultMemoryAllocator; - [GlobalSetup] public void Setup() { @@ -32,11 +27,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave }; Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}"); this.runner.Init(); - this.defaultMemoryAllocator = Configuration.Default.MemoryAllocator; - -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - this.arrayPoolMemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault(); -#pragma warning restore CS0618 } private void ForEachImage(Action action, int maxDegreeOfParallelism) @@ -59,17 +49,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave [Benchmark(Baseline = true)] [ArgumentsSource(nameof(ParallelismValues))] - public void ImageSharp_DefaultMemoryAllocator(int maxDegreeOfParallelism) - { - Configuration.Default.MemoryAllocator = this.defaultMemoryAllocator; - this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); - } - - [Benchmark] - [ArgumentsSource(nameof(ParallelismValues))] - public void ImageSharp_ArrayPoolMemoryAllocator(int maxDegreeOfParallelism) + public void ImageSharp(int maxDegreeOfParallelism) { - Configuration.Default.MemoryAllocator = this.arrayPoolMemoryAllocator; this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism); } diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 873845393..acec87ca5 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -192,20 +192,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } } - private enum AllocatorKind - { - Classic, - Unmanaged - } - private class CommandLineOptions { [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] public bool ImageSharp { get; set; } - [Option('a', "allocator", Required = false, Default = AllocatorKind.Unmanaged, HelpText = "Select allocator: Classic (ArrayPoolMemoryAllocator) or Unmanaged")] - public AllocatorKind Allocator { get; set; } - [Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")] public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; @@ -253,40 +244,29 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.Allocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; public MemoryAllocator CreateMemoryAllocator() { - switch (this.Allocator) + if (this.TrimTimeSeconds.HasValue) { - case AllocatorKind.Classic: -#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete - return ArrayPoolMemoryAllocator.CreateDefault(); -#pragma warning restore CS0618 - case AllocatorKind.Unmanaged: - if (this.TrimTimeSeconds.HasValue) + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes), + new UniformUnmanagedMemoryPool.TrimSettings { - return new UniformUnmanagedMemoryPoolMemoryAllocator( - 1024 * 1024, - (int)B(this.MaxContiguousPoolBufferMegaBytes), - B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes), - new UniformUnmanagedMemoryPool.TrimSettings - { - TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 - }); - } - else - { - return new UniformUnmanagedMemoryPoolMemoryAllocator( - 1024 * 1024, - (int)B(this.MaxContiguousPoolBufferMegaBytes), - B(this.MaxPoolSizeMegaBytes), - (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes)); - } - - default: - throw new ArgumentOutOfRangeException(); + TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000 + }); + } + else + { + return new UniformUnmanagedMemoryPoolMemoryAllocator( + 1024 * 1024, + (int)B(this.MaxContiguousPoolBufferMegaBytes), + B(this.MaxPoolSizeMegaBytes), + (int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes)); } } From 2d6596f26b16fe63d8ce04758492fe4f0e2e1e30 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Nov 2021 17:38:31 +0100 Subject: [PATCH 036/228] CopyPixelDataTo --- src/ImageSharp/ImageFrame{TPixel}.cs | 12 ++++ src/ImageSharp/Image{TPixel}.cs | 13 +++++ .../ImageSharp.Tests/Image/ImageFrameTests.cs | 55 +++++++++++++++++++ tests/ImageSharp.Tests/Image/ImageTests.cs | 54 ++++++++++++++++++ .../TestUtilities/TestUtils.cs | 26 +++++++++ 5 files changed, 160 insertions(+) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index e8268d381..4753e90e0 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -266,6 +266,18 @@ namespace SixLabors.ImageSharp } } + /// + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + /// /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index 710b4fedf..d01706b01 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; @@ -300,6 +301,18 @@ namespace SixLabors.ImageSharp } } + /// + /// Copy image pixels to . + /// + /// The to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(destination); + + /// + /// Copy image pixels to . + /// + /// The of to copy image pixels to. + public void CopyPixelDataTo(Span destination) => this.GetPixelMemoryGroup().CopyTo(MemoryMarshal.Cast(destination)); + /// /// Gets the representation of the pixels as a in the source image's pixel format /// stored in row major order, if the backing buffer is contiguous. diff --git a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs index 9e3aed2b0..4d01fd754 100644 --- a/tests/ImageSharp.Tests/Image/ImageFrameTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFrameTests.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; @@ -93,6 +95,59 @@ namespace SixLabors.ImageSharp.Tests ArgumentOutOfRangeException ex = Assert.Throws(() => frame[3, y] = default); Assert.Equal("y", ex.ParamName); } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) + { + this.LimitBufferCapacity(20); + } + + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); + } + + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) + { + image.Frames.RootFrame.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.Frames.RootFrame.CopyPixelDataTo(destination); + } + + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); + + Assert.ThrowsAny(() => + { + if (byteSpan) + { + image.Frames.RootFrame.CopyPixelDataTo(new byte[199]); + } + else + { + image.Frames.RootFrame.CopyPixelDataTo(new La16[99]); + } + }); + } } public class ProcessPixelRows : ProcessPixelRowsTestBase diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 4f68453a9..7b6787529 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Memory; @@ -167,6 +168,59 @@ namespace SixLabors.ImageSharp.Tests ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); Assert.Equal("y", ex.ParamName); } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public void CopyPixelDataTo_Success(bool disco, bool byteSpan) + { + if (disco) + { + this.LimitBufferCapacity(20); + } + + using var image = new Image(this.configuration, 10, 10); + if (disco) + { + Assert.True(image.GetPixelMemoryGroup().Count > 1); + } + + byte[] expected = TestUtils.FillImageWithRandomBytes(image); + byte[] actual = new byte[expected.Length]; + if (byteSpan) + { + image.CopyPixelDataTo(actual); + } + else + { + Span destination = MemoryMarshal.Cast(actual); + image.CopyPixelDataTo(destination); + } + + Assert.True(expected.AsSpan().SequenceEqual(actual)); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) + { + using var image = new Image(this.configuration, 10, 10); + + Assert.ThrowsAny(() => + { + if (byteSpan) + { + image.CopyPixelDataTo(new byte[199]); + } + else + { + image.CopyPixelDataTo(new La16[99]); + } + }); + } } public class ProcessPixelRows : ProcessPixelRowsTestBase diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs index e24fb7624..3c27b60fe 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs @@ -54,6 +54,32 @@ namespace SixLabors.ImageSharp.Tests public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; + public static byte[] GetRandomBytes(int length, int seed = 42) + { + var rnd = new Random(42); + byte[] bytes = new byte[length]; + rnd.NextBytes(bytes); + return bytes; + } + + internal static byte[] FillImageWithRandomBytes(Image image) + { + byte[] expected = TestUtils.GetRandomBytes(image.Width * image.Height * 2); + image.ProcessPixelRows(accessor => + { + int cnt = 0; + for (int y = 0; y < accessor.Height; y++) + { + Span row = accessor.GetRowSpan(y); + for (int x = 0; x < row.Length; x++) + { + row[x] = new La16(expected[cnt++], expected[cnt++]); + } + } + }); + return expected; + } + public static bool IsEquivalentTo(this Image a, Image b, bool compareAlpha = true) where TPixel : unmanaged, IPixel { From 8dda1d5d31a6f1ffa3cc844acfea176a37710e3f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Nov 2021 17:47:34 +0100 Subject: [PATCH 037/228] make GetNetCoreVersion work with preview --- tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs index 00d8a5c1c..3ccaf2ba3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs @@ -269,7 +269,14 @@ namespace SixLabors.ImageSharp.Tests int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) { - return Version.Parse(assemblyPath[netCoreAppIndex + 1]); + string runtimeFolderStr = assemblyPath[netCoreAppIndex + 1]; + int previewSuffix = runtimeFolderStr.IndexOf('-'); + if (previewSuffix > 0) + { + runtimeFolderStr = runtimeFolderStr.Substring(0, previewSuffix); + } + + return Version.Parse(runtimeFolderStr); } return null; From 71867675ba8c8196c18b5452ef2439df304b5eac Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 7 Nov 2021 18:37:11 +0100 Subject: [PATCH 038/228] Disable MemoryAllocator_Create_LimitPoolSize on OSX --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 9a55b3b82..033101f5d 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -164,6 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Fact] + [PlatformSpecific(~TestPlatforms.OSX)] // TODO: Investigate OSX failure public void MemoryAllocator_Create_LimitPoolSize() { RemoteExecutor.Invoke(RunTest).Dispose(); From 14eef74a341e784ec4d01857d60efffafaf80544 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 8 Nov 2021 21:36:06 +0100 Subject: [PATCH 039/228] Use [ConditionalFact] instead of [PlatformSpecific] --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 033101f5d..6a1190169 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -163,8 +163,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [Fact] - [PlatformSpecific(~TestPlatforms.OSX)] // TODO: Investigate OSX failure + public static bool IsNotOsx = !TestEnvironment.IsOSX; + + [ConditionalFact(nameof(IsNotOsx))] // TODO: Investigate OSX failure public void MemoryAllocator_Create_LimitPoolSize() { RemoteExecutor.Invoke(RunTest).Dispose(); From d09108aadffcdb48ac8400735cc5e6215d749205 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 8 Nov 2021 22:19:44 +0100 Subject: [PATCH 040/228] fix test bug in WrapSystemDrawingBitmap_FromBytes_WhenObserved --- tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs index dd1f5b701..ec9e3450f 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.WrapMemory.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Drawing; using System.Drawing.Imaging; +using System.IO; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; @@ -176,6 +177,11 @@ namespace SixLabors.ImageSharp.Tests Assert.False(memoryManager.IsDisposed); } + if (!Directory.Exists(TestEnvironment.ActualOutputDirectoryFullPath)) + { + Directory.CreateDirectory(TestEnvironment.ActualOutputDirectoryFullPath); + } + string fn = System.IO.Path.Combine( TestEnvironment.ActualOutputDirectoryFullPath, $"{nameof(this.WrapSystemDrawingBitmap_WhenObserved)}.bmp"); From 2b6d940d6fc17eea41fcc595c226dc5d658601d9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 9 Nov 2021 23:05:53 +0100 Subject: [PATCH 041/228] update LoadResizeSaveParallelMemoryStress --- .../LoadResizeSaveParallelMemoryStress.cs | 12 ++++++++++-- tests/ImageSharp.Tests.ProfilingSandbox/Program.cs | 1 - 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index acec87ca5..734bbf826 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -52,7 +52,12 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { Console.WriteLine("Running ImageSharp with options:"); Console.WriteLine(options.ToString()); - Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); + + if (!options.KeepDefaultAllocator) + { + MemoryAllocator.Default = Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); + } + timer = Stopwatch.StartNew(); try { @@ -197,6 +202,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox [Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")] public bool ImageSharp { get; set; } + [Option('d', "default-allocator", Required = false, Default = false, HelpText = "Keep default MemoryAllocator and ignore all settings")] + public bool KeepDefaultAllocator { get; set; } + [Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")] public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4; @@ -244,7 +252,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; public MemoryAllocator CreateMemoryAllocator() { diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index 2e0d8d97c..bbdb73ffb 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -36,7 +36,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox { try { - Console.WriteLine("WUT: " + GetNetCoreVersion()); LoadResizeSaveParallelMemoryStress.Run(args); } catch (Exception ex) From 7818ae000460b06034c4fdf5b8aab971ae0eead4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 13 Nov 2021 15:51:08 +0100 Subject: [PATCH 042/228] temporarily remove try-catch in ImageProcessor.Apply() --- .../Processors/ImageProcessor{TPixel}.cs | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs index b0896636e..e290e7089 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -45,27 +45,15 @@ namespace SixLabors.ImageSharp.Processing.Processors /// void IImageProcessor.Execute() { - try - { - this.BeforeImageApply(); + // TODO: Try-catch logic temporarily removed, put it back. + this.BeforeImageApply(); - foreach (ImageFrame sourceFrame in this.Source.Frames) - { - this.Apply(sourceFrame); - } - - this.AfterImageApply(); - } -#if DEBUG - catch (Exception) + foreach (ImageFrame sourceFrame in this.Source.Frames) { - throw; -#else - catch (Exception ex) - { - throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); -#endif + this.Apply(sourceFrame); } + + this.AfterImageApply(); } /// From f7b580712542711d4259242e2530a9c7947fec84 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 13 Nov 2021 16:02:25 +0100 Subject: [PATCH 043/228] (temporarily?) add RunTestsInLoop.ps1 --- tests/ImageSharp.Tests/RunTestsInLoop.ps1 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/ImageSharp.Tests/RunTestsInLoop.ps1 diff --git a/tests/ImageSharp.Tests/RunTestsInLoop.ps1 b/tests/ImageSharp.Tests/RunTestsInLoop.ps1 new file mode 100644 index 000000000..ef4bd5ccb --- /dev/null +++ b/tests/ImageSharp.Tests/RunTestsInLoop.ps1 @@ -0,0 +1,21 @@ +# This script can be used to collect logs from sporadic bugs +Param( + [int]$TestRunCount=10, + [string]$TargetFramework="netcoreapp3.1" +) + +$runId = Get-Random -Minimum 0 -Maximum 9999 + +dotnet build -c Release -f $TargetFramework +for ($i = 0; $i -lt $TestRunCount; $i++) { + $logFile = ".\_testlog-" + $runId.ToString("d4") + "-run-" + $i.ToString("d3") + ".log" + Write-Host "Test run $i ..." + & dotnet test --no-build -c Release -f $TargetFramework 3>&1 2>&1 > $logFile + if ($LastExitCode -eq 0) { + Write-Host "Success!" + Remove-Item $logFile + } + else { + Write-Host "Failed: $logFile" + } +} From bfcb1be7d1707907e011b526b673da0514068c2f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 13 Nov 2021 19:54:19 +0100 Subject: [PATCH 044/228] Logging made optional in TestMemoryAllocator --- .../MemoryGroupTests.Allocate.cs | 1 + .../Processors/Transforms/ResizeTests.cs | 1 + .../TestUtilities/TestMemoryAllocator.cs | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 07b99584d..0549309c1 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -194,6 +194,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public void MemoryAllocatorIsUtilizedCorrectly(AllocationOptions allocationOptions) { this.MemoryAllocator.BufferCapacityInBytes = 200; + this.MemoryAllocator.EnableNonThreadSafeLogging(); HashSet bufferHashes; diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs index 669fb939b..022bb224c 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs @@ -117,6 +117,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms int workingBufferSizeHintInBytes = workingBufferLimitInRows * destSize.Width * SizeOfVector4; var allocator = new TestMemoryAllocator(); + allocator.EnableNonThreadSafeLogging(); configuration.MemoryAllocator = allocator; configuration.WorkingBufferSizeHintInBytes = workingBufferSizeHintInBytes; diff --git a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs index c644b2fbf..5fb6d873a 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestMemoryAllocator.cs @@ -12,8 +12,8 @@ namespace SixLabors.ImageSharp.Tests.Memory { internal class TestMemoryAllocator : MemoryAllocator { - private readonly List allocationLog = new List(); - private readonly List returnLog = new List(); + private List allocationLog; + private List returnLog; public TestMemoryAllocator(byte dirtyValue = 42) { @@ -27,12 +27,18 @@ namespace SixLabors.ImageSharp.Tests.Memory public int BufferCapacityInBytes { get; set; } = int.MaxValue; - public IReadOnlyList AllocationLog => this.allocationLog; + public IReadOnlyList AllocationLog => this.allocationLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); - public IReadOnlyList ReturnLog => this.returnLog; + public IReadOnlyList ReturnLog => this.returnLog ?? throw new InvalidOperationException("Call TestMemoryAllocator.EnableLogging() first!"); protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes; + public void EnableNonThreadSafeLogging() + { + this.allocationLog = new List(); + this.returnLog = new List(); + } + public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { T[] array = this.AllocateArray(length, options); @@ -43,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Memory where T : struct { var array = new T[length + 42]; - this.allocationLog.Add(AllocationRequest.Create(options, length, array)); + this.allocationLog?.Add(AllocationRequest.Create(options, length, array)); if (options == AllocationOptions.None) { @@ -57,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Memory private void Return(BasicArrayBuffer buffer) where T : struct { - this.returnLog.Add(new ReturnRequest(buffer.Array.GetHashCode())); + this.returnLog?.Add(new ReturnRequest(buffer.Array.GetHashCode())); } public struct AllocationRequest From 34a39e514b01254af24e6ee9c8b9f8bd8c4de74b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 13 Nov 2021 19:54:51 +0100 Subject: [PATCH 045/228] Relax criteria for Shuffle3 --- src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs | 4 ++++ src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 7687a5b95..929b78692 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -28,6 +28,10 @@ namespace SixLabors.ImageSharp /// /// The source span of bytes. /// The destination span of bytes. + /// + /// Implementation can assume that source.Length is less or equal than dest.Length. + /// Loops should iterate using source.Length. + /// void RunFallbackShuffle(ReadOnlySpan source, Span dest); } diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs index 07744566a..abf9e9fed 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.Shuffle.cs @@ -77,6 +77,7 @@ namespace SixLabors.ImageSharp TShuffle shuffle) where TShuffle : struct, IShuffle3 { + // Source length should be smaller than dest length, and divisible by 3. VerifyShuffle3SpanInput(source, dest); #if SUPPORTS_RUNTIME_INTRINSICS @@ -182,9 +183,9 @@ namespace SixLabors.ImageSharp where T : struct { DebugGuard.IsTrue( - source.Length == dest.Length, + source.Length <= dest.Length, nameof(source), - "Input spans must be of same length!"); + "Source should fit into dest!"); DebugGuard.IsTrue( source.Length % 3 == 0, From 5c9be36ab94b3b462389b66fb70b8f0aa8dc3a71 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 13 Nov 2021 19:55:28 +0100 Subject: [PATCH 046/228] attempt to fix MemoryAllocator_Create_LimitPoolSize --- .../UniformUnmanagedPoolMemoryAllocatorTests.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 6a1190169..aec9aee36 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -163,9 +163,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - public static bool IsNotOsx = !TestEnvironment.IsOSX; - - [ConditionalFact(nameof(IsNotOsx))] // TODO: Investigate OSX failure + [Fact] public void MemoryAllocator_Create_LimitPoolSize() { RemoteExecutor.Invoke(RunTest).Dispose(); @@ -181,17 +179,23 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators MemoryGroup g1 = allocator.AllocateGroup(B(8), 1024); ref byte r0 = ref MemoryMarshal.GetReference(g0[0].Span); ref byte r1 = ref MemoryMarshal.GetReference(g1[0].Span); - g0.Dispose(); g1.Dispose(); - MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); - MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); + // Do some unmanaged allocations to make sure new non-pooled unmanaged allocations will grab different memory: + IntPtr dummy1 = Marshal.AllocHGlobal((IntPtr)B(8)); + IntPtr dummy2 = Marshal.AllocHGlobal((IntPtr)B(8)); + + using MemoryGroup g2 = allocator.AllocateGroup(B(8), 1024); + using MemoryGroup g3 = allocator.AllocateGroup(B(8), 1024); ref byte r2 = ref MemoryMarshal.GetReference(g2[0].Span); ref byte r3 = ref MemoryMarshal.GetReference(g3[0].Span); Assert.True(Unsafe.AreSame(ref r0, ref r2)); Assert.False(Unsafe.AreSame(ref r1, ref r3)); + + Marshal.FreeHGlobal(dummy1); + Marshal.FreeHGlobal(dummy2); } static long B(int value) => value << 20; From 065cb1ca459362fe1aaa6bb8e1afad4491c8515f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 17 Nov 2021 20:34:26 +0100 Subject: [PATCH 047/228] Fix vanilla build on VS2019 --- src/ImageSharp/ImageSharp.csproj | 1 + tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj | 1 + .../ImageSharp.Tests.ProfilingSandbox.csproj | 1 + tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 1 + 4 files changed, 4 insertions(+) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 6ad20713d..800b69326 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -39,6 +39,7 @@ netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + 9.0 diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 8f0b4a86f..4d7af89a5 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -28,6 +28,7 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 + 9.0 diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 1a470fa31..a141a58b0 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -30,6 +30,7 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 + 9.0 diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 471287006..c560b1b78 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -23,6 +23,7 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 + 9.0 From 251e802b7e36d4723f78c4820e945ab0f3c381e2 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 18 Nov 2021 21:13:59 +0100 Subject: [PATCH 048/228] Revert of: Fix vanilla build on VS2019 --- src/ImageSharp/ImageSharp.csproj | 1 + tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj | 1 + .../ImageSharp.Tests.ProfilingSandbox.csproj | 1 + tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 1 + 4 files changed, 4 insertions(+) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 6ad20713d..800b69326 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -39,6 +39,7 @@ netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + 9.0 diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 8f0b4a86f..4d7af89a5 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -28,6 +28,7 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 + 9.0 diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index 1a470fa31..a141a58b0 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -30,6 +30,7 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 + 9.0 diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 471287006..c560b1b78 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -23,6 +23,7 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 + 9.0 From ab0480f1bd7117971051f1aa5f53d09f59a01ac2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 18:57:55 +0100 Subject: [PATCH 049/228] memory clearing should not be UniformUnmanagedMemoryPool concern --- .../Internals/UniformUnmanagedMemoryPool.cs | 7 +----- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 15 +++++++++--- .../MemoryGroup{T}.Owned.cs | 23 +++++++++++++++---- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index cfb0b3eb5..3d5c0da2c 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Memory.Internals public int Capacity { get; } - public UnmanagedMemoryHandle Rent(AllocationOptions allocationOptions = AllocationOptions.None) + public UnmanagedMemoryHandle Rent() { UnmanagedMemoryHandle[] buffersLocal = this.buffers; @@ -81,11 +81,6 @@ namespace SixLabors.ImageSharp.Memory.Internals buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); } - if (allocationOptions.Has(AllocationOptions.Clean)) - { - this.GetSpan(buffer).Clear(); - } - return buffer; } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 3aa2bbf83..5fc563d09 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -98,10 +98,14 @@ namespace SixLabors.ImageSharp.Memory if (lengthInBytes <= this.poolBufferSizeInBytes) { - UnmanagedMemoryHandle array = this.pool.Rent(options); + UnmanagedMemoryHandle array = this.pool.Rent(); if (array != null) { - return new UniformUnmanagedMemoryPool.FinalizableBuffer(this.pool, array, length); + var buffer = new UniformUnmanagedMemoryPool.FinalizableBuffer(this.pool, array, length); + if (options.Has(AllocationOptions.Clean)) + { + buffer.Clear(); + } } } @@ -124,10 +128,15 @@ namespace SixLabors.ImageSharp.Memory if (totalLengthInBytes <= this.poolBufferSizeInBytes) { // Optimized path renting single array from the pool - UnmanagedMemoryHandle array = this.pool.Rent(options); + UnmanagedMemoryHandle array = this.pool.Rent(); if (array != null) { var buffer = new UniformUnmanagedMemoryPool.FinalizableBuffer(this.pool, array, (int)totalLength); + if (options.Has(AllocationOptions.Clean)) + { + buffer.Clear(); + } + return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index b5771f0c7..44ff438f3 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -30,8 +30,8 @@ namespace SixLabors.ImageSharp.Memory this.View = new MemoryGroupView(this); } - public Owned(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] pooledArrays, int bufferLength, long totalLength, int sizeOfLastBuffer) - : this(CreateBuffers(pool, pooledArrays, bufferLength, sizeOfLastBuffer), bufferLength, totalLength, true) + public Owned(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] pooledArrays, int bufferLength, long totalLength, int sizeOfLastBuffer, AllocationOptions options) + : this(CreateBuffers(pool, pooledArrays, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) { this.pooledHandles = pooledArrays; this.unmanagedMemoryPool = pool; @@ -66,16 +66,29 @@ namespace SixLabors.ImageSharp.Memory UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] pooledBuffers, int bufferLength, - int sizeOfLastBuffer) + int sizeOfLastBuffer, + AllocationOptions options) { var result = new IMemoryOwner[pooledBuffers.Length]; for (int i = 0; i < pooledBuffers.Length - 1; i++) { pooledBuffers[i].AssignedToNewOwner(); - result[i] = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[i], bufferLength); + var currentBuffer = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[i], bufferLength); + if (options.Has(AllocationOptions.Clean)) + { + currentBuffer.Clear(); + } + + result[i] = currentBuffer; + } + + var lastBuffer = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer); + if (options.Has(AllocationOptions.Clean)) + { + lastBuffer.Clear(); } - result[result.Length - 1] = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer); + result[result.Length - 1] = lastBuffer; return result; } From ad982c499aaa2211cd7e4e5c6cfe63b5dca8c18c Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 18:59:12 +0100 Subject: [PATCH 050/228] emulate leaks in LoadResizeSaveParallelMemoryStress --- .../LoadResizeSaveStressRunner.cs | 15 ++++++++++++ .../LoadResizeSaveParallelMemoryStress.cs | 24 +++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index 52c0a51b9..999a44ff3 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using ImageMagick; using PhotoSauce.MagicScaler; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests; using SkiaSharp; @@ -53,6 +54,9 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public int ThumbnailSize { get; set; } = 150; + // Inject leaking memory allocation requests to ImageSharp processing code to stress-test finalizer behavior. + public bool EmulateLeakedAllocations { get; set; } + private static readonly string[] ProgressiveFiles = { "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", @@ -180,6 +184,12 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave using var image = ImageSharpImage.Load(input); this.IncreaseTotalMegapixels(image.Width, image.Height); + if (this.EmulateLeakedAllocations) + { + _ = Configuration.Default.MemoryAllocator.Allocate(image.Width * image.Height); + _ = Configuration.Default.MemoryAllocator.Allocate(1 << 21); + } + image.Mutate(i => i.Resize(new ResizeOptions { Size = new ImageSharpSize(this.ThumbnailSize, this.ThumbnailSize), @@ -191,6 +201,11 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // Save the results image.Save(output, this.imageSharpJpegEncoder); + + if (this.EmulateLeakedAllocations) + { + _ = Configuration.Default.MemoryAllocator.Allocate2D(image.Width, image.Height); + } } public void MagickResize(string input) diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index 734bbf826..e8b3a744c 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -58,6 +58,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox MemoryAllocator.Default = Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); } + lrs.Benchmarks.EmulateLeakedAllocations = options.LeakAllocations; + timer = Stopwatch.StartNew(); try { @@ -235,6 +237,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox [Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")] public int? TrimTimeSeconds { get; set; } + [Option('l', "leak-allocations", Required = false, Default = false, HelpText = "Inject leaking memory allocation requests to stress-test finalizer behavior.")] + public bool LeakAllocations { get; set; } + public static CommandLineOptions Parse(string[] args) { CommandLineOptions result = null; @@ -252,7 +257,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})"; + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd}_l({this.LeakAllocations}))"; public MemoryAllocator CreateMemoryAllocator() { @@ -285,7 +290,22 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize); - private void ImageSharpBenchmarkParallel() => this.ForEachImage(this.Benchmarks.ImageSharpResize); + private void ImageSharpBenchmarkParallel() + { + int cnt = 0; + this.ForEachImage(f => + { + this.Benchmarks.ImageSharpResize(f); + if (cnt % 4 == 0 && this.Benchmarks.EmulateLeakedAllocations) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + } + + cnt++; + }); + } private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); From 579719724e9f858e72294bd83da5e597d2d5c104 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 18:59:59 +0100 Subject: [PATCH 051/228] MemoryOwnerFinalizer_ReturnsToPool --- ...niformUnmanagedPoolMemoryAllocatorTests.cs | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index aec9aee36..a08cfb741 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -322,5 +322,55 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators g1.First().Span[0] = 42; } } + + [ConditionalTheory(nameof(IsWindows))] + [InlineData(300)] + [InlineData(600)] + [InlineData(1200)] + public void MemoryOwnerFinalizer_ReturnsToPool(int length) + { + RunTest(length.ToString()); + // RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + + static void RunTest(string lengthStr) + { + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator(512, 1024, 16 * 1024, 1024); + int lengthInner = int.Parse(lengthStr); + + AllocateSingleAndForget(allocator, lengthInner); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + AllocateSingleAndForget(allocator, lengthInner, true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + + using IMemoryOwner g = allocator.Allocate(lengthInner); + Assert.Equal(42, g.GetSpan()[0]); + } + } + + private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) + { + IMemoryOwner g = allocator.Allocate(length); + if (check) + { + Assert.Equal(42, g.GetSpan()[0]); + } + + g.GetSpan()[0] = 42; + + if (length < 512) + { + // For ArrayPool.Shared, first array will be returned to the TLS storage of the finalizer thread, + // repeat rental to make sure per-core buckets are also utilized. + IMemoryOwner g1 = allocator.Allocate(length); + g1.GetSpan()[0] = 42; + } + } } } From aaacc4185c5b4e7546c07d7d7d0758475df47b7a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 19:01:57 +0100 Subject: [PATCH 052/228] Add missing GC.SuppressFinalize(this) --- .../Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs | 1 + .../Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs index 56209d343..c3d736cee 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs @@ -65,6 +65,7 @@ namespace SixLabors.ImageSharp.Memory.Internals } base.Dispose(disposing); + GC.SuppressFinalize(this); } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 44ff438f3..6f92e4864 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -157,6 +157,8 @@ namespace SixLabors.ImageSharp.Memory { ((UniformUnmanagedMemoryPool.Buffer)memoryOwner).MarkDisposed(); } + + GC.SuppressFinalize(this); } else if (disposing) { From 27ede235a3766c2673e45928f822d421ab610f51 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 20 Nov 2021 14:35:02 +0100 Subject: [PATCH 053/228] fixes --- .../Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs | 2 ++ src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs | 2 +- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 5fc563d09..2e664b413 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -106,6 +106,8 @@ namespace SixLabors.ImageSharp.Memory { buffer.Clear(); } + + return buffer; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index f517482d7..da7419b4b 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -200,7 +200,7 @@ namespace SixLabors.ImageSharp.Memory return null; } - return new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer); + return new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options); } public static MemoryGroup Wrap(params Memory[] source) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index a08cfb741..4b4a04df8 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -329,8 +329,8 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [InlineData(1200)] public void MemoryOwnerFinalizer_ReturnsToPool(int length) { - RunTest(length.ToString()); - // RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); + // RunTest(length.ToString()); + RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); static void RunTest(string lengthStr) { From 432c03a65db5195d0bc2fc6c8048a37c4b1b75a5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 20 Nov 2021 15:01:41 +0100 Subject: [PATCH 054/228] remove outdated AllocationOptions.Contiguous --- .../Memory/Allocators/AllocationOptions.cs | 8 +---- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 10 +++--- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 18 +++++----- .../Transforms/Resize/ResizeKernelMap.cs | 2 +- ...niformUnmanagedPoolMemoryAllocatorTests.cs | 19 ---------- .../MemoryGroupTests.Allocate.cs | 36 ++++++++----------- 6 files changed, 32 insertions(+), 61 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs index 72c785532..ae856c978 100644 --- a/src/ImageSharp/Memory/Allocators/AllocationOptions.cs +++ b/src/ImageSharp/Memory/Allocators/AllocationOptions.cs @@ -19,12 +19,6 @@ namespace SixLabors.ImageSharp.Memory /// /// Indicates that the allocated buffer should be cleaned following allocation. /// - Clean = 1, - - /// - /// Affects only group allocations. - /// Indicates that the requested or should be made of contiguous blocks up to . - /// - Contiguous = 2 + Clean = 1 } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index 2e664b413..ecc30c97c 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -144,10 +144,12 @@ namespace SixLabors.ImageSharp.Memory } // Attempt to rent the whole group from the pool, allocate a group of unmanaged buffers if the attempt fails: - MemoryGroup poolGroup = options.Has(AllocationOptions.Contiguous) ? - null : - MemoryGroup.Allocate(this.pool, totalLength, bufferAlignment, options); - return poolGroup ?? MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); + if (MemoryGroup.TryAllocate(this.pool, totalLength, bufferAlignment, options, out MemoryGroup poolGroup)) + { + return poolGroup; + } + + return MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); } public override void ReleaseRetainedResources() diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index da7419b4b..5ff17f0c2 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -85,9 +85,7 @@ namespace SixLabors.ImageSharp.Memory int bufferAlignmentInElements, AllocationOptions options = AllocationOptions.None) { - int bufferCapacityInBytes = options.Has(AllocationOptions.Contiguous) ? - int.MaxValue : - allocator.GetBufferCapacityInBytes(); + int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes(); Guard.NotNull(allocator, nameof(allocator)); Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); Guard.MustBeGreaterThanOrEqualTo(bufferAlignmentInElements, 0, nameof(bufferAlignmentInElements)); @@ -151,11 +149,12 @@ namespace SixLabors.ImageSharp.Memory return new Owned(buffers, length, length, true); } - public static MemoryGroup Allocate( + public static bool TryAllocate( UniformUnmanagedMemoryPool pool, long totalLengthInElements, int bufferAlignmentInElements, - AllocationOptions options = AllocationOptions.None) + AllocationOptions options, + out MemoryGroup memoryGroup) { Guard.NotNull(pool, nameof(pool)); Guard.MustBeGreaterThanOrEqualTo(totalLengthInElements, 0, nameof(totalLengthInElements)); @@ -165,7 +164,8 @@ namespace SixLabors.ImageSharp.Memory if (bufferAlignmentInElements > blockCapacityInElements) { - return null; + memoryGroup = null; + return false; } if (totalLengthInElements == 0) @@ -197,10 +197,12 @@ namespace SixLabors.ImageSharp.Memory if (arrays == null) { // Pool is full - return null; + memoryGroup = null; + return false; } - return new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options); + memoryGroup = new Owned(pool, arrays, bufferLength, totalLengthInElements, sizeOfLastBuffer, options); + return true; } public static MemoryGroup Wrap(params Memory[] source) diff --git a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs index 4486c6775..c9dda5f6b 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms this.sourceLength = sourceLength; this.DestinationLength = destinationLength; this.MaxDiameter = (radius * 2) + 1; - this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, AllocationOptions.Clean | AllocationOptions.Contiguous); + this.data = memoryAllocator.Allocate2D(this.MaxDiameter, bufferHeight, preferContiguosImageBuffers: true, AllocationOptions.Clean); this.pinHandle = this.data.DangerousGetSingleMemory().Pin(); this.kernels = new ResizeKernel[destinationLength]; this.tempValues = new double[this.MaxDiameter]; diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 4b4a04df8..e67831e19 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -111,25 +111,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [Theory] - [InlineData(512)] - [InlineData(2048)] - [InlineData(8192)] - [InlineData(65536)] - public void AllocateGroup_OptionsContiguous_AllocatesContiguousBuffer(int lengthInBytes) - { - var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( - 128, - 1024, - 2048, - 4096); - int length = lengthInBytes / Unsafe.SizeOf(); - using MemoryGroup g = allocator.AllocateGroup(length, 32, AllocationOptions.Contiguous); - Assert.Equal(length, g.BufferLength); - Assert.Equal(length, g.TotalLength); - Assert.Equal(1, g.Count); - } - [Fact] public unsafe void Allocate_MemoryIsPinnableMultipleTimes() { diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 0549309c1..e6d07a191 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -93,10 +93,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers var pool = new UniformUnmanagedMemoryPool(bufferCapacity, expectedNumberOfBuffers); // Act: - using var g = MemoryGroup.Allocate(pool, totalLength, bufferAlignment); + Assert.True(MemoryGroup.TryAllocate(pool, totalLength, bufferAlignment, AllocationOptions.None, out MemoryGroup g)); // Assert: ValidateAllocateMemoryGroup(expectedNumberOfBuffers, expectedBufferSize, expectedSizeOfLastBuffer, g); + g.Dispose(); } private static unsafe Span GetSpan(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) => @@ -116,38 +117,42 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers pool.Return(buffers); - using var g = MemoryGroup.Allocate(pool, 50, 10, options); + Assert.True(MemoryGroup.TryAllocate(pool, 50, 10, options, out MemoryGroup g)); Span expected = stackalloc byte[10]; expected.Fill((byte)(options == AllocationOptions.Clean ? 0 : 42)); foreach (Memory memory in g) { Assert.True(expected.SequenceEqual(memory.Span)); } + + g.Dispose(); } [Theory] - [InlineData(64, 4, 60, 240, false)] - [InlineData(64, 4, 60, 244, true)] + [InlineData(64, 4, 60, 240, true)] + [InlineData(64, 4, 60, 244, false)] public void Allocate_FromPool_AroundLimit( int bufferCapacityBytes, int poolCapacity, int alignmentBytes, int requestBytes, - bool shouldReturnNull) + bool shouldSucceed) { var pool = new UniformUnmanagedMemoryPool(bufferCapacityBytes, poolCapacity); int alignmentElements = alignmentBytes / Unsafe.SizeOf(); int requestElements = requestBytes / Unsafe.SizeOf(); - using var g = MemoryGroup.Allocate(pool, requestElements, alignmentElements); - if (shouldReturnNull) + Assert.Equal(shouldSucceed, MemoryGroup.TryAllocate(pool, requestElements, alignmentElements, AllocationOptions.None, out MemoryGroup g)); + if (shouldSucceed) { - Assert.Null(g); + Assert.NotNull(g); } else { - Assert.NotNull(g); + Assert.Null(g); } + + g?.Dispose(); } internal static void ValidateAllocateMemoryGroup( @@ -218,19 +223,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.Equal(expectedBlockCount, this.MemoryAllocator.ReturnLog.Count); Assert.True(bufferHashes.SetEquals(this.MemoryAllocator.ReturnLog.Select(l => l.HashCodeOfBuffer))); } - - [Theory] - [InlineData(128)] - [InlineData(1024)] - public void Allocate_OptionsContiguous_AllocatesContiguousBuffer(int lengthInBytes) - { - this.MemoryAllocator.BufferCapacityInBytes = 256; - int length = lengthInBytes / Unsafe.SizeOf(); - using var g = MemoryGroup.Allocate(this.MemoryAllocator, length, 32, AllocationOptions.Contiguous); - Assert.Equal(length, g.BufferLength); - Assert.Equal(length, g.TotalLength); - Assert.Equal(1, g.Count); - } } } From 77e77008577d34f7b8534c130806ac2c97f287b0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 24 Nov 2021 21:07:30 +0100 Subject: [PATCH 055/228] Reimplement buffer ownership management --- src/ImageSharp/Common/Helpers/DebugGuard.cs | 14 ++ .../Allocators/Internals/IRefCounted.cs | 21 ++ .../Internals/RefCountedLifetimeGuard.cs | 56 +++++ .../Internals/SharedArrayPoolBuffer{T}.cs | 55 ++++- .../UniformUnmanagedMemoryPool.Buffer{T}.cs | 72 ------- ...iformUnmanagedMemoryPool.LifetimeGuards.cs | 50 +++++ .../Internals/UniformUnmanagedMemoryPool.cs | 201 +++++++++--------- .../Internals/UnmanagedBufferLifetimeGuard.cs | 27 +++ .../Internals/UnmanagedBuffer{T}.cs | 60 +++--- .../Internals/UnmanagedMemoryHandle.cs | 106 +++++---- .../Memory/Allocators/MemoryAllocator.cs | 25 +-- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 30 +-- .../Allocators/UnmanagedMemoryAllocator.cs | 2 +- .../MemoryGroup{T}.Owned.cs | 154 ++++++++------ .../DiscontiguousBuffers/MemoryGroup{T}.cs | 2 +- tests/ImageSharp.Tests/ConfigurationTests.cs | 11 +- .../ImageSharp.Tests/Image/ImageCloneTests.cs | 1 - .../Image/ProcessPixelRowsTestBase.cs | 26 +-- .../RefCountingLifetimeGuardTests.cs | 116 ++++++++++ .../Allocators/SharedArrayPoolBufferTests.cs | 60 ++++++ .../UniformUnmanagedMemoryPoolTests.Trim.cs | 59 ++++- .../UniformUnmanagedMemoryPoolTests.cs | 154 +++++++++----- ...niformUnmanagedPoolMemoryAllocatorTests.cs | 5 +- .../Memory/Allocators/UnmanagedBufferTests.cs | 93 ++++++++ .../Allocators/UnmanagedMemoryHandleTests.cs | 142 +++---------- .../MemoryGroupTests.Allocate.cs | 8 +- .../BasicTestPatternProvider.cs | 2 - 27 files changed, 988 insertions(+), 564 deletions(-) create mode 100644 src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs delete mode 100644 src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs create mode 100644 src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs create mode 100644 tests/ImageSharp.Tests/Memory/Allocators/RefCountingLifetimeGuardTests.cs create mode 100644 tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs create mode 100644 tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index f56cb37a8..43622066c 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -26,6 +26,20 @@ namespace SixLabors } } + /// + /// Verifies whether a specific condition is met, throwing an exception if it's false. + /// + /// Whether the object is disposed. + /// The name of the object. + [Conditional("DEBUG")] + public static void NotDisposed(bool isDisposed, string objectName) + { + if (isDisposed) + { + throw new ObjectDisposedException(objectName); + } + } + /// /// Verifies, that the target span is of same size than the 'other' span. /// diff --git a/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs b/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs new file mode 100644 index 000000000..363b68048 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/IRefCounted.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Defines an common interface for ref-counted objects. + /// + internal interface IRefCounted + { + /// + /// Increments the reference counter. + /// + void AddRef(); + + /// + /// Decrements the reference counter. + /// + void ReleaseRef(); + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs new file mode 100644 index 000000000..61682aa56 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/RefCountedLifetimeGuard.cs @@ -0,0 +1,56 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.InteropServices; +using System.Threading; + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Implements reference counting lifetime guard mechanism similar to the one provided by , + /// but without the restriction of the guarded object being a handle. + /// + internal abstract class RefCountedLifetimeGuard : IDisposable + { + private int refCount = 1; + private int disposed; + private int released; + + ~RefCountedLifetimeGuard() + { + Interlocked.Exchange(ref this.disposed, 1); + this.ReleaseRef(); + } + + public bool IsDisposed => this.disposed == 1; + + public void AddRef() => Interlocked.Increment(ref this.refCount); + + public void ReleaseRef() + { + Interlocked.Decrement(ref this.refCount); + if (this.refCount == 0) + { + int wasReleased = Interlocked.Exchange(ref this.released, 1); + + if (wasReleased == 0) + { + this.Release(); + } + } + } + + public void Dispose() + { + int wasDisposed = Interlocked.Exchange(ref this.disposed, 1); + if (wasDisposed == 0) + { + this.ReleaseRef(); + GC.SuppressFinalize(this); + } + } + + protected abstract void Release(); + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 6cb2a24fb..5027d94b4 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -3,30 +3,26 @@ using System; using System.Buffers; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace SixLabors.ImageSharp.Memory.Internals { - internal class SharedArrayPoolBuffer : ManagedBufferBase + internal class SharedArrayPoolBuffer : ManagedBufferBase, IRefCounted where T : struct { private readonly int lengthInBytes; private byte[] array; + private LifetimeGuard lifetimeGuard; public SharedArrayPoolBuffer(int lengthInElements) { this.lengthInBytes = lengthInElements * Unsafe.SizeOf(); this.array = ArrayPool.Shared.Rent(this.lengthInBytes); + this.lifetimeGuard = new LifetimeGuard(this.array); } - // The worst thing that could happen is that a VERY poorly written user code holding a Span on the stack, - // while loosing the reference to Image (or disposing it) may write to an unrelated ArrayPool array. - // This is an unlikely scenario we mitigate by a warning in DangerousGetRowSpan(i) APIs. -#pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span - ~SharedArrayPoolBuffer() => this.Dispose(false); -#pragma warning restore - protected override void Dispose(bool disposing) { if (this.array == null) @@ -34,12 +30,51 @@ namespace SixLabors.ImageSharp.Memory.Internals return; } - ArrayPool.Shared.Return(this.array); + this.lifetimeGuard.Dispose(); this.array = null; } - public override Span GetSpan() => MemoryMarshal.Cast(this.array.AsSpan(0, this.lengthInBytes)); + public override Span GetSpan() + { + this.CheckDisposed(); + return MemoryMarshal.Cast(this.array.AsSpan(0, this.lengthInBytes)); + } protected override object GetPinnableObject() => this.array; + + public void AddRef() + { + this.CheckDisposed(); + this.lifetimeGuard.AddRef(); + } + + public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); + + [Conditional("DEBUG")] + private void CheckDisposed() + { + if (this.array == null) + { + throw new ObjectDisposedException("SharedArrayPoolBuffer"); + } + } + + private class LifetimeGuard : RefCountedLifetimeGuard + { + private byte[] array; + + public LifetimeGuard(byte[] array) => this.array = array; + + protected override void Release() + { + // If this is called by a finalizer, we will end storing the first array of this bucket + // on the thread local storage of the finalizer thread. + // This is not ideal, but subsequent leaks will end up returning arrays to per-cpu buckets, + // meaning likely a different bucket than it was rented from, + // but this is PROBABLY better than not returning the arrays at all. + ArrayPool.Shared.Return(this.array); + this.array = null; + } + } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs deleted file mode 100644 index c3d736cee..000000000 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.Buffer{T}.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace SixLabors.ImageSharp.Memory.Internals -{ - internal partial class UniformUnmanagedMemoryPool - { - public class Buffer : UnmanagedBuffer - where T : struct - { - private UniformUnmanagedMemoryPool pool; - - public Buffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) - : base(bufferHandle, length) => - this.pool = pool; - - /// - protected override void Dispose(bool disposing) - { - if (this.pool == null) - { - return; - } - - this.pool.Return(this.BufferHandle); - this.pool = null; - this.BufferHandle = null; - } - - internal void MarkDisposed() - { - this.pool = null; - this.BufferHandle = null; - } - } - - public sealed class FinalizableBuffer : Buffer - where T : struct - { - public FinalizableBuffer(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle bufferHandle, int length) - : base(pool, bufferHandle, length) - { - bufferHandle.AssignedToNewOwner(); - } - - // A VERY poorly written user code holding a Span on the stack, - // while loosing the reference to Image (or disposing it) may write to (now unrelated) pool buffer, - // or cause memory corruption if the underlying UmnanagedMemoryHandle has been released. - // This is an unlikely scenario we mitigate a warning in DangerousGetRowSpan(i) APIs. -#pragma warning disable CA2015 // Adding a finalizer to a type derived from MemoryManager may permit memory to be freed while it is still in use by a Span - ~FinalizableBuffer() => this.Dispose(false); -#pragma warning restore - - protected override void Dispose(bool disposing) - { - if (!disposing && this.BufferHandle != null) - { - // We need to prevent handle finalization here. - // See comments on UnmanagedMemoryHandle.Resurrect() - this.BufferHandle.Resurrect(); - } - - base.Dispose(disposing); - GC.SuppressFinalize(this); - } - } - } -} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs new file mode 100644 index 000000000..032537d38 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + internal partial class UniformUnmanagedMemoryPool + { + public UnmanagedBuffer CreateGuardedBuffer( + UnmanagedMemoryHandle handle, + int lengthInElements, + AllocationOptions options) + where T : struct + { + var buffer = new UnmanagedBuffer(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); + if (options.Has(AllocationOptions.Clean)) + { + buffer.Clear(); + } + + return buffer; + } + + public RefCountedLifetimeGuard CreateGroupLifetimeGuard(UnmanagedMemoryHandle[] handles) => new GroupLifetimeGuard(this, handles); + + private sealed class GroupLifetimeGuard : RefCountedLifetimeGuard + { + private readonly UniformUnmanagedMemoryPool pool; + private readonly UnmanagedMemoryHandle[] handles; + + public GroupLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] handles) + { + this.pool = pool; + this.handles = handles; + } + + protected override void Release() => this.pool.Return(this.handles); + } + + private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard + { + private readonly UniformUnmanagedMemoryPool pool; + + public ReturnToPoolBufferLifetimeGuard(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle handle) + : base(handle) => + this.pool = pool; + + protected override void Release() => this.pool.Return(this.Handle); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index 3d5c0da2c..4cccab4af 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -2,19 +2,24 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Threading; namespace SixLabors.ImageSharp.Memory.Internals { internal partial class UniformUnmanagedMemoryPool { + private static int minTrimPeriodMilliseconds = int.MaxValue; + private static readonly List> AllPools = new(); + private static Timer trimTimer; + private static readonly Stopwatch Stopwatch = Stopwatch.StartNew(); private readonly TrimSettings trimSettings; - private UnmanagedMemoryHandle[] buffers; + private readonly UnmanagedMemoryHandle[] buffers; private int index; - private Timer trimTimer; private long lastTrimTimestamp; public UniformUnmanagedMemoryPool(int bufferLength, int capacity) @@ -31,16 +36,7 @@ namespace SixLabors.ImageSharp.Memory.Internals if (trimSettings.Enabled) { - // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds, - // and also invoke it on Gen 2 GC. - // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. - var weakPoolRef = new WeakReference(this); - this.trimTimer = new Timer( - s => TimerCallback((WeakReference)s), - weakPoolRef, - this.trimSettings.TrimPeriodMilliseconds / 4, - this.trimSettings.TrimPeriodMilliseconds / 4); - + UpdateTimer(trimSettings, this); #if NETCORE31COMPATIBLE Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); #endif @@ -52,31 +48,33 @@ namespace SixLabors.ImageSharp.Memory.Internals public int Capacity { get; } + /// + /// Rent a single buffer or return if the pool is full. + /// public UnmanagedMemoryHandle Rent() { UnmanagedMemoryHandle[] buffersLocal = this.buffers; - // Avoid taking the lock if the pool is released or is over limit: - if (buffersLocal == null || this.index == buffersLocal.Length) + // Avoid taking the lock if the pool is is over it's limit: + if (this.index == buffersLocal.Length) { - return null; + return UnmanagedMemoryHandle.NullHandle; } UnmanagedMemoryHandle buffer; - lock (buffersLocal) { // Check again after taking the lock: - if (this.buffers == null || this.index == buffersLocal.Length) + if (this.index == buffersLocal.Length) { - return null; + return UnmanagedMemoryHandle.NullHandle; } buffer = buffersLocal[this.index]; - buffersLocal[this.index++] = null; + buffersLocal[this.index++] = default; } - if (buffer == null) + if (buffer.IsInvalid) { buffer = UnmanagedMemoryHandle.Allocate(this.BufferLength); } @@ -84,12 +82,15 @@ namespace SixLabors.ImageSharp.Memory.Internals return buffer; } - public UnmanagedMemoryHandle[] Rent(int bufferCount, AllocationOptions allocationOptions = AllocationOptions.None) + /// + /// Rent buffers or return 'null' if the pool is full. + /// + public UnmanagedMemoryHandle[] Rent(int bufferCount) { UnmanagedMemoryHandle[] buffersLocal = this.buffers; - // Avoid taking the lock if the pool is released or is over limit: - if (buffersLocal == null || this.index + bufferCount >= buffersLocal.Length + 1) + // Avoid taking the lock if the pool is is over it's limit: + if (this.index + bufferCount >= buffersLocal.Length + 1) { return null; } @@ -98,7 +99,7 @@ namespace SixLabors.ImageSharp.Memory.Internals lock (buffersLocal) { // Check again after taking the lock: - if (this.buffers == null || this.index + bufferCount >= buffersLocal.Length + 1) + if (this.index + bufferCount >= buffersLocal.Length + 1) { return null; } @@ -107,128 +108,138 @@ namespace SixLabors.ImageSharp.Memory.Internals for (int i = 0; i < bufferCount; i++) { result[i] = buffersLocal[this.index]; - buffersLocal[this.index++] = null; + buffersLocal[this.index++] = UnmanagedMemoryHandle.NullHandle; } } for (int i = 0; i < result.Length; i++) { - if (result[i] == null) + if (result[i].IsInvalid) { result[i] = UnmanagedMemoryHandle.Allocate(this.BufferLength); } - - if (allocationOptions.Has(AllocationOptions.Clean)) - { - this.GetSpan(result[i]).Clear(); - } } return result; } - public void Return(UnmanagedMemoryHandle buffer) + public void Return(UnmanagedMemoryHandle bufferHandle) { - UnmanagedMemoryHandle[] buffersLocal = this.buffers; - if (buffersLocal == null) - { - buffer.Dispose(); - return; - } - - lock (buffersLocal) + Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed."); + lock (this.buffers) { // Check again after taking the lock: - if (this.buffers == null) - { - buffer.Dispose(); - return; - } - if (this.index == 0) { ThrowReturnedMoreBuffersThanRented(); // DEBUG-only exception - buffer.Dispose(); + bufferHandle.Free(); return; } - this.buffers[--this.index] = buffer; + this.buffers[--this.index] = bufferHandle; } } - public void Return(Span buffers) + public void Return(Span bufferHandles) { - UnmanagedMemoryHandle[] buffersLocal = this.buffers; - if (buffersLocal == null) - { - DisposeAll(buffers); - return; - } - - lock (buffersLocal) + lock (this.buffers) { - // Check again after taking the lock: - if (this.buffers == null) - { - DisposeAll(buffers); - return; - } - - if (this.index - buffers.Length + 1 <= 0) + if (this.index - bufferHandles.Length + 1 <= 0) { ThrowReturnedMoreBuffersThanRented(); - DisposeAll(buffers); + DisposeAll(bufferHandles); return; } - for (int i = buffers.Length - 1; i >= 0; i--) + for (int i = bufferHandles.Length - 1; i >= 0; i--) { - buffersLocal[--this.index] = buffers[i]; + ref UnmanagedMemoryHandle h = ref bufferHandles[i]; + Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed."); + this.buffers[--this.index] = bufferHandles[i]; } } } public void Release() { - this.trimTimer?.Dispose(); - this.trimTimer = null; - UnmanagedMemoryHandle[] oldBuffers = Interlocked.Exchange(ref this.buffers, null); - DebugGuard.NotNull(oldBuffers, nameof(oldBuffers)); - DisposeAll(oldBuffers); + lock (this.buffers) + { + for (int i = this.index; i < this.buffers.Length; i++) + { + UnmanagedMemoryHandle buffer = this.buffers[i]; + if (buffer.IsInvalid) + { + break; + } + + buffer.Free(); + this.buffers[i] = UnmanagedMemoryHandle.NullHandle; + } + } } private static void DisposeAll(Span buffers) { foreach (UnmanagedMemoryHandle handle in buffers) { - handle?.Dispose(); + handle.Free(); } } - private unsafe Span GetSpan(UnmanagedMemoryHandle h) => - new Span((byte*)h.DangerousGetHandle(), this.BufferLength); - // This indicates a bug in the library, however Return() might be called from a finalizer, // therefore we should never throw here in production. [Conditional("DEBUG")] private static void ThrowReturnedMoreBuffersThanRented() => throw new InvalidMemoryOperationException("Returned more buffers then rented"); - private static void TimerCallback(WeakReference weakPoolRef) + private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool) { - if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool)) + lock (AllPools) { - pool.Trim(); + AllPools.Add(new WeakReference(pool)); + + // Invoke the timer callback more frequently, than trimSettings.TrimPeriodMilliseconds. + // We are checking in the callback if enough time passed since the last trimming. If not, we do nothing. + int period = settings.TrimPeriodMilliseconds / 4; + if (trimTimer == null) + { + trimTimer = new Timer(_ => TimerCallback(), null, period, period); + } + else if (settings.TrimPeriodMilliseconds < minTrimPeriodMilliseconds) + { + trimTimer.Change(period, period); + } + + minTrimPeriodMilliseconds = Math.Min(minTrimPeriodMilliseconds, settings.TrimPeriodMilliseconds); } } - private bool Trim() + private static void TimerCallback() { - UnmanagedMemoryHandle[] buffersLocal = this.buffers; - if (buffersLocal == null) + lock (AllPools) { - return false; + // Remove lost references from the list: + for (int i = AllPools.Count - 1; i >= 0; i--) + { + if (!AllPools[i].TryGetTarget(out _)) + { + AllPools.RemoveAt(i); + } + } + + foreach (WeakReference weakPoolRef in AllPools) + { + if (weakPoolRef.TryGetTarget(out UniformUnmanagedMemoryPool pool)) + { + pool.Trim(); + } + } } + } + + private bool Trim() + { + UnmanagedMemoryHandle[] buffersLocal = this.buffers; bool isHighPressure = this.IsHighMemoryPressure(); @@ -250,16 +261,11 @@ namespace SixLabors.ImageSharp.Memory.Internals { lock (buffersLocal) { - if (this.buffers == null) - { - return false; - } - // Trim all: - for (int i = this.index; i < buffersLocal.Length && buffersLocal[i] != null; i++) + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) { - buffersLocal[i].Dispose(); - buffersLocal[i] = null; + buffersLocal[i].Free(); + buffersLocal[i] = UnmanagedMemoryHandle.NullHandle; } } @@ -270,14 +276,9 @@ namespace SixLabors.ImageSharp.Memory.Internals { lock (buffersLocal) { - if (this.buffers == null) - { - return false; - } - // Count the buffers in the pool: int retainedCount = 0; - for (int i = this.index; i < buffersLocal.Length && buffersLocal[i] != null; i++) + for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) { retainedCount++; } @@ -288,8 +289,8 @@ namespace SixLabors.ImageSharp.Memory.Internals int trimStop = this.index + retainedCount - trimCount; for (int i = trimStart; i >= trimStop; i--) { - buffersLocal[i].Dispose(); - buffersLocal[i] = null; + buffersLocal[i].Free(); + buffersLocal[i] = UnmanagedMemoryHandle.NullHandle; } this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs new file mode 100644 index 000000000..d59f04c93 --- /dev/null +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory.Internals +{ + /// + /// Defines a strategy for managing unmanaged memory ownership. + /// + internal abstract class UnmanagedBufferLifetimeGuard : RefCountedLifetimeGuard + { + private UnmanagedMemoryHandle handle; + + protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; + + public UnmanagedMemoryHandle Handle => this.handle; + + public sealed class FreeHandle : UnmanagedBufferLifetimeGuard + { + public FreeHandle(UnmanagedMemoryHandle handle) + : base(handle) + { + } + + protected override void Release() => this.handle.Free(); + } + } +} diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index c343e44a8..5d0c6dd61 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; namespace SixLabors.ImageSharp.Memory.Internals { @@ -13,58 +14,67 @@ namespace SixLabors.ImageSharp.Memory.Internals /// access to unmanaged buffers allocated by . /// /// The element type. - internal unsafe class UnmanagedBuffer : MemoryManager + internal sealed unsafe class UnmanagedBuffer : MemoryManager, IRefCounted where T : struct { private readonly int lengthInElements; - /// - /// Initializes a new instance of the class. - /// - /// The number of elements to allocate. - public UnmanagedBuffer(int lengthInElements) - : this(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()), lengthInElements) - { - } + private readonly UnmanagedBufferLifetimeGuard lifetimeGuard; + + private int disposed; - protected UnmanagedBuffer(UnmanagedMemoryHandle bufferHandle, int lengthInElements) + public UnmanagedBuffer(int lengthInElements, UnmanagedBufferLifetimeGuard lifetimeGuard) { + DebugGuard.NotNull(lifetimeGuard, nameof(lifetimeGuard)); + this.lengthInElements = lengthInElements; - this.BufferHandle = bufferHandle; + this.lifetimeGuard = lifetimeGuard; } - public UnmanagedMemoryHandle BufferHandle { get; protected set; } - - private void* Pointer => (void*)this.BufferHandle.DangerousGetHandle(); + private void* Pointer => this.lifetimeGuard.Handle.Pointer; - public override Span GetSpan() => new(this.Pointer, this.lengthInElements); + public override Span GetSpan() + { + DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); + DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); + return new(this.Pointer, this.lengthInElements); + } /// public override MemoryHandle Pin(int elementIndex = 0) { + DebugGuard.NotDisposed(this.disposed == 1, this.GetType().Name); + DebugGuard.NotDisposed(this.lifetimeGuard.IsDisposed, this.lifetimeGuard.GetType().Name); + // Will be released in Unpin - bool unused = false; - this.BufferHandle.DangerousAddRef(ref unused); + this.lifetimeGuard.AddRef(); void* pbData = Unsafe.Add(this.Pointer, elementIndex); return new MemoryHandle(pbData, pinnable: this); } - /// - public override void Unpin() => this.BufferHandle.DangerousRelease(); - /// protected override void Dispose(bool disposing) { - if (this.BufferHandle.IsInvalid) + DebugGuard.IsTrue(disposing, nameof(disposing), "Unmanaged buffers should not have finalizer!"); + + if (Interlocked.Exchange(ref this.disposed, 1) == 1) { + // Already disposed return; } - if (disposing) - { - this.BufferHandle.Dispose(); - } + this.lifetimeGuard.Dispose(); } + + /// + public override void Unpin() => this.lifetimeGuard.ReleaseRef(); + + public void AddRef() => this.lifetimeGuard.AddRef(); + + public void ReleaseRef() => this.lifetimeGuard.ReleaseRef(); + + public static UnmanagedBuffer Allocate(int lengthInElements) => + new(lengthInElements, new UnmanagedBufferLifetimeGuard.FreeHandle(UnmanagedMemoryHandle.Allocate(lengthInElements * Unsafe.SizeOf()))); } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs index 12ea933bb..ce2fab60a 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -2,20 +2,20 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; -using Microsoft.Win32.SafeHandles; namespace SixLabors.ImageSharp.Memory.Internals { - internal sealed class UnmanagedMemoryHandle : SafeHandle + /// + /// Encapsulates the functionality around allocating and releasing unmanaged memory. NOT a . + /// + internal struct UnmanagedMemoryHandle : IEquatable { - // Number of allocation re-attempts when OutOfMemoryException is thrown. + // Number of allocation re-attempts when detecting OutOfMemoryException. private const int MaxAllocationAttempts = 1000; - private readonly int lengthInBytes; - private bool resurrected; - // Track allocations for testing purposes: private static int totalOutstandingHandles; @@ -24,10 +24,16 @@ namespace SixLabors.ImageSharp.Memory.Internals // A Monitor to wait/signal when we are low on memory. private static object lowMemoryMonitor; + public static readonly UnmanagedMemoryHandle NullHandle = default; + + private IntPtr handle; + private readonly int lengthInBytes; + private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes) - : base(handle, true) { + this.handle = handle; this.lengthInBytes = lengthInBytes; + if (lengthInBytes > 0) { GC.AddMemoryPressure(lengthInBytes); @@ -36,6 +42,14 @@ namespace SixLabors.ImageSharp.Memory.Internals Interlocked.Increment(ref totalOutstandingHandles); } + public IntPtr Handle => this.handle; + + public bool IsInvalid => this.Handle == IntPtr.Zero; + + public bool IsValid => this.Handle != IntPtr.Zero; + + public unsafe void* Pointer => (void*)this.Handle; + /// /// Gets the total outstanding handle allocations for testing purposes. /// @@ -46,36 +60,34 @@ namespace SixLabors.ImageSharp.Memory.Internals /// internal static long TotalOomRetries => totalOomRetries; - /// - public override bool IsInvalid => this.handle == IntPtr.Zero; + public static bool operator ==(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => a.Equals(b); + + public static bool operator !=(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => !a.Equals(b); - protected override bool ReleaseHandle() + [MethodImpl(InliningOptions.HotPath)] + public unsafe Span GetSpan() { if (this.IsInvalid) { - return false; + ThrowDisposed(); } - Marshal.FreeHGlobal(this.handle); - if (this.lengthInBytes > 0) - { - GC.RemoveMemoryPressure(this.lengthInBytes); - } + return new Span(this.Pointer, this.lengthInBytes); + } - if (lowMemoryMonitor != null) + [MethodImpl(InliningOptions.HotPath)] + public unsafe Span GetSpan(int lengthInBytes) + { + DebugGuard.MustBeLessThanOrEqualTo(lengthInBytes, this.lengthInBytes, nameof(lengthInBytes)); + if (this.IsInvalid) { - // We are low on memory. Signal all threads waiting in AllocateHandle(). - Monitor.Enter(lowMemoryMonitor); - Monitor.PulseAll(lowMemoryMonitor); - Monitor.Exit(lowMemoryMonitor); + ThrowDisposed(); } - this.handle = IntPtr.Zero; - Interlocked.Decrement(ref totalOutstandingHandles); - return true; + return new Span(this.Pointer, lengthInBytes); } - internal static UnmanagedMemoryHandle Allocate(int lengthInBytes) + public static UnmanagedMemoryHandle Allocate(int lengthInBytes) { IntPtr handle = AllocateHandle(lengthInBytes); return new UnmanagedMemoryHandle(handle, lengthInBytes); @@ -115,26 +127,38 @@ namespace SixLabors.ImageSharp.Memory.Internals return handle; } - /// - /// UnmanagedMemoryHandle's finalizer would release the underlying handle returning the memory to the OS. - /// We want to prevent this when a finalizable owner (buffer or MemoryGroup) is returning the handle to - /// in it's finalizer. - /// Since UnmanagedMemoryHandle is CriticalFinalizable, it is guaranteed that the owner's finalizer is called first. - /// - internal void Resurrect() + public void Free() { - GC.SuppressFinalize(this); - this.resurrected = true; - } + IntPtr h = Interlocked.Exchange(ref this.handle, IntPtr.Zero); - internal void AssignedToNewOwner() - { - if (this.resurrected) + if (h == IntPtr.Zero) + { + return; + } + + Marshal.FreeHGlobal(h); + Interlocked.Decrement(ref totalOutstandingHandles); + if (this.lengthInBytes > 0) + { + GC.RemoveMemoryPressure(this.lengthInBytes); + } + + if (Volatile.Read(ref lowMemoryMonitor) != null) { - // The handle has been resurrected - GC.ReRegisterForFinalize(this); - this.resurrected = false; + // We are low on memory. Signal all threads waiting in AllocateHandle(). + Monitor.Enter(lowMemoryMonitor); + Monitor.PulseAll(lowMemoryMonitor); + Monitor.Exit(lowMemoryMonitor); } } + + public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); + + public override bool Equals(object obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); + + public override int GetHashCode() => this.handle.GetHashCode(); + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(UnmanagedMemoryHandle)); } } diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index d9ebb7cb9..6649468da 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -11,26 +11,15 @@ namespace SixLabors.ImageSharp.Memory /// public abstract class MemoryAllocator { - private static MemoryAllocator defaultMemoryAllocator = Create(); - /// - /// Gets or sets the default global instance for the current process. + /// Gets the default platform-specific global instance that + /// serves as the default value for . + /// + /// This is a get-only property, + /// you should set 's + /// to change the default allocator used by and it's operations. /// - /// - /// Since is lazy-initialized, setting the value of - /// will only override 's - /// before the first read of the property. - /// After that, a manual assigment of is necessary. - /// - public static MemoryAllocator Default - { - get => defaultMemoryAllocator; - set - { - Guard.NotNull(value, nameof(Default)); - defaultMemoryAllocator = value; - } - } + public static MemoryAllocator Default { get; } = Create(); /// /// Gets the length of the largest contiguous buffer that can be handled by this allocator instance in bytes. diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index ecc30c97c..b6d1abddf 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -98,15 +98,10 @@ namespace SixLabors.ImageSharp.Memory if (lengthInBytes <= this.poolBufferSizeInBytes) { - UnmanagedMemoryHandle array = this.pool.Rent(); - if (array != null) + UnmanagedMemoryHandle mem = this.pool.Rent(); + if (mem.IsValid) { - var buffer = new UniformUnmanagedMemoryPool.FinalizableBuffer(this.pool, array, length); - if (options.Has(AllocationOptions.Clean)) - { - buffer.Clear(); - } - + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options); return buffer; } } @@ -130,15 +125,10 @@ namespace SixLabors.ImageSharp.Memory if (totalLengthInBytes <= this.poolBufferSizeInBytes) { // Optimized path renting single array from the pool - UnmanagedMemoryHandle array = this.pool.Rent(); - if (array != null) + UnmanagedMemoryHandle mem = this.pool.Rent(); + if (mem.IsValid) { - var buffer = new UniformUnmanagedMemoryPool.FinalizableBuffer(this.pool, array, (int)totalLength); - if (options.Has(AllocationOptions.Clean)) - { - buffer.Clear(); - } - + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLength, options); return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); } } @@ -152,13 +142,7 @@ namespace SixLabors.ImageSharp.Memory return MemoryGroup.Allocate(this.nonPoolAllocator, totalLength, bufferAlignment, options); } - public override void ReleaseRetainedResources() - { - UniformUnmanagedMemoryPool oldPool = Interlocked.Exchange( - ref this.pool, - new UniformUnmanagedMemoryPool(this.poolBufferSizeInBytes, this.poolCapacity, this.trimSettings)); - oldPool.Release(); - } + public override void ReleaseRetainedResources() => this.pool.Release(); private static long GetDefaultMaxPoolSizeBytes() { diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs index 731c8e014..9b0869c40 100644 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -20,7 +20,7 @@ namespace SixLabors.ImageSharp.Memory public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) { - var buffer = new UnmanagedBuffer(length); + var buffer = UnmanagedBuffer.Allocate(length); if (options.Has(AllocationOptions.Clean)) { buffer.GetSpan().Clear(); diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 6f92e4864..a59602efa 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -18,9 +18,7 @@ namespace SixLabors.ImageSharp.Memory public sealed class Owned : MemoryGroup, IEnumerable> { private IMemoryOwner[] memoryOwners; - private byte[][] pooledArrays; - private UniformUnmanagedMemoryPool unmanagedMemoryPool; - private UnmanagedMemoryHandle[] pooledHandles; + private RefCountedLifetimeGuard groupLifetimeGuard; public Owned(IMemoryOwner[] memoryOwners, int bufferLength, long totalLength, bool swappable) : base(bufferLength, totalLength) @@ -30,14 +28,15 @@ namespace SixLabors.ImageSharp.Memory this.View = new MemoryGroupView(this); } - public Owned(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] pooledArrays, int bufferLength, long totalLength, int sizeOfLastBuffer, AllocationOptions options) - : this(CreateBuffers(pool, pooledArrays, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) - { - this.pooledHandles = pooledArrays; - this.unmanagedMemoryPool = pool; - } - - ~Owned() => this.Dispose(false); + public Owned( + UniformUnmanagedMemoryPool pool, + UnmanagedMemoryHandle[] pooledHandles, + int bufferLength, + long totalLength, + int sizeOfLastBuffer, + AllocationOptions options) + : this(CreateBuffers(pooledHandles, bufferLength, sizeOfLastBuffer, options), bufferLength, totalLength, true) => + this.groupLifetimeGuard = pool.CreateGroupLifetimeGuard(pooledHandles); public bool Swappable { get; } @@ -63,7 +62,6 @@ namespace SixLabors.ImageSharp.Memory } private static IMemoryOwner[] CreateBuffers( - UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle[] pooledBuffers, int bufferLength, int sizeOfLastBuffer, @@ -72,42 +70,35 @@ namespace SixLabors.ImageSharp.Memory var result = new IMemoryOwner[pooledBuffers.Length]; for (int i = 0; i < pooledBuffers.Length - 1; i++) { - pooledBuffers[i].AssignedToNewOwner(); - var currentBuffer = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[i], bufferLength); - if (options.Has(AllocationOptions.Clean)) - { - currentBuffer.Clear(); - } - + var currentBuffer = ObservedBuffer.Create(pooledBuffers[i], bufferLength, options); result[i] = currentBuffer; } - var lastBuffer = new UniformUnmanagedMemoryPool.Buffer(pool, pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer); - if (options.Has(AllocationOptions.Clean)) - { - lastBuffer.Clear(); - } - + var lastBuffer = ObservedBuffer.Create(pooledBuffers[pooledBuffers.Length - 1], sizeOfLastBuffer, options); result[result.Length - 1] = lastBuffer; return result; } /// [MethodImpl(InliningOptions.ShortMethod)] - public override MemoryGroupEnumerator GetEnumerator() - { - return new MemoryGroupEnumerator(this); - } + public override MemoryGroupEnumerator GetEnumerator() => new(this); public override void IncreaseRefCounts() { this.EnsureNotDisposed(); - bool dummy = default; - foreach (IMemoryOwner memoryOwner in this.memoryOwners) + + if (this.groupLifetimeGuard != null) + { + this.groupLifetimeGuard.AddRef(); + } + else { - if (memoryOwner is UnmanagedBuffer unmanagedBuffer) + foreach (IMemoryOwner memoryOwner in this.memoryOwners) { - unmanagedBuffer.BufferHandle?.DangerousAddRef(ref dummy); + if (memoryOwner is IRefCounted unmanagedBuffer) + { + unmanagedBuffer.AddRef(); + } } } } @@ -115,11 +106,18 @@ namespace SixLabors.ImageSharp.Memory public override void DecreaseRefCounts() { this.EnsureNotDisposed(); - foreach (IMemoryOwner memoryOwner in this.memoryOwners) + if (this.groupLifetimeGuard != null) { - if (memoryOwner is UnmanagedBuffer unmanagedBuffer) + this.groupLifetimeGuard.ReleaseRef(); + } + else + { + foreach (IMemoryOwner memoryOwner in this.memoryOwners) { - unmanagedBuffer.BufferHandle?.DangerousRelease(); + if (memoryOwner is IRefCounted unmanagedBuffer) + { + unmanagedBuffer.ReleaseRef(); + } } } } @@ -133,34 +131,18 @@ namespace SixLabors.ImageSharp.Memory protected override void Dispose(bool disposing) { - if (this.IsDisposed) + if (this.IsDisposed || !disposing) { return; } this.View.Invalidate(); - if (this.unmanagedMemoryPool != null) + if (this.groupLifetimeGuard != null) { - this.unmanagedMemoryPool.Return(this.pooledHandles); - if (!disposing) - { - foreach (UnmanagedMemoryHandle handle in this.pooledHandles) - { - // We need to prevent handle finalization here. - // See comments on UnmanagedMemoryHandle.Resurrect() - handle.Resurrect(); - } - } - - foreach (IMemoryOwner memoryOwner in this.memoryOwners) - { - ((UniformUnmanagedMemoryPool.Buffer)memoryOwner).MarkDisposed(); - } - - GC.SuppressFinalize(this); + this.groupLifetimeGuard.Dispose(); } - else if (disposing) + else { foreach (IMemoryOwner memoryOwner in this.memoryOwners) { @@ -170,9 +152,7 @@ namespace SixLabors.ImageSharp.Memory this.memoryOwners = null; this.IsValid = false; - this.pooledArrays = null; - this.unmanagedMemoryPool = null; - this.pooledHandles = null; + this.groupLifetimeGuard = null; } [MethodImpl(InliningOptions.ShortMethod)] @@ -195,29 +175,67 @@ namespace SixLabors.ImageSharp.Memory IMemoryOwner[] tempOwners = a.memoryOwners; long tempTotalLength = a.TotalLength; int tempBufferLength = a.BufferLength; - byte[][] tempPooledArrays = a.pooledArrays; - UniformUnmanagedMemoryPool tempUnmangedPool = a.unmanagedMemoryPool; - UnmanagedMemoryHandle[] tempPooledHandles = a.pooledHandles; + RefCountedLifetimeGuard tempGroupOwner = a.groupLifetimeGuard; a.memoryOwners = b.memoryOwners; a.TotalLength = b.TotalLength; a.BufferLength = b.BufferLength; - a.pooledArrays = b.pooledArrays; - a.unmanagedMemoryPool = b.unmanagedMemoryPool; - a.pooledHandles = b.pooledHandles; + a.groupLifetimeGuard = b.groupLifetimeGuard; b.memoryOwners = tempOwners; b.TotalLength = tempTotalLength; b.BufferLength = tempBufferLength; - b.pooledArrays = tempPooledArrays; - b.unmanagedMemoryPool = tempUnmangedPool; - b.pooledHandles = tempPooledHandles; + b.groupLifetimeGuard = tempGroupOwner; a.View.Invalidate(); b.View.Invalidate(); a.View = new MemoryGroupView(a); b.View = new MemoryGroupView(b); } + + // No-ownership + private sealed class ObservedBuffer : MemoryManager + { + private readonly UnmanagedMemoryHandle handle; + private readonly int lengthInElements; + + private ObservedBuffer(UnmanagedMemoryHandle handle, int lengthInElements) + { + this.handle = handle; + this.lengthInElements = lengthInElements; + } + + public static ObservedBuffer Create( + UnmanagedMemoryHandle handle, + int lengthInElements, + AllocationOptions options) + { + var buffer = new ObservedBuffer(handle, lengthInElements); + if (options.Has(AllocationOptions.Clean)) + { + buffer.GetSpan().Clear(); + } + + return buffer; + } + + protected override void Dispose(bool disposing) + { + // No-op. + } + + public override unsafe Span GetSpan() => new(this.handle.Pointer, this.lengthInElements); + + public override unsafe MemoryHandle Pin(int elementIndex = 0) + { + void* pbData = Unsafe.Add(this.handle.Pointer, elementIndex); + return new MemoryHandle(pbData); + } + + public override void Unpin() + { + } + } } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 5ff17f0c2..0fcbd6f96 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Memory bufferCount++; } - UnmanagedMemoryHandle[] arrays = pool.Rent(bufferCount, options); + UnmanagedMemoryHandle[] arrays = pool.Rent(bufferCount); if (arrays == null) { diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 491f717cc..f77db33f0 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -156,17 +156,14 @@ namespace SixLabors.ImageSharp.Tests static void RunTest() { - MemoryAllocator allocator = new TestMemoryAllocator(); - MemoryAllocator.Default = allocator; - var c1 = new Configuration(); var c2 = new Configuration(new MockConfigurationModule()); var c3 = Configuration.CreateDefaultInstance(); - Assert.Same(allocator, Configuration.Default.MemoryAllocator); - Assert.Same(allocator, c1.MemoryAllocator); - Assert.Same(allocator, c2.MemoryAllocator); - Assert.Same(allocator, c3.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, Configuration.Default.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c1.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c2.MemoryAllocator); + Assert.Same(MemoryAllocator.Default, c3.MemoryAllocator); } } diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index 8cca00a73..f60264334 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -134,7 +134,6 @@ namespace SixLabors.ImageSharp.Tests } } }); - } } } diff --git a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs index 255e1a9a4..fa0752e77 100644 --- a/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs +++ b/tests/ImageSharp.Tests/Image/ProcessPixelRowsTestBase.cs @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Tests static void RunTest(string testTypeName, string throwExceptionStr) { bool throwExceptionInner = bool.Parse(throwExceptionStr); - var buffer = new UnmanagedBuffer(100); + var buffer = UnmanagedBuffer.Allocate(100); var allocator = new MockUnmanagedMemoryAllocator(buffer); Configuration.Default.MemoryAllocator = allocator; @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests { GetTest(testTypeName).ProcessPixelRowsImpl(image, _ => { - buffer.BufferHandle.Dispose(); + ((IDisposable)buffer).Dispose(); Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); if (throwExceptionInner) { @@ -218,8 +218,8 @@ namespace SixLabors.ImageSharp.Tests static void RunTest(string testTypeName, string throwExceptionStr) { bool throwExceptionInner = bool.Parse(throwExceptionStr); - var buffer1 = new UnmanagedBuffer(100); - var buffer2 = new UnmanagedBuffer(100); + var buffer1 = UnmanagedBuffer.Allocate(100); + var buffer2 = UnmanagedBuffer.Allocate(100); var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2); Configuration.Default.MemoryAllocator = allocator; @@ -231,8 +231,8 @@ namespace SixLabors.ImageSharp.Tests { GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, (_, _) => { - buffer1.BufferHandle.Dispose(); - buffer2.BufferHandle.Dispose(); + ((IDisposable)buffer1).Dispose(); + ((IDisposable)buffer2).Dispose(); Assert.Equal(2, UnmanagedMemoryHandle.TotalOutstandingHandles); if (throwExceptionInner) { @@ -258,9 +258,9 @@ namespace SixLabors.ImageSharp.Tests static void RunTest(string testTypeName, string throwExceptionStr) { bool throwExceptionInner = bool.Parse(throwExceptionStr); - var buffer1 = new UnmanagedBuffer(100); - var buffer2 = new UnmanagedBuffer(100); - var buffer3 = new UnmanagedBuffer(100); + var buffer1 = UnmanagedBuffer.Allocate(100); + var buffer2 = UnmanagedBuffer.Allocate(100); + var buffer3 = UnmanagedBuffer.Allocate(100); var allocator = new MockUnmanagedMemoryAllocator(buffer1, buffer2, buffer3); Configuration.Default.MemoryAllocator = allocator; @@ -273,9 +273,9 @@ namespace SixLabors.ImageSharp.Tests { GetTest(testTypeName).ProcessPixelRowsImpl(image1, image2, image3, (_, _, _) => { - buffer1.BufferHandle.Dispose(); - buffer2.BufferHandle.Dispose(); - buffer3.BufferHandle.Dispose(); + ((IDisposable)buffer1).Dispose(); + ((IDisposable)buffer2).Dispose(); + ((IDisposable)buffer3).Dispose(); Assert.Equal(3, UnmanagedMemoryHandle.TotalOutstandingHandles); if (throwExceptionInner) { @@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.Tests protected internal override int GetBufferCapacityInBytes() => int.MaxValue; public override IMemoryOwner Allocate(int length, AllocationOptions options = AllocationOptions.None) => - (IMemoryOwner)this.buffers.Pop(); + this.buffers.Pop() as IMemoryOwner; } } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/RefCountingLifetimeGuardTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/RefCountingLifetimeGuardTests.cs new file mode 100644 index 000000000..ab1aab74a --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/RefCountingLifetimeGuardTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class RefCountingLifetimeGuardTests + { + [Theory] + [InlineData(1)] + [InlineData(3)] + public void Dispose_ResultsInSingleRelease(int disposeCount) + { + var guard = new MockLifetimeGuard(); + Assert.Equal(0, guard.ReleaseInvocationCount); + + for (int i = 0; i < disposeCount; i++) + { + guard.Dispose(); + } + + Assert.Equal(1, guard.ReleaseInvocationCount); + } + + [Fact] + public void Finalize_ResultsInSingleRelease() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); + LeakGuard(false); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(1, MockLifetimeGuard.GlobalReleaseInvocationCount); + } + } + + [Theory] + [InlineData(1)] + [InlineData(3)] + public void AddRef_PreventsReleaseOnDispose(int addRefCount) + { + var guard = new MockLifetimeGuard(); + + for (int i = 0; i < addRefCount; i++) + { + guard.AddRef(); + } + + guard.Dispose(); + + for (int i = 0; i < addRefCount; i++) + { + Assert.Equal(0, guard.ReleaseInvocationCount); + guard.ReleaseRef(); + } + + Assert.Equal(1, guard.ReleaseInvocationCount); + } + + [Fact] + public void AddRef_PreventsReleaseOnFinalize() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + LeakGuard(true); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(0, MockLifetimeGuard.GlobalReleaseInvocationCount); + } + } + + [Fact] + public void AddRefReleaseRefMisuse_DoesntLeadToMultipleReleases() + { + var guard = new MockLifetimeGuard(); + guard.Dispose(); + guard.AddRef(); + guard.ReleaseRef(); + + Assert.Equal(1, guard.ReleaseInvocationCount); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void LeakGuard(bool addRef) + { + var guard = new MockLifetimeGuard(); + if (addRef) + { + guard.AddRef(); + } + } + + private class MockLifetimeGuard : RefCountedLifetimeGuard + { + public int ReleaseInvocationCount { get; private set; } + + public static int GlobalReleaseInvocationCount { get; private set; } + + protected override void Release() + { + this.ReleaseInvocationCount++; + GlobalReleaseInvocationCount++; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs new file mode 100644 index 000000000..fab520e19 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/SharedArrayPoolBufferTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Linq; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class SharedArrayPoolBufferTests + { + [Fact] + public void AllocatesArrayPoolArray() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + using (var buffer = new SharedArrayPoolBuffer(900)) + { + Assert.Equal(900, buffer.GetSpan().Length); + buffer.GetSpan().Fill(42); + } + + byte[] array = ArrayPool.Shared.Rent(900); + byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + + Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); + } + } + + [Fact] + public void OutstandingReferences_RetainArrays() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var buffer = new SharedArrayPoolBuffer(900); + Span span = buffer.GetSpan(); + + buffer.AddRef(); + ((IDisposable)buffer).Dispose(); + span.Fill(42); + byte[] array = ArrayPool.Shared.Rent(900); + Assert.NotEqual(42, array[0]); + ArrayPool.Shared.Return(array); + + buffer.ReleaseRef(); + array = ArrayPool.Shared.Rent(900); + byte[] expected = Enumerable.Repeat((byte)42, 900).ToArray(); + Assert.True(expected.AsSpan().SequenceEqual(array.AsSpan(0, 900))); + ArrayPool.Shared.Return(array); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 9726c9c58..35d2237c0 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.IO; +using System.Runtime.CompilerServices; +using System.Text; using System.Threading; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Memory.Internals; @@ -13,14 +15,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { public partial class UniformUnmanagedMemoryPoolTests { - [CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)] - public class NonParallelTests - { - } - [Collection(nameof(NonParallelTests))] public class Trim { + [CollectionDefinition(nameof(NonParallelTests), DisableParallelization = true)] + public class NonParallelTests + { + } + [Fact] public void TrimPeriodElapsed_TrimsHalfOfUnusedArrays() { @@ -45,18 +47,56 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } + [Fact] + public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var trimSettings1 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 6_000 }; + var pool1 = new UniformUnmanagedMemoryPool(128, 256, trimSettings1); + Thread.Sleep(8_000); // Let some callbacks fire already + var trimSettings2 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 3_000 }; + var pool2 = new UniformUnmanagedMemoryPool(128, 256, trimSettings2); + + pool1.Return(pool1.Rent(64)); + pool2.Return(pool2.Rent(64)); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + + // This exercises pool weak reference list trimming: + LeakPoolInstance(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + + Thread.Sleep(15_000); + Assert.True( + UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80"); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void LeakPoolInstance() + { + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 4_000 }; + _ = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + } + } + #if NETCORE31COMPATIBLE public static readonly bool Is32BitProcess = !Environment.Is64BitProcess; - private static readonly List PressureArrays = new List(); + private static readonly List PressureArrays = new(); [ConditionalFact(nameof(Is32BitProcess))] public static void GC_Collect_OnHighLoad_TrimsEntirePool() { RemoteExecutor.Invoke(RunTest).Dispose(); + static void RunTest() { Assert.False(Environment.Is64BitProcess); - const int OneMb = 1024 * 1024; + const int OneMb = 1 << 20; var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { HighPressureThresholdRate = 0.2f }; @@ -82,6 +122,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + // Prevent eager collection of the pool: + GC.KeepAlive(pool); + static void TouchPage(byte[] b) { uint size = (uint)b.Length; diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs index 86bbff181..021d071fe 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; @@ -17,13 +18,42 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { private readonly ITestOutputHelper output; - public UniformUnmanagedMemoryPoolTests(ITestOutputHelper output) + public UniformUnmanagedMemoryPoolTests(ITestOutputHelper output) => this.output = output; + + private class CleanupUtil : IDisposable { - this.output = output; - } + private readonly UniformUnmanagedMemoryPool pool; + private readonly List handlesToDestroy = new(); + private readonly List ptrsToDestroy = new(); + + public CleanupUtil(UniformUnmanagedMemoryPool pool) + { + this.pool = pool; + } + + public void Register(UnmanagedMemoryHandle handle) => this.handlesToDestroy.Add(handle); - private static unsafe Span GetSpan(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) => - new Span((void*)h.DangerousGetHandle(), pool.BufferLength); + public void Register(IEnumerable handles) => this.handlesToDestroy.AddRange(handles); + + public void Register(IntPtr memoryPtr) => this.ptrsToDestroy.Add(memoryPtr); + + public void Register(IEnumerable memoryPtrs) => this.ptrsToDestroy.AddRange(memoryPtrs); + + public void Dispose() + { + foreach (UnmanagedMemoryHandle handle in this.handlesToDestroy) + { + handle.Free(); + } + + this.pool.Release(); + + foreach (IntPtr ptr in this.ptrsToDestroy) + { + Marshal.FreeHGlobal(ptr); + } + } + } [Theory] [InlineData(3, 11)] @@ -41,10 +71,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators public void Rent_SingleBuffer_ReturnsCorrectBuffer(int length, int capacity) { var pool = new UniformUnmanagedMemoryPool(length, capacity); + using var cleanup = new CleanupUtil(pool); + for (int i = 0; i < capacity; i++) { UnmanagedMemoryHandle h = pool.Rent(); CheckBuffer(length, pool, h); + cleanup.Register(h); } } @@ -68,9 +101,8 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators private static void CheckBuffer(int length, UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) { - Assert.NotNull(h); - Assert.False(h.IsClosed); - Span span = GetSpan(pool, h); + Assert.False(h.IsInvalid); + Span span = h.GetSpan(); span.Fill(123); byte[] expected = new byte[length]; @@ -86,7 +118,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators public void Rent_MultiBuffer_ReturnsCorrectBuffers(int length, int bufferCount) { var pool = new UniformUnmanagedMemoryPool(length, 10); + using var cleanup = new CleanupUtil(pool); UnmanagedMemoryHandle[] handles = pool.Rent(bufferCount); + cleanup.Register(handles); + Assert.NotNull(handles); Assert.Equal(bufferCount, handles.Length); @@ -100,12 +135,15 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators public void Rent_MultipleTimesWithoutReturn_ReturnsDifferentHandles() { var pool = new UniformUnmanagedMemoryPool(128, 10); + using var cleanup = new CleanupUtil(pool); UnmanagedMemoryHandle[] a = pool.Rent(2); + cleanup.Register(a); UnmanagedMemoryHandle b = pool.Rent(); + cleanup.Register(b); - Assert.NotEqual(a[0].DangerousGetHandle(), a[1].DangerousGetHandle()); - Assert.NotEqual(a[0].DangerousGetHandle(), b.DangerousGetHandle()); - Assert.NotEqual(a[1].DangerousGetHandle(), b.DangerousGetHandle()); + Assert.NotEqual(a[0].Handle, a[1].Handle); + Assert.NotEqual(a[0].Handle, b.Handle); + Assert.NotEqual(a[1].Handle, b.Handle); } [Theory] @@ -115,6 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators public void RentReturnRent_SameBuffers(int totalCount, int rentUnit, int capacity) { var pool = new UniformUnmanagedMemoryPool(128, capacity); + using var cleanup = new CleanupUtil(pool); var allHandles = new HashSet(); var handleUnits = new List(); @@ -128,6 +167,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { allHandles.Add(array); } + + // Allocate some memory, so potential new pool allocation wouldn't allocated the same memory: + cleanup.Register(Marshal.AllocHGlobal(128)); } foreach (UnmanagedMemoryHandle[] arrayUnit in handleUnits) @@ -151,14 +193,20 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { Assert.Contains(array, allHandles); } + + cleanup.Register(allHandles); } [Fact] - public void Rent_SingleBuffer_OverCapacity_ReturnsNull() + public void Rent_SingleBuffer_OverCapacity_ReturnsInvalidBuffer() { var pool = new UniformUnmanagedMemoryPool(7, 1000); - Assert.NotNull(pool.Rent(1000)); - Assert.Null(pool.Rent()); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] initial = pool.Rent(1000); + Assert.NotNull(initial); + cleanup.Register(initial); + UnmanagedMemoryHandle b1 = pool.Rent(); + Assert.True(b1.IsInvalid); } [Theory] @@ -168,8 +216,12 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators public void Rent_MultiBuffer_OverCapacity_ReturnsNull(int initialRent, int attempt, int capacity) { var pool = new UniformUnmanagedMemoryPool(128, capacity); - Assert.NotNull(pool.Rent(initialRent)); - Assert.Null(pool.Rent(attempt)); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] initial = pool.Rent(initialRent); + Assert.NotNull(initial); + cleanup.Register(initial); + UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); + Assert.Null(b1); } [Theory] @@ -180,56 +232,49 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators public void Rent_MultiBuff_BelowCapacity_Succeeds(int initialRent, int attempt, int capacity) { var pool = new UniformUnmanagedMemoryPool(128, capacity); - Assert.NotNull(pool.Rent(initialRent)); - Assert.NotNull(pool.Rent(attempt)); + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle[] b0 = pool.Rent(initialRent); + Assert.NotNull(b0); + cleanup.Register(b0); + UnmanagedMemoryHandle[] b1 = pool.Rent(attempt); + Assert.NotNull(b1); + cleanup.Register(b1); } [Theory] [InlineData(false)] [InlineData(true)] - public void Release_SubsequentRentReturnsNull(bool multiple) + public void RentReturnRelease_SubsequentRentReturnsDifferentHandles(bool multiple) { var pool = new UniformUnmanagedMemoryPool(16, 16); - pool.Rent(); // Dummy rent + using var cleanup = new CleanupUtil(pool); + UnmanagedMemoryHandle b0 = pool.Rent(); + IntPtr h0 = b0.Handle; + UnmanagedMemoryHandle b1 = pool.Rent(); + IntPtr h1 = b1.Handle; + pool.Return(b0); + pool.Return(b1); pool.Release(); + + // Do some unmanaged allocations to make sure new pool buffers are different: + IntPtr[] dummy = Enumerable.Range(0, 100).Select(_ => Marshal.AllocHGlobal(16)).ToArray(); + cleanup.Register(dummy); + if (multiple) { UnmanagedMemoryHandle b = pool.Rent(); - Assert.Null(b); + cleanup.Register(b); + Assert.NotEqual(h0, b.Handle); + Assert.NotEqual(h1, b.Handle); } else { UnmanagedMemoryHandle[] b = pool.Rent(2); - Assert.Null(b); - } - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void Release_SubsequentReturnClosesHandle(bool multiple) - { - var pool = new UniformUnmanagedMemoryPool(16, 16); - if (multiple) - { - UnmanagedMemoryHandle[] b = pool.Rent(2); - pool.Release(); - - Assert.False(b[0].IsClosed); - Assert.False(b[1].IsClosed); - - pool.Return(b); - - Assert.True(b[0].IsClosed); - Assert.True(b[1].IsClosed); - } - else - { - UnmanagedMemoryHandle b = pool.Rent(); - pool.Release(); - Assert.False(b.IsClosed); - pool.Return(b); - Assert.True(b.IsClosed); + cleanup.Register(b); + Assert.NotEqual(h0, b[0].Handle); + Assert.NotEqual(h1, b[0].Handle); + Assert.NotEqual(h0, b[1].Handle); + Assert.NotEqual(h1, b[1].Handle); } } @@ -257,6 +302,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { int count = Environment.ProcessorCount * 200; var pool = new UniformUnmanagedMemoryPool(8, count); + using var cleanup = new CleanupUtil(pool); var rnd = new Random(0); Parallel.For(0, Environment.ProcessorCount, (int i) => @@ -267,8 +313,8 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { UnmanagedMemoryHandle[] data = pool.Rent(2); - GetSpan(pool, data[0]).Fill((byte)i); - GetSpan(pool, data[1]).Fill((byte)i); + data[0].GetSpan().Fill((byte)i); + data[1].GetSpan().Fill((byte)i); allArrays.Add(data[0]); allArrays.Add(data[1]); @@ -283,7 +329,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators foreach (UnmanagedMemoryHandle array in allArrays) { - Assert.True(expected.SequenceEqual(GetSpan(pool, array))); + Assert.True(expected.SequenceEqual(array.GetSpan())); pool.Return(new[] { array }); } }); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index e67831e19..12acbb914 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -247,6 +247,8 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); b.Dispose(); g.Dispose(); + Assert.Equal(5, UnmanagedMemoryHandle.TotalOutstandingHandles); + allocator.ReleaseRetainedResources(); Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); } } @@ -307,7 +309,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [ConditionalTheory(nameof(IsWindows))] [InlineData(300)] [InlineData(600)] - [InlineData(1200)] public void MemoryOwnerFinalizer_ReturnsToPool(int length) { // RunTest(length.ToString()); @@ -332,9 +333,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators using IMemoryOwner g = allocator.Allocate(lengthInner); Assert.Equal(42, g.GetSpan()[0]); + GC.KeepAlive(allocator); } } + [MethodImpl(MethodImplOptions.NoInlining)] private static void AllocateSingleAndForget(UniformUnmanagedMemoryPoolMemoryAllocator allocator, int length, bool check = false) { IMemoryOwner g = allocator.Allocate(length); diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs new file mode 100644 index 000000000..68251be86 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedBufferTests.cs @@ -0,0 +1,93 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.Collections.Generic; +using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Memory.Internals; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory.Allocators +{ + public class UnmanagedBufferTests + { + public class AllocatorBufferTests : BufferTestSuite + { + public AllocatorBufferTests() + : base(new UnmanagedMemoryAllocator(1024 * 64)) + { + } + } + + [Fact] + public void Allocate_CreatesValidBuffer() + { + using var buffer = UnmanagedBuffer.Allocate(10); + Span span = buffer.GetSpan(); + Assert.Equal(10, span.Length); + span[9] = 123; + Assert.Equal(123, span[9]); + } + + [Fact] + public unsafe void Dispose_DoesNotReleaseOutstandingReferences() + { + RemoteExecutor.Invoke(RunTest).Dispose(); + + static void RunTest() + { + var buffer = UnmanagedBuffer.Allocate(10); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + Span span = buffer.GetSpan(); + + // Pin should AddRef + using (MemoryHandle h = buffer.Pin()) + { + int* ptr = (int*)h.Pointer; + ((IDisposable)buffer).Dispose(); + Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); + ptr[3] = 13; + Assert.Equal(13, span[3]); + } // Unpin should ReleaseRef + + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + } + + [Theory] + [InlineData(2)] + [InlineData(12)] + public void BufferFinalization_TracksAllocations(int count) + { + RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); + + static void RunTest(string countStr) + { + int countInner = int.Parse(countStr); + List> l = FillList(countInner); + + l.RemoveRange(0, l.Count / 2); + + GC.Collect(); + GC.WaitForPendingFinalizers(); + + Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements + Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + + static List> FillList(int countInner) + { + var l = new List>(); + for (int i = 0; i < countInner; i++) + { + var h = UnmanagedBuffer.Allocate(42); + l.Add(h); + } + + return l; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs index ecc2188eb..a3f827355 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UnmanagedMemoryHandleTests.cs @@ -14,10 +14,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [Fact] public unsafe void Allocate_AllocatesReadWriteMemory() { - using var h = UnmanagedMemoryHandle.Allocate(128); - Assert.False(h.IsClosed); + var h = UnmanagedMemoryHandle.Allocate(128); Assert.False(h.IsInvalid); - byte* ptr = (byte*)h.DangerousGetHandle(); + Assert.True(h.IsValid); + byte* ptr = (byte*)h.Handle; for (int i = 0; i < 128; i++) { ptr[i] = (byte)i; @@ -27,21 +27,23 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators { Assert.Equal((byte)i, ptr[i]); } + + h.Free(); } [Fact] - public void Dispose_ClosesHandle() + public void Free_ClosesHandle() { var h = UnmanagedMemoryHandle.Allocate(128); - h.Dispose(); - Assert.True(h.IsClosed); + h.Free(); Assert.True(h.IsInvalid); + Assert.Equal(IntPtr.Zero, h.Handle); } [Theory] [InlineData(1)] [InlineData(13)] - public void CreateDispose_TracksAllocations(int count) + public void Create_Free_AllocationsAreTracked(int count) { RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); @@ -60,125 +62,39 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators for (int i = 0; i < countInner; i++) { Assert.Equal(countInner - i, UnmanagedMemoryHandle.TotalOutstandingHandles); - l[i].Dispose(); + l[i].Free(); Assert.Equal(countInner - i - 1, UnmanagedMemoryHandle.TotalOutstandingHandles); } } } - [Theory] - [InlineData(2)] - [InlineData(12)] - public void CreateFinalize_TracksAllocations(int count) - { - RemoteExecutor.Invoke(RunTest, count.ToString()).Dispose(); - - static void RunTest(string countStr) - { - int countInner = int.Parse(countStr); - List l = FillList(countInner); - - l.RemoveRange(0, l.Count / 2); - - GC.Collect(); - GC.WaitForPendingFinalizers(); - - Assert.Equal(countInner / 2, l.Count); // This is here to prevent eager finalization of the list's elements - Assert.Equal(countInner / 2, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - - static List FillList(int countInner) - { - var l = new List(); - for (int i = 0; i < countInner; i++) - { - var h = UnmanagedMemoryHandle.Allocate(42); - l.Add(h); - } - - return l; - } - } - [Fact] - public void Resurrect_PreventsFinalization() + public void Equality_WhenTrue() { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - AllocateResurrect(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - - static void AllocateResurrect() - { - var h = UnmanagedMemoryHandle.Allocate(42); - h.Resurrect(); - } - } - - private static UnmanagedMemoryHandle resurrectedHandle; - - private class HandleOwner - { - private UnmanagedMemoryHandle handle; - - public HandleOwner(UnmanagedMemoryHandle handle) => this.handle = handle; - - ~HandleOwner() - { - resurrectedHandle = this.handle; - this.handle.Resurrect(); - } + var h1 = UnmanagedMemoryHandle.Allocate(10); + UnmanagedMemoryHandle h2 = h1; + + Assert.True(h1.Equals(h2)); + Assert.True(h2.Equals(h1)); + Assert.True(h1 == h2); + Assert.False(h1 != h2); + Assert.True(h1.GetHashCode() == h2.GetHashCode()); + h1.Free(); } [Fact] - public void AssignedToNewOwner_ReRegistersForFinalization() + public void Equality_WhenFalse() { - RemoteExecutor.Invoke(RunTest).Dispose(); - - static void RunTest() - { - AllocateAndForget(); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - GC.Collect(); - GC.WaitForPendingFinalizers(); - VerifyResurrectedHandle(true); - GC.Collect(); - GC.WaitForPendingFinalizers(); - VerifyResurrectedHandle(false); - GC.Collect(); - GC.WaitForPendingFinalizers(); - - Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); - } - - static void AllocateAndForget() - { - _ = new HandleOwner(UnmanagedMemoryHandle.Allocate(42)); - } + var h1 = UnmanagedMemoryHandle.Allocate(10); + var h2 = UnmanagedMemoryHandle.Allocate(10); - static void VerifyResurrectedHandle(bool reAssign) - { - Assert.NotNull(resurrectedHandle); - Assert.Equal(1, UnmanagedMemoryHandle.TotalOutstandingHandles); - Assert.False(resurrectedHandle.IsClosed); - Assert.False(resurrectedHandle.IsInvalid); - resurrectedHandle.AssignedToNewOwner(); - if (reAssign) - { - _ = new HandleOwner(resurrectedHandle); - } + Assert.False(h1.Equals(h2)); + Assert.False(h2.Equals(h1)); + Assert.False(h1 == h2); + Assert.True(h1 != h2); - resurrectedHandle = null; - } + h1.Free(); + h2.Free(); } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index e6d07a191..257874f1f 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -19,8 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public class Allocate : MemoryGroupTestsBase { #pragma warning disable SA1509 - public static TheoryData AllocateData = - new TheoryData() + public static TheoryData AllocateData = new() { { default(S5), 22, 4, 4, 1, 4, 4 }, { default(S5), 22, 4, 7, 2, 4, 3 }, @@ -100,9 +99,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers g.Dispose(); } - private static unsafe Span GetSpan(UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) => - new Span((void*)h.DangerousGetHandle(), pool.BufferLength); - [Theory] [InlineData(AllocationOptions.None)] [InlineData(AllocationOptions.Clean)] @@ -112,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers UnmanagedMemoryHandle[] buffers = pool.Rent(5); foreach (UnmanagedMemoryHandle b in buffers) { - GetSpan(pool, b).Fill(42); + b.GetSpan().Fill(42); } pool.Return(buffers); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs index 3b8d0073e..2d1c6e224 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BasicTestPatternProvider.cs @@ -61,8 +61,6 @@ namespace SixLabors.ImageSharp.Tests } }); - - return result; } From b43e963b606add00a03e9fb7551ad13b42ceb850 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 24 Nov 2021 21:08:02 +0100 Subject: [PATCH 056/228] stress testing improvements --- .../LoadResizeSaveStressRunner.cs | 31 +++----- .../LoadResizeSaveParallelMemoryStress.cs | 70 +++++++++++++------ tests/ImageSharp.Tests/RunTestsInLoop.ps1 | 7 +- 3 files changed, 61 insertions(+), 47 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs index 999a44ff3..eda054968 100644 --- a/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs +++ b/tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressRunner.cs @@ -44,6 +44,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public double TotalProcessedMegapixels { get; private set; } + public Size LastProcessedImageSize { get; private set; } + private string outputDirectory; public int ImageCount { get; set; } = int.MaxValue; @@ -54,9 +56,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public int ThumbnailSize { get; set; } = 150; - // Inject leaking memory allocation requests to ImageSharp processing code to stress-test finalizer behavior. - public bool EmulateLeakedAllocations { get; set; } - private static readonly string[] ProgressiveFiles = { "ancyloscelis-apiformis-m-paraguay-face_2014-08-08-095255-zs-pmax_15046500892_o.jpg", @@ -125,8 +124,9 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave new ParallelOptions { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism }, action); - private void IncreaseTotalMegapixels(int width, int height) + private void LogImageProcessed(int width, int height) { + this.LastProcessedImageSize = new Size(width, height); double pixels = width * (double)height; this.TotalProcessedMegapixels += pixels / 1_000_000.0; } @@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SystemDrawingResize(string input) { using var image = SystemDrawingImage.FromFile(input, true); - this.IncreaseTotalMegapixels(image.Width, image.Height); + this.LogImageProcessed(image.Width, image.Height); (int Width, int Height) scaled = this.ScaledSize(image.Width, image.Height, this.ThumbnailSize); var resized = new Bitmap(scaled.Width, scaled.Height); @@ -182,13 +182,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // Resize it to fit a 150x150 square using var image = ImageSharpImage.Load(input); - this.IncreaseTotalMegapixels(image.Width, image.Height); - - if (this.EmulateLeakedAllocations) - { - _ = Configuration.Default.MemoryAllocator.Allocate(image.Width * image.Height); - _ = Configuration.Default.MemoryAllocator.Allocate(1 << 21); - } + this.LogImageProcessed(image.Width, image.Height); image.Mutate(i => i.Resize(new ResizeOptions { @@ -201,17 +195,12 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave // Save the results image.Save(output, this.imageSharpJpegEncoder); - - if (this.EmulateLeakedAllocations) - { - _ = Configuration.Default.MemoryAllocator.Allocate2D(image.Width, image.Height); - } } public void MagickResize(string input) { using var image = new MagickImage(input); - this.IncreaseTotalMegapixels(image.Width, image.Height); + this.LogImageProcessed(image.Width, image.Height); // Resize it to fit a 150x150 square image.Resize(this.ThumbnailSize, this.ThumbnailSize); @@ -246,7 +235,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaCanvasResize(string input) { using var original = SKBitmap.Decode(input); - this.IncreaseTotalMegapixels(original.Width, original.Height); + this.LogImageProcessed(original.Width, original.Height); (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); using var surface = SKSurface.Create(new SKImageInfo(scaled.Width, scaled.Height, original.ColorType, original.AlphaType)); using var paint = new SKPaint() { FilterQuality = SKFilterQuality.High }; @@ -264,7 +253,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave public void SkiaBitmapResize(string input) { using var original = SKBitmap.Decode(input); - this.IncreaseTotalMegapixels(original.Width, original.Height); + this.LogImageProcessed(original.Width, original.Height); (int Width, int Height) scaled = this.ScaledSize(original.Width, original.Height, this.ThumbnailSize); using var resized = original.Resize(new SKImageInfo(scaled.Width, scaled.Height), SKFilterQuality.High); if (resized == null) @@ -283,7 +272,7 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave using var codec = SKCodec.Create(input); SKImageInfo info = codec.Info; - this.IncreaseTotalMegapixels(info.Width, info.Height); + this.LogImageProcessed(info.Width, info.Height); (int Width, int Height) scaled = this.ScaledSize(info.Width, info.Height, this.ThumbnailSize); SKSizeI supportedScale = codec.GetScaledDimensions((float)scaled.Width / info.Width); diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs index e8b3a744c..c7484daa0 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Runtime.CompilerServices; using System.Text; using System.Threading; using CommandLine; @@ -27,13 +28,19 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox this.Benchmarks.Init(); } + private int gcFrequency; + + private int leakFrequency; + + private int imageCounter; + public LoadResizeSaveStressRunner Benchmarks { get; } public static void Run(string[] args) { Console.WriteLine($"Running: {typeof(LoadResizeSaveParallelMemoryStress).Assembly.Location}"); Console.WriteLine($"64 bit: {Environment.Is64BitProcess}"); - var options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; + CommandLineOptions options = args.Length > 0 ? CommandLineOptions.Parse(args) : null; var lrs = new LoadResizeSaveParallelMemoryStress(); if (options != null) @@ -55,10 +62,12 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox if (!options.KeepDefaultAllocator) { - MemoryAllocator.Default = Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); + Configuration.Default.MemoryAllocator = options.CreateMemoryAllocator(); } - lrs.Benchmarks.EmulateLeakedAllocations = options.LeakAllocations; + lrs.leakFrequency = options.LeakFrequency; + lrs.gcFrequency = options.GcFrequency; + timer = Stopwatch.StartNew(); try @@ -80,16 +89,23 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox Configuration.Default.MemoryAllocator.ReleaseRetainedResources(); } - for (int i = 0; i < options.FinalGcCount; i++) + int finalGcCount = -Math.Min(0, options.GcFrequency); + + if (finalGcCount > 0) { - GC.Collect(); - GC.WaitForPendingFinalizers(); - Thread.Sleep(1000); + Console.WriteLine($"TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}"); + Console.WriteLine($"GC x {finalGcCount}, with 3 seconds wait."); + for (int i = 0; i < finalGcCount; i++) + { + Thread.Sleep(3000); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } } } var stats = new Stats(timer, lrs.Benchmarks.TotalProcessedMegapixels); - Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}"); + Console.WriteLine($"Total Megapixels: {stats.TotalMegapixels}, TotalOomRetries: {UnmanagedMemoryHandle.TotalOomRetries}, TotalOutstandingHandles: {UnmanagedMemoryHandle.TotalOutstandingHandles}, Total Gen2 GC count: {GC.CollectionCount(2)}"); Console.WriteLine(stats.GetMarkdown()); if (options?.FileOutput != null) { @@ -121,8 +137,9 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox 2. ImageSharp 3. MagicScaler 4. SkiaSharp -5. NetVips -6. ImageMagick +5. SkiaSharp - Decode to target size +6. NetVips +7. ImageMagick "); ConsoleKey key = Console.ReadKey().Key; @@ -149,9 +166,12 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox lrs.SkiaBitmapBenchmarkParallel(); break; case ConsoleKey.D5: - lrs.NetVipsBenchmarkParallel(); + lrs.SkiaBitmapDecodeToTargetSizeBenchmarkParallel(); break; case ConsoleKey.D6: + lrs.NetVipsBenchmarkParallel(); + break; + case ConsoleKey.D7: lrs.MagickBenchmarkParallel(); break; } @@ -222,8 +242,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox [Option('r', "repeat-count", Required = false, Default = 1, HelpText = "Times to run the whole benchmark")] public int RepeatCount { get; set; } = 1; - [Option('g', "final-gc-count", Required = false, Default = 0, HelpText = "How many times to GC.Collect after execution")] - public int FinalGcCount { get; set; } + [Option('g', "gc-frequency", Required = false, Default = 0, HelpText = "Positive number: call GC every 'g'-th resize. Negative number: call GC '-g' times in the end.")] + public int GcFrequency { get; set; } [Option('e', "release-at-end", Required = false, Default = false, HelpText = "Specify to run ReleaseRetainedResources() after execution")] public bool ReleaseRetainedResourcesAtEnd { get; set; } @@ -237,8 +257,8 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox [Option('t', "trim-period", Required = false, Default = null, HelpText = "Trim period for the pool in seconds")] public int? TrimTimeSeconds { get; set; } - [Option('l', "leak-allocations", Required = false, Default = false, HelpText = "Inject leaking memory allocation requests to stress-test finalizer behavior.")] - public bool LeakAllocations { get; set; } + [Option('l', "leak-frequency", Required = false, Default = 0, HelpText = "Inject leaking memory allocations after every 'l'-th resize to stress test finalizer behavior.")] + public int LeakFrequency { get; set; } public static CommandLineOptions Parse(string[] args) { @@ -257,7 +277,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox } public override string ToString() => - $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd}_l({this.LeakAllocations}))"; + $"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_d({this.KeepDefaultAllocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.GcFrequency})_e({this.ReleaseRetainedResourcesAtEnd})_l({this.LeakFrequency})"; public MemoryAllocator CreateMemoryAllocator() { @@ -290,28 +310,32 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox private void SystemDrawingBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SystemDrawingResize); - private void ImageSharpBenchmarkParallel() - { - int cnt = 0; + private void ImageSharpBenchmarkParallel() => this.ForEachImage(f => { + int cnt = Interlocked.Increment(ref this.imageCounter); this.Benchmarks.ImageSharpResize(f); - if (cnt % 4 == 0 && this.Benchmarks.EmulateLeakedAllocations) + if (this.leakFrequency > 0 && cnt % this.leakFrequency == 0) + { + _ = Configuration.Default.MemoryAllocator.Allocate(1 << 16); + Size size = this.Benchmarks.LastProcessedImageSize; + _ = new Image(size.Width, size.Height); + } + + if (this.gcFrequency > 0 && cnt % this.gcFrequency == 0) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } - - cnt++; }); - } private void MagickBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagickResize); private void MagicScalerBenchmarkParallel() => this.ForEachImage(this.Benchmarks.MagicScalerResize); private void SkiaBitmapBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapResize); + private void SkiaBitmapDecodeToTargetSizeBenchmarkParallel() => this.ForEachImage(this.Benchmarks.SkiaBitmapDecodeToTargetSize); private void NetVipsBenchmarkParallel() => this.ForEachImage(this.Benchmarks.NetVipsResize); } diff --git a/tests/ImageSharp.Tests/RunTestsInLoop.ps1 b/tests/ImageSharp.Tests/RunTestsInLoop.ps1 index ef4bd5ccb..c7c5c9ac5 100644 --- a/tests/ImageSharp.Tests/RunTestsInLoop.ps1 +++ b/tests/ImageSharp.Tests/RunTestsInLoop.ps1 @@ -1,16 +1,17 @@ # This script can be used to collect logs from sporadic bugs Param( [int]$TestRunCount=10, - [string]$TargetFramework="netcoreapp3.1" + [string]$TargetFramework="netcoreapp3.1", + [string]$Configuration="Release" ) $runId = Get-Random -Minimum 0 -Maximum 9999 -dotnet build -c Release -f $TargetFramework +dotnet build -c $Configuration -f $TargetFramework for ($i = 0; $i -lt $TestRunCount; $i++) { $logFile = ".\_testlog-" + $runId.ToString("d4") + "-run-" + $i.ToString("d3") + ".log" Write-Host "Test run $i ..." - & dotnet test --no-build -c Release -f $TargetFramework 3>&1 2>&1 > $logFile + & dotnet test --no-build -c $Configuration -f $TargetFramework 3>&1 2>&1 > $logFile if ($LastExitCode -eq 0) { Write-Host "Success!" Remove-Item $logFile From b685d3787a529aa2d8e89c8f950deec1e295eeaa Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 24 Nov 2021 21:21:27 +0100 Subject: [PATCH 057/228] re-enable tests on Unix --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 12acbb914..9f96dffbd 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -253,10 +253,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - public static bool IsWindows => TestEnvironment.IsWindows; - - // TODO: This doesn't seem to work on Unix. Open an issue & investigate. - [ConditionalTheory(nameof(IsWindows))] [InlineData(300)] [InlineData(600)] [InlineData(1200)] @@ -306,7 +302,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [ConditionalTheory(nameof(IsWindows))] [InlineData(300)] [InlineData(600)] public void MemoryOwnerFinalizer_ReturnsToPool(int length) From b0b56df9b1b9e52de38f27c78f58b8d02cf92cc3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 24 Nov 2021 21:23:28 +0100 Subject: [PATCH 058/228] wait a bit more in trim tests --- .../Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 35d2237c0..532753c80 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators UnmanagedMemoryHandle[] b = pool.Rent(64); pool.Return(a); Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - Thread.Sleep(15_000); + Thread.Sleep(20_000); // We expect at least 2 Trim actions, first trim 32, then 16 arrays. // 128 - 32 - 16 = 80 @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators GC.WaitForPendingFinalizers(); Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - Thread.Sleep(15_000); + Thread.Sleep(20_000); Assert.True( UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, - $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 80"); + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); } [MethodImpl(MethodImplOptions.NoInlining)] From 75102503bfd242d56a6a9e49d267a99ff5301002 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 24 Nov 2021 22:17:46 +0100 Subject: [PATCH 059/228] Support Pbm, Pgm and Ppm images --- src/ImageSharp/Advanced/AotCompilerTools.cs | 5 + src/ImageSharp/Configuration.cs | 3 + .../Formats/ImageExtensions.Save.cs | 104 ++++++++ .../Formats/ImageExtensions.Save.tt | 1 + src/ImageSharp/Formats/Pbm/BinaryDecoder.cs | 192 +++++++++++++++ src/ImageSharp/Formats/Pbm/BinaryEncoder.cs | 203 ++++++++++++++++ .../Pbm/BufferedReadStreamExtensions.cs | 65 +++++ .../Formats/Pbm/IPbmEncoderOptions.cs | 26 ++ .../Formats/Pbm/MetadataExtensions.cs | 21 ++ src/ImageSharp/Formats/Pbm/PbmColorType.cs | 26 ++ .../Formats/Pbm/PbmConfigurationModule.cs | 19 ++ src/ImageSharp/Formats/Pbm/PbmConstants.cs | 28 +++ src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 67 +++++ src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 169 +++++++++++++ src/ImageSharp/Formats/Pbm/PbmEncoder.cs | 48 ++++ src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs | 176 ++++++++++++++ src/ImageSharp/Formats/Pbm/PbmEncoding.cs | 21 ++ src/ImageSharp/Formats/Pbm/PbmFormat.cs | 37 +++ .../Formats/Pbm/PbmImageFormatDetector.cs | 33 +++ src/ImageSharp/Formats/Pbm/PbmMetadata.cs | 48 ++++ src/ImageSharp/Formats/Pbm/PlainDecoder.cs | 197 +++++++++++++++ src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 228 ++++++++++++++++++ .../Formats/Pbm/StreamExtensions.cs | 19 ++ src/ImageSharp/ImageSharp.csproj | 2 +- tests/ImageSharp.Tests/ConfigurationTests.cs | 2 +- .../Formats/GeneralFormatTests.cs | 9 + .../Formats/ImageFormatManagerTests.cs | 3 + .../Formats/Pbm/ImageExtensionsTest.cs | 155 ++++++++++++ .../Formats/Pbm/PbmDecoderTests.cs | 40 +++ .../Formats/Pbm/PbmEncoderTests.cs | 144 +++++++++++ .../Formats/Pbm/PbmMetadataTests.cs | 84 +++++++ .../Formats/Pbm/PbmTestUtils.cs | 65 +++++ .../Formats/Pbm/RoundTripTests.cs | 44 ++++ .../Image/ImageTests.SaveAsync.cs | 2 + tests/ImageSharp.Tests/TestImages.cs | 11 + .../TestUtilities/TestEnvironment.Formats.cs | 2 + tests/Images/Input/Pbm/00000_00000.ppm | 4 + .../Pbm/Gene-UP WebSocket RunImageMask.pgm | Bin 0 -> 614417 bytes .../Images/Input/Pbm/blackandwhite_binary.pbm | 3 + .../Images/Input/Pbm/blackandwhite_plain.pbm | 10 + tests/Images/Input/Pbm/grayscale_plain.pgm | 10 + tests/Images/Input/Pbm/rgb_plain.ppm | 8 + tests/Images/Input/Pbm/rings.pgm | Bin 0 -> 40038 bytes 43 files changed, 2332 insertions(+), 2 deletions(-) create mode 100644 src/ImageSharp/Formats/Pbm/BinaryDecoder.cs create mode 100644 src/ImageSharp/Formats/Pbm/BinaryEncoder.cs create mode 100644 src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs create mode 100644 src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs create mode 100644 src/ImageSharp/Formats/Pbm/MetadataExtensions.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmColorType.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmConstants.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmDecoder.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmEncoder.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmEncoding.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmFormat.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs create mode 100644 src/ImageSharp/Formats/Pbm/PbmMetadata.cs create mode 100644 src/ImageSharp/Formats/Pbm/PlainDecoder.cs create mode 100644 src/ImageSharp/Formats/Pbm/PlainEncoder.cs create mode 100644 src/ImageSharp/Formats/Pbm/StreamExtensions.cs create mode 100644 tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs create mode 100644 tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs create mode 100644 tests/ImageSharp.Tests/Formats/Pbm/RoundTripTests.cs create mode 100644 tests/Images/Input/Pbm/00000_00000.ppm create mode 100644 tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm create mode 100644 tests/Images/Input/Pbm/blackandwhite_binary.pbm create mode 100644 tests/Images/Input/Pbm/blackandwhite_plain.pbm create mode 100644 tests/Images/Input/Pbm/grayscale_plain.pgm create mode 100644 tests/Images/Input/Pbm/rgb_plain.ppm create mode 100644 tests/Images/Input/Pbm/rings.pgm diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index 3961cc6c5..e41e38f7f 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -200,6 +201,7 @@ namespace SixLabors.ImageSharp.Advanced default(BmpEncoderCore).Encode(default, default, default); default(GifEncoderCore).Encode(default, default, default); default(JpegEncoderCore).Encode(default, default, default); + default(PbmEncoderCore).Encode(default, default, default); default(PngEncoderCore).Encode(default, default, default); default(TgaEncoderCore).Encode(default, default, default); default(TiffEncoderCore).Encode(default, default, default); @@ -217,6 +219,7 @@ namespace SixLabors.ImageSharp.Advanced default(BmpDecoderCore).Decode(default, default, default); default(GifDecoderCore).Decode(default, default, default); default(JpegDecoderCore).Decode(default, default, default); + default(PbmDecoderCore).Decode(default, default, default); default(PngDecoderCore).Decode(default, default, default); default(TgaDecoderCore).Decode(default, default, default); default(TiffDecoderCore).Decode(default, default, default); @@ -234,6 +237,7 @@ namespace SixLabors.ImageSharp.Advanced AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); + AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); AotCompileImageEncoder(); @@ -251,6 +255,7 @@ namespace SixLabors.ImageSharp.Advanced AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); + AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); AotCompileImageDecoder(); diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index ea9524827..4b7333de0 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -178,6 +179,7 @@ namespace SixLabors.ImageSharp /// /// /// . + /// . /// . /// . /// . @@ -188,6 +190,7 @@ namespace SixLabors.ImageSharp new JpegConfigurationModule(), new GifConfigurationModule(), new BmpConfigurationModule(), + new PbmConfigurationModule(), new TgaConfigurationModule(), new TiffConfigurationModule(), new WebpConfigurationModule()); diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index c5237f2bc..a6a65aef6 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Webp; @@ -331,6 +332,109 @@ namespace SixLabors.ImageSharp encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance), cancellationToken); + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + public static void SaveAsPbm(this Image source, string path) => SaveAsPbm(source, path, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path) => SaveAsPbmAsync(source, path, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path, CancellationToken cancellationToken) + => SaveAsPbmAsync(source, path, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// Thrown if the path is null. + public static void SaveAsPbm(this Image source, string path, PbmEncoder encoder) => + source.Save( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The file path to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the path is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, string path, PbmEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + path, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), + cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// Thrown if the stream is null. + public static void SaveAsPbm(this Image source, Stream stream) + => SaveAsPbm(source, stream, null); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, Stream stream, CancellationToken cancellationToken = default) + => SaveAsPbmAsync(source, stream, null, cancellationToken); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static void SaveAsPbm(this Image source, Stream stream, PbmEncoder encoder) + => source.Save( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance)); + + /// + /// Saves the image to the given stream with the Pbm format. + /// + /// The image this method extends. + /// The stream to save the image to. + /// The encoder to save the image with. + /// The token to monitor for cancellation requests. + /// Thrown if the stream is null. + /// A representing the asynchronous operation. + public static Task SaveAsPbmAsync(this Image source, Stream stream, PbmEncoder encoder, CancellationToken cancellationToken = default) => + source.SaveAsync( + stream, + encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PbmFormat.Instance), + cancellationToken); + /// /// Saves the image to the given stream with the Png format. /// diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt index 874f3ab0d..c4a00b37c 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Advanced; "Bmp", "Gif", "Jpeg", + "Pbm", "Png", "Tga", "Webp", diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs new file mode 100644 index 000000000..1c86b2bd8 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -0,0 +1,192 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel decoding methods for the PBM binary encoding. + /// + internal class BinaryDecoder + { + /// + /// Decode the specified pixels. + /// + /// The type of pixel to encode to. + /// The configuration. + /// The pixel array to encode into. + /// The stream to read the data from. + /// The ColorType to decode. + /// The maximum expected pixel value + /// + /// Thrown if an invalid combination of setting is requested. + /// + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, int maxPixelValue) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (maxPixelValue < 256) + { + ProcessGrayscale(configuration, pixels, stream); + } + else + { + ProcessWideGrayscale(configuration, pixels, stream); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (maxPixelValue < 256) + { + ProcessRgb(configuration, pixels, stream); + } + else + { + ProcessWideRgb(configuration, pixels, stream); + } + } + else + { + ProcessBlackAndWhite(configuration, pixels, stream); + } + } + + private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + int bytesPerPixel = 1; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL8Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + int bytesPerPixel = 2; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL16Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + int bytesPerPixel = 3; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromRgb24Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + int bytesPerPixel = 6; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + stream.Read(rowSpan); + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromRgb48Bytes( + configuration, + rowSpan, + pixelSpan, + width); + } + } + + private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + int startBit = 0; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + var white = new L8(255); + var black = new L8(0); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width;) + { + int raw = stream.ReadByte(); + int bit = startBit; + startBit = 0; + for (; bit < 8; bit++) + { + bool bitValue = (raw & (0x80 >> bit)) != 0; + rowSpan[x] = bitValue ? black : white; + x++; + if (x == width) + { + startBit = (bit + 1) % 8; + if (startBit != 0) + { + stream.Seek(-1, System.IO.SeekOrigin.Current); + } + break; + } + } + } + + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs new file mode 100644 index 000000000..1233c87fc --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs @@ -0,0 +1,203 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel encoding methods for the PBM binary encoding. + /// + internal class BinaryEncoder + { + /// + /// Decode pixels into the PBM binary encoding. + /// + /// The type of input pixel. + /// The configuration. + /// The bytestream to write to. + /// The input image. + /// The ColorType to use. + /// The maximum expected pixel value + /// + /// Thrown if an invalid combination of setting is requested. + /// + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, int maxPixelValue) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (maxPixelValue < 256) + { + WriteGrayscale(configuration, stream, image); + } + else + { + WriteWideGrayscale(configuration, stream, image); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (maxPixelValue < 256) + { + WriteRgb(configuration, stream, image); + } + else + { + WriteWideRgb(configuration, stream, image); + } + } + else + { + WriteBlackAndWhite(configuration, stream, image); + } + } + + private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + + PixelOperations.Instance.ToL8Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesPerPixel = 2; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + + PixelOperations.Instance.ToL16Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesPerPixel = 3; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + + PixelOperations.Instance.ToRgb24Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesPerPixel = 6; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + + PixelOperations.Instance.ToRgb48Bytes( + configuration, + pixelSpan, + rowSpan, + width); + + stream.Write(rowSpan); + } + } + + private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + int previousValue = 0; + int startBit = 0; + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + for (int x = 0; x < width;) + { + int value = previousValue; + for (int i = startBit; i < 8; i++) + { + if (rowSpan[x].PackedValue < 128) + { + value |= 0x80 >> i; + } + + x++; + if (x == width) + { + previousValue = value; + startBit = (i + 1) % 8; + break; + } + } + + if (startBit == 0) + { + stream.WriteByte((byte)value); + previousValue = 0; + } + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs new file mode 100644 index 000000000..054731b48 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.IO; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Extensions methods for . + /// + internal static class BufferedReadStreamExtensions + { + /// + /// Skip over any whitespace or any comments. + /// + public static void SkipWhitespaceAndComments(this BufferedReadStream stream) + { + bool isWhitespace; + do + { + int val = stream.ReadByte(); + + // Comments start with '#' and end at the next new-line. + if (val == 0x23) + { + int innerValue; + do + { + innerValue = stream.ReadByte(); + } + while (innerValue != 0x0a); + + // Continue searching for whitespace. + val = innerValue; + } + + isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; + } + while (isWhitespace); + stream.Seek(-1, SeekOrigin.Current); + } + + /// + /// Read a decimal text value. + /// + /// The integer value of the decimal. + public static int ReadDecimal(this BufferedReadStream stream) + { + int value = 0; + while (true) + { + int current = stream.ReadByte() - 0x30; + if (current < 0 || current > 9) + { + break; + } + + value = (value * 10) + current; + } + + return value; + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs new file mode 100644 index 000000000..c5c409ec8 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Configuration options for use during PBM encoding. + /// + internal interface IPbmEncoderOptions + { + /// + /// Gets the encoding of the pixels. + /// + PbmEncoding? Encoding { get; } + + /// + /// Gets the Color type of the resulting image. + /// + PbmColorType? ColorType { get; } + + /// + /// Gets the maximum pixel value, per component. + /// + int? MaxPixelValue { get; } + } +} diff --git a/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs new file mode 100644 index 000000000..cce8fb318 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/MetadataExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.Metadata; + +namespace SixLabors.ImageSharp +{ + /// + /// Extension methods for the type. + /// + public static partial class MetadataExtensions + { + /// + /// Gets the pbm format specific metadata for the image. + /// + /// The metadata this method extends. + /// The . + public static PbmMetadata GetPbmMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(PbmFormat.Instance); + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmColorType.cs b/src/ImageSharp/Formats/Pbm/PbmColorType.cs new file mode 100644 index 000000000..827f19344 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmColorType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Provides enumeration of available PBM color types. + /// + public enum PbmColorType : byte + { + /// + /// PBM + /// + BlackAndWhite = 0, + + /// + /// PGM - Greyscale. Single component. + /// + Grayscale = 1, + + /// + /// PPM - RGB Color. 3 components. + /// + Rgb = 2, + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs new file mode 100644 index 000000000..172bda667 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmConfigurationModule.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the Pbm format. + /// + public sealed class PbmConfigurationModule : IConfigurationModule + { + /// + public void Configure(Configuration configuration) + { + configuration.ImageFormatsManager.SetEncoder(PbmFormat.Instance, new PbmEncoder()); + configuration.ImageFormatsManager.SetDecoder(PbmFormat.Instance, new PbmDecoder()); + configuration.ImageFormatsManager.AddImageFormatDetector(new PbmImageFormatDetector()); + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmConstants.cs b/src/ImageSharp/Formats/Pbm/PbmConstants.cs new file mode 100644 index 000000000..0aa9b706a --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmConstants.cs @@ -0,0 +1,28 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Contains PBM constant values defined in the specification. + /// + internal static class PbmConstants + { + /// + /// The maximum allowable pixel value of a ppm image. + /// + public const ushort MaxLength = 65535; + + /// + /// The list of mimetypes that equate to a ppm. + /// + public static readonly IEnumerable MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap", "image/x-portable-arbitrarymap" }; + + /// + /// The list of file extensions that equate to a ppm. + /// + public static readonly IEnumerable FileExtensions = new[] { "ppm", "pbm", "pgm", "pam" }; + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs new file mode 100644 index 000000000..640ec3823 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -0,0 +1,67 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Image decoder for generating an image out of a ppm stream. + /// + public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector + { + /// + public Image Decode(Configuration configuration, Stream stream) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new PbmDecoderCore(configuration); + return decoder.Decode(configuration, stream); + } + + /// + public Image Decode(Configuration configuration, Stream stream) + => this.Decode(configuration, stream); + + /// + public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new PbmDecoderCore(configuration); + return decoder.DecodeAsync(configuration, stream, cancellationToken); + } + + /// + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + + /// + public IImageInfo Identify(Configuration configuration, Stream stream) + { + Guard.NotNull(stream, nameof(stream)); + + var decoder = new PbmDecoderCore(configuration); + return decoder.Identify(configuration, stream); + } + + /// + public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + { + Guard.NotNull(stream, nameof(stream)); + + // The introduction of a local variable that refers to an object the implements + // IDisposable means you must use async/await, where the compiler generates the + // state machine and a continuation. + var decoder = new PbmDecoderCore(configuration); + return await decoder.IdentifyAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs new file mode 100644 index 000000000..31969af47 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Threading; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Performs the PBM decoding operation. + /// + internal sealed class PbmDecoderCore : IImageDecoderInternals + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public PbmDecoderCore(Configuration configuration) => this.Configuration = configuration ?? Configuration.Default; + + /// + public Configuration Configuration { get; } + + /// + /// Gets the colortype to use + /// + public PbmColorType ColorType { get; private set; } + + /// + /// Gets the size of the pixel array + /// + public Size PixelSize { get; private set; } + + /// + /// Gets the maximum pixel value + /// + public int MaxPixelValue { get; private set; } + + /// + /// Gets the Encoding of pixels + /// + public PbmEncoding Encoding { get; private set; } + + /// + /// Gets the decoded by this decoder instance. + /// + public ImageMetadata Metadata { get; private set; } + + /// + Size IImageDecoderInternals.Dimensions => this.PixelSize; + + /// + public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + this.ProcessHeader(stream); + + var image = new Image(this.Configuration, this.PixelSize.Width, this.PixelSize.Height, this.Metadata); + + Buffer2D pixels = image.GetRootFramePixelBuffer(); + + this.ProcessPixels(stream, pixels); + + return image; + } + + /// + public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) + { + this.ProcessHeader(stream); + + int bitsPerPixel = this.MaxPixelValue > 255 ? 16 : 8; + return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); + } + + /// + /// Processes the ppm header. + /// + /// The input stream. + private void ProcessHeader(BufferedReadStream stream) + { + byte[] buffer = new byte[2]; + + int bytesRead = stream.Read(buffer, 0, 2); + if (bytesRead != 2 || buffer[0] != 'P') + { + // Empty or not an PPM image. + throw new InvalidImageContentException("TODO"); + } + + switch ((char)buffer[1]) + { + case '1': + // Plain PBM format: 1 component per pixel, boolean value ('0' or '1'). + this.ColorType = PbmColorType.BlackAndWhite; + this.Encoding = PbmEncoding.Plain; + break; + case '2': + // Plain PGM format: 1 component per pixel, in decimal text. + this.ColorType = PbmColorType.Grayscale; + this.Encoding = PbmEncoding.Plain; + break; + case '3': + // Plain PPM format: 3 components per pixel, in decimal text. + this.ColorType = PbmColorType.Rgb; + this.Encoding = PbmEncoding.Plain; + break; + case '4': + // Binary PBM format: 1 component per pixel, 8 picels per byte. + this.ColorType = PbmColorType.BlackAndWhite; + this.Encoding = PbmEncoding.Binary; + break; + case '5': + // Binary PGM format: 1 components per pixel, in binary integers. + this.ColorType = PbmColorType.Grayscale; + this.Encoding = PbmEncoding.Binary; + break; + case '6': + // Binary PPM format: 3 components per pixel, in binary integers. + this.ColorType = PbmColorType.Rgb; + this.Encoding = PbmEncoding.Binary; + break; + case '7': + // PAM image: sequence of images. + // Not implemented yet + default: + throw new NotImplementedException("TODO"); + } + + stream.SkipWhitespaceAndComments(); + int width = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + int height = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + if (this.ColorType != PbmColorType.BlackAndWhite) + { + this.MaxPixelValue = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + } + else + { + this.MaxPixelValue = 1; + } + + this.PixelSize = new Size(width, height); + this.Metadata = new ImageMetadata(); + PbmMetadata meta = this.Metadata.GetPbmMetadata(); + meta.Encoding = this.Encoding; + meta.ColorType = this.ColorType; + meta.MaxPixelValue = this.MaxPixelValue; + } + + private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) + where TPixel : unmanaged, IPixel + { + if (this.Encoding == PbmEncoding.Binary) + { + BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.MaxPixelValue); + } + else + { + PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.MaxPixelValue); + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs new file mode 100644 index 000000000..21565d161 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Image encoder for writing an image to a stream as PGM, PBM, PPM or PAM bitmap. + /// + public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions + { + /// + /// Gets or sets the Encoding of the pixels. + /// + public PbmEncoding? Encoding { get; set; } + + /// + /// Gets or sets the Color type of the resulting image. + /// + public PbmColorType? ColorType { get; set; } + + /// + /// Gets or sets the maximum pixel value, per component. + /// + public int? MaxPixelValue { get; set; } + + /// + public void Encode(Image image, Stream stream) + where TPixel : unmanaged, IPixel + { + var encoder = new PbmEncoderCore(image.GetConfiguration(), this); + encoder.Encode(image, stream); + } + + /// + public Task EncodeAsync(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + var encoder = new PbmEncoderCore(image.GetConfiguration(), this); + return encoder.EncodeAsync(image, stream, cancellationToken); + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs new file mode 100644 index 000000000..527ceb8ee --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs @@ -0,0 +1,176 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using System.Text; +using System.Threading; +using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Image encoder for writing an image to a stream as a PGM, PBM, PPM or PAM bitmap. + /// + internal sealed class PbmEncoderCore : IImageEncoderInternals + { + private const char NewLine = '\n'; + + /// + /// The global configuration. + /// + private Configuration configuration; + + /// + /// The encoder options. + /// + private readonly IPbmEncoderOptions options; + + /// + /// The encoding for the pixels. + /// + private PbmEncoding encoding; + + /// + /// Gets the Color type of the resulting image. + /// + private PbmColorType colorType; + + /// + /// Gets the maximum pixel value, per component. + /// + private int maxPixelValue; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// The encoder options. + public PbmEncoderCore(Configuration configuration, IPbmEncoderOptions options) + { + this.configuration = configuration; + this.options = options; + } + + /// + /// Encodes the image to the specified stream from the . + /// + /// The pixel format. + /// The to encode from. + /// The to encode the image data to. + /// The token to request cancellation. + public void Encode(Image image, Stream stream, CancellationToken cancellationToken) + where TPixel : unmanaged, IPixel + { + Guard.NotNull(image, nameof(image)); + Guard.NotNull(stream, nameof(stream)); + + this.DeduceOptions(image); + + string signature = this.DeduceSignature(); + this.WriteHeader(stream, signature, image.Size()); + + this.WritePixels(stream, image.Frames.RootFrame); + + stream.Flush(); + } + + private void DeduceOptions(Image image) + where TPixel : unmanaged, IPixel + { + this.configuration = image.GetConfiguration(); + PbmMetadata metadata = image.Metadata.GetPbmMetadata(); + this.encoding = this.options.Encoding ?? metadata.Encoding; + this.colorType = this.options.ColorType ?? metadata.ColorType; + if (this.colorType != PbmColorType.BlackAndWhite) + { + this.maxPixelValue = this.options.MaxPixelValue ?? metadata.MaxPixelValue; + } + } + + private string DeduceSignature() + { + string signature; + if (this.colorType == PbmColorType.BlackAndWhite) + { + if (this.encoding == PbmEncoding.Plain) + { + signature = "P1"; + } + else + { + signature = "P4"; + } + } + else if (this.colorType == PbmColorType.Grayscale) + { + if (this.encoding == PbmEncoding.Plain) + { + signature = "P2"; + } + else + { + signature = "P5"; + } + } + else + { + // RGB ColorType + if (this.encoding == PbmEncoding.Plain) + { + signature = "P3"; + } + else + { + signature = "P6"; + } + } + + return signature; + } + + private void WriteHeader(Stream stream, string signature, Size pixelSize) + { + var builder = new StringBuilder(20); + builder.Append(signature); + builder.Append(NewLine); + builder.Append(pixelSize.Width.ToString()); + builder.Append(NewLine); + builder.Append(pixelSize.Height.ToString()); + builder.Append(NewLine); + if (this.colorType != PbmColorType.BlackAndWhite) + { + builder.Append(this.maxPixelValue.ToString()); + builder.Append(NewLine); + } + + string headerStr = builder.ToString(); + byte[] headerBytes = Encoding.ASCII.GetBytes(headerStr); + stream.Write(headerBytes, 0, headerBytes.Length); + } + + /// + /// Writes the pixel data to the binary stream. + /// + /// The pixel format. + /// The to write to. + /// + /// The containing pixel data. + /// + private void WritePixels(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + if (this.encoding == PbmEncoding.Plain) + { + PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.maxPixelValue); + } + else + { + BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.maxPixelValue); + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoding.cs b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs new file mode 100644 index 000000000..be7fb909f --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmEncoding.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Provides enumeration of available PBM encodings. + /// + public enum PbmEncoding : byte + { + /// + /// Plain text decimal encoding. + /// + Plain = 0, + + /// + /// Binary integer encoding. + /// + Binary = 1, + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmFormat.cs b/src/ImageSharp/Formats/Pbm/PbmFormat.cs new file mode 100644 index 000000000..35aa9cf8c --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmFormat.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Registers the image encoders, decoders and mime type detectors for the PBM format. + /// + public sealed class PbmFormat : IImageFormat + { + private PbmFormat() + { + } + + /// + /// Gets the current instance. + /// + public static PbmFormat Instance { get; } = new PbmFormat(); + + /// + public string Name => "PBM"; + + /// + public string DefaultMimeType => "image/x-portable-pixmap"; + + /// + public IEnumerable MimeTypes => PbmConstants.MimeTypes; + + /// + public IEnumerable FileExtensions => PbmConstants.FileExtensions; + + /// + public PbmMetadata CreateDefaultFormatMetadata() => new(); + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs new file mode 100644 index 000000000..943424dc9 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Detects Pbm file headers. + /// + public sealed class PbmImageFormatDetector : IImageFormatDetector + { + private const byte P = (byte)'P'; + private const byte Zero = (byte)'0'; + private const byte Seven = (byte)'7'; + + /// + public int HeaderSize => 2; + + /// + public IImageFormat DetectFormat(ReadOnlySpan header) => this.IsSupportedFileFormat(header) ? PbmFormat.Instance : null; + + private bool IsSupportedFileFormat(ReadOnlySpan header) + { + if (header.Length >= this.HeaderSize) + { + return header[0] == P && header[1] > Zero && header[1] < Seven; + } + + return false; + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs new file mode 100644 index 000000000..b29cd27c2 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -0,0 +1,48 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Provides PBM specific metadata information for the image. + /// + public class PbmMetadata : IDeepCloneable + { + /// + /// Initializes a new instance of the class. + /// + public PbmMetadata() + { + this.MaxPixelValue = this.ColorType == PbmColorType.BlackAndWhite ? 1 : 255; + } + + /// + /// Initializes a new instance of the class. + /// + /// The metadata to create an instance from. + private PbmMetadata(PbmMetadata other) + { + this.Encoding = other.Encoding; + this.ColorType = other.ColorType; + this.MaxPixelValue = other.MaxPixelValue; + } + + /// + /// Gets or sets the encoding of the pixels. + /// + public PbmEncoding Encoding { get; set; } = PbmEncoding.Plain; + + /// + /// Gets or sets the color type. + /// + public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; + + /// + /// Gets or sets the maximum pixel value. + /// + public int MaxPixelValue { get; set; } + + /// + public IDeepCloneable DeepClone() => new PbmMetadata(this); + } +} diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs new file mode 100644 index 000000000..bf2b10e1d --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -0,0 +1,197 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using SixLabors.ImageSharp.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel decoding methods for the PBM plain encoding. + /// + internal class PlainDecoder + { + /// + /// Decode the specified pixels. + /// + /// The type of pixel to encode to. + /// The configuration. + /// The pixel array to encode into. + /// The stream to read the data from. + /// The ColorType to decode. + /// The maximum expected pixel value + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, int maxPixelValue) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (maxPixelValue < 256) + { + ProcessGrayscale(configuration, pixels, stream); + } + else + { + ProcessWideGrayscale(configuration, pixels, stream); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (maxPixelValue < 256) + { + ProcessRgb(configuration, pixels, stream); + } + else + { + ProcessWideRgb(configuration, pixels, stream); + } + } + else + { + ProcessBlackAndWhite(configuration, pixels, stream); + } + } + + private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + byte value = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new L8(value); + } + + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + ushort value = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new L16(value); + } + + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL16( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + byte red = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + byte green = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + byte blue = (byte)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new Rgb24(red, green, blue); + } + + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromRgb24( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + ushort red = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + ushort green = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + ushort blue = (ushort)stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = new Rgb48(red, green, blue); + } + + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromRgb48( + configuration, + rowSpan, + pixelSpan); + } + } + + private static void ProcessBlackAndWhite(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) + where TPixel : unmanaged, IPixel + { + int width = pixels.Width; + int height = pixels.Height; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + var white = new L8(0); + var black = new L8(255); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int value = stream.ReadDecimal(); + stream.SkipWhitespaceAndComments(); + rowSpan[x] = value == 0 ? white : black; + } + + Span pixelSpan = pixels.GetRowSpan(y); + PixelOperations.Instance.FromL8( + configuration, + rowSpan, + pixelSpan); + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs new file mode 100644 index 000000000..d90eaf73f --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Buffers; +using System.IO; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// Pixel encoding methods for the PBM plain encoding. + /// + internal class PlainEncoder + { + private const int MaxLineLength = 70; + private const byte NewLine = 0x0a; + private const byte Space = 0x20; + private const byte Zero = 0x30; + private const byte One = 0x31; + + /// + /// Decode pixels into the PBM plain encoding. + /// + /// The type of input pixel. + /// The configuration. + /// The bytestream to write to. + /// The input image. + /// The ColorType to use. + /// The maximum expected pixel value + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, int maxPixelValue) + where TPixel : unmanaged, IPixel + { + if (colorType == PbmColorType.Grayscale) + { + if (maxPixelValue < 256) + { + WriteGrayscale(configuration, stream, image); + } + else + { + WriteWideGrayscale(configuration, stream, image); + } + } + else if (colorType == PbmColorType.Rgb) + { + if (maxPixelValue < 256) + { + WriteRgb(configuration, stream, image); + } + else + { + WriteWideRgb(configuration, stream, image); + } + } + else + { + WriteBlackAndWhite(configuration, stream, image); + } + } + + private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesWritten = -1; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + for (int x = 0; x < width; x++) + { + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); + } + } + } + + private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesWritten = -1; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToL16( + configuration, + pixelSpan, + rowSpan); + + for (int x = 0; x < width; x++) + { + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); + } + } + } + + private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesWritten = -1; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToRgb24( + configuration, + pixelSpan, + rowSpan); + + for (int x = 0; x < width; x++) + { + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].R); + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].G); + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].B); + } + } + } + + private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesWritten = -1; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToRgb48( + configuration, + pixelSpan, + rowSpan); + + for (int x = 0; x < width; x++) + { + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].R); + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].G); + WriteWhitespace(stream, ref bytesWritten); + bytesWritten += stream.WriteDecimal(rowSpan[x].B); + } + } + } + + private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + int width = image.Size().Width; + int height = image.Size().Height; + int bytesWritten = -1; + MemoryAllocator allocator = configuration.MemoryAllocator; + using IMemoryOwner row = allocator.Allocate(width); + Span rowSpan = row.GetSpan(); + + for (int y = 0; y < height; y++) + { + Span pixelSpan = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToL8( + configuration, + pixelSpan, + rowSpan); + + for (int x = 0; x < width; x++) + { + WriteWhitespace(stream, ref bytesWritten); + if (rowSpan[x].PackedValue > 127) + { + stream.WriteByte(Zero); + } + else + { + stream.WriteByte(One); + } + + bytesWritten++; + } + } + } + + private static void WriteWhitespace(Stream stream, ref int bytesWritten) + { + if (bytesWritten > MaxLineLength) + { + stream.WriteByte(NewLine); + bytesWritten = 1; + } + else if (bytesWritten == -1) + { + bytesWritten = 0; + } + else + { + stream.WriteByte(Space); + bytesWritten++; + } + } + } +} diff --git a/src/ImageSharp/Formats/Pbm/StreamExtensions.cs b/src/ImageSharp/Formats/Pbm/StreamExtensions.cs new file mode 100644 index 000000000..9851afee0 --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/StreamExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Text; + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + internal static class StreamExtensions + { + public static int WriteDecimal(this Stream stream, int value) + { + string str = value.ToString(); + byte[] bytes = Encoding.ASCII.GetBytes(str); + stream.Write(bytes); + return bytes.Length; + } + } +} diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 800b69326..a0a45e8aa 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -10,7 +10,7 @@ Apache-2.0 https://github.com/SixLabors/ImageSharp/ $(RepositoryUrl) - Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore + Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET Debug;Release;Debug-InnerLoop;Release-InnerLoop diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 803babdfa..104f07c40 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests public Configuration DefaultConfiguration { get; } - private readonly int expectedDefaultConfigurationCount = 7; + private readonly int expectedDefaultConfigurationCount = 8; public ConfigurationTests() { diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index bf13a9097..eeb77da67 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -136,6 +136,11 @@ namespace SixLabors.ImageSharp.Tests.Formats image.SaveAsJpeg(output); } + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.pbm"))) + { + image.SaveAsPbm(output); + } + using (FileStream output = File.OpenWrite(Path.Combine(path, $"{file.FileNameWithoutExtension}.png"))) { image.SaveAsPng(output); @@ -183,6 +188,10 @@ namespace SixLabors.ImageSharp.Tests.Formats } [Theory] + [InlineData(10, 10, "pbm")] + [InlineData(100, 100, "pbm")] + [InlineData(100, 10, "pbm")] + [InlineData(10, 100, "pbm")] [InlineData(10, 10, "png")] [InlineData(100, 100, "png")] [InlineData(100, 10, "png")] diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs index 5cd70b100..05a7a3be8 100644 --- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs +++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -33,6 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Formats [Fact] public void IfAutoLoadWellKnownFormatsIsTrueAllFormatsAreLoaded() { + Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); @@ -41,6 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageEncoders.Select(item => item.Value).OfType().Count()); + Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); Assert.Equal(1, this.DefaultFormatsManager.ImageDecoders.Select(item => item.Value).OfType().Count()); diff --git a/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs b/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs new file mode 100644 index 000000000..6ab0cf22f --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/ImageExtensionsTest.cs @@ -0,0 +1,155 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Threading.Tasks; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + public class ImageExtensionsTest + { + [Fact] + public void SaveAsPbm_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPbm_Path.pbm"); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_Path() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest)); + string file = Path.Combine(dir, "SaveAsPbmAsync_Path.pbm"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(file); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPbm_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPbm_Path_Encoder.pbm"); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(file, new PbmEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_Path_Encoder() + { + string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions)); + string file = Path.Combine(dir, "SaveAsPbmAsync_Path_Encoder.pbm"); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(file, new PbmEncoder()); + } + + using (Image.Load(file, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPbm_Stream() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_StreamAsync() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(memoryStream); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public void SaveAsPbm_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + image.SaveAsPbm(memoryStream, new PbmEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + + [Fact] + public async Task SaveAsPbmAsync_Stream_Encoder() + { + using var memoryStream = new MemoryStream(); + + using (var image = new Image(10, 10)) + { + await image.SaveAsPbmAsync(memoryStream, new PbmEncoder()); + } + + memoryStream.Position = 0; + + using (Image.Load(memoryStream, out IImageFormat mime)) + { + Assert.Equal("image/x-portable-pixmap", mime.DefaultMimeType); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs new file mode 100644 index 000000000..4ff359387 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Pbm; + +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Trait("Format", "Pbm")] + public class PbmDecoderTests + { + [Theory] + [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] + [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] + [InlineData(GrayscalePlain, PbmColorType.Grayscale)] + [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] + [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] + [InlineData(RgbPlain, PbmColorType.Rgb)] + [InlineData(RgbBinary, PbmColorType.Rgb)] + public void PpmDecoder_CanDecode(string imagePath, PbmColorType expectedColorType) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + PbmMetadata bitmapMetadata = image.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedColorType, bitmapMetadata.ColorType); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs new file mode 100644 index 000000000..339cc4a5c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -0,0 +1,144 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Pbm; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.Formats.Tga; + +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Collection("RunSerial")] + [Trait("Format", "Pbm")] + public class PbmEncoderTests + { + public static readonly TheoryData ColorType = + new TheoryData + { + PbmColorType.BlackAndWhite, + PbmColorType.Grayscale, + PbmColorType.Rgb + }; + + public static readonly TheoryData PbmColorTypeFiles = + new TheoryData + { + { BlackAndWhiteBinary, PbmColorType.BlackAndWhite }, + { BlackAndWhitePlain, PbmColorType.BlackAndWhite }, + { GrayscaleBinary, PbmColorType.Grayscale }, + { GrayscaleBinaryWide, PbmColorType.Grayscale }, + { GrayscalePlain, PbmColorType.Grayscale }, + { RgbBinary, PbmColorType.Rgb }, + { RgbPlain, PbmColorType.Rgb }, + }; + + [Theory] + [MemberData(nameof(PbmColorTypeFiles))] + public void PbmEncoder_PreserveColorType(string imagePath, PbmColorType pbmColorType) + { + var options = new PbmEncoder(); + + var testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + PbmMetadata meta = output.Metadata.GetPbmMetadata(); + Assert.Equal(pbmColorType, meta.ColorType); + } + } + } + } + + [Theory] + [MemberData(nameof(PbmColorTypeFiles))] + public void PbmEncoder_WithPlainEncoding_PreserveBitsPerPixel(string imagePath, PbmColorType pbmColorType) + { + var options = new PbmEncoder() + { + Encoding = PbmEncoding.Plain + }; + + TestFile testFile = TestFile.Create(imagePath); + using (Image input = testFile.CreateRgba32Image()) + { + using (var memStream = new MemoryStream()) + { + input.Save(memStream, options); + memStream.Position = 0; + using (var output = Image.Load(memStream)) + { + PbmMetadata meta = output.Metadata.GetPbmMetadata(); + Assert.Equal(pbmColorType, meta.ColorType); + } + } + } + } + + [Theory] + [WithFile(BlackAndWhitePlain, PixelTypes.Rgb24)] + public void PbmEncoder_P1_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Plain); + + [Theory] + [WithFile(BlackAndWhiteBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P4_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); + + /* Disabled as Magick throws an error reading the input image + [Theory] + [WithFile(GrayscalePlain, PixelTypes.Rgb24)] + public void PbmEncoder_P2_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Plain); + */ + + [Theory] + [WithFile(GrayscaleBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P5_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Binary); + + /* Disabled as Magick throws an error reading the input image + [Theory] + [WithFile(RgbPlain, PixelTypes.Rgb24)] + public void PbmEncoder_P3_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Plain); + */ + + [Theory] + [WithFile(RgbBinary, PixelTypes.Rgb24)] + public void PbmEncoder_P6_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Binary); + + private static void TestPbmEncoderCore( + TestImageProvider provider, + PbmColorType colorType, + PbmEncoding encoding, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage()) + { + var encoder = new PbmEncoder { ColorType = colorType, Encoding = encoding }; + + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + memStream.Position = 0; + using (var encodedImage = (Image)Image.Load(memStream)) + { + TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); + } + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs new file mode 100644 index 000000000..00b4f443d --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs @@ -0,0 +1,84 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.Formats.Pbm; + +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Trait("Format", "Pbm")] + public class PbmMetadataTests + { + [Fact] + public void CloneIsDeep() + { + var meta = new PbmMetadata { ColorType = PbmColorType.Grayscale }; + var clone = (PbmMetadata)meta.DeepClone(); + + clone.ColorType = PbmColorType.Rgb; + + Assert.False(meta.ColorType.Equals(clone.ColorType)); + } + + [Theory] + [InlineData(BlackAndWhitePlain, PbmEncoding.Plain)] + [InlineData(BlackAndWhiteBinary, PbmEncoding.Binary)] + [InlineData(GrayscaleBinary, PbmEncoding.Binary)] + [InlineData(GrayscaleBinaryWide, PbmEncoding.Binary)] + [InlineData(GrayscalePlain, PbmEncoding.Plain)] + [InlineData(RgbBinary, PbmEncoding.Binary)] + [InlineData(RgbPlain, PbmEncoding.Plain)] + public void Identify_DetectsCorrectEncoding(string imagePath, PbmEncoding expectedEncoding) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedEncoding, bitmapMetadata.Encoding); + } + + [Theory] + [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] + [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] + [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] + [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] + [InlineData(GrayscalePlain, PbmColorType.Grayscale)] + [InlineData(RgbBinary, PbmColorType.Rgb)] + [InlineData(RgbPlain, PbmColorType.Rgb)] + public void Identify_DetectsCorrectColorType(string imagePath, PbmColorType expectedColorType) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedColorType, bitmapMetadata.ColorType); + } + + [Theory] + [InlineData(BlackAndWhitePlain, 1)] + [InlineData(BlackAndWhiteBinary, 1)] + [InlineData(GrayscaleBinary, 255)] + [InlineData(GrayscaleBinaryWide, 65535)] + [InlineData(GrayscalePlain, 15)] + [InlineData(RgbBinary, 255)] + [InlineData(RgbPlain, 15)] + public void Identify_DetectsCorrectMaxPixelValue(string imagePath, int expectedMaxPixelValue) + { + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); + Assert.NotNull(bitmapMetadata); + Assert.Equal(expectedMaxPixelValue, bitmapMetadata.MaxPixelValue); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs new file mode 100644 index 000000000..7b701fe3d --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs @@ -0,0 +1,65 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using ImageMagick; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + public static class PbmTestUtils + { + public static void CompareWithReferenceDecoder( + TestImageProvider provider, + Image image, + bool useExactComparer = true, + float compareTolerance = 0.01f) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + string path = TestImageProvider.GetFilePathOrNull(provider); + if (path == null) + { + throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); + } + + var testFile = TestFile.Create(path); + Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + if (useExactComparer) + { + ImageComparer.Exact.VerifySimilarity(magickImage, image); + } + else + { + ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); + } + } + + public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) + where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel + { + using (var magickImage = new MagickImage(fileInfo)) + { + magickImage.AutoOrient(); + var result = new Image(configuration, magickImage.Width, magickImage.Height); + + Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); + + using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) + { + byte[] data = pixels.ToByteArray(PixelMapping.RGBA); + + PixelOperations.Instance.FromRgba32Bytes( + configuration, + data, + resultPixels, + resultPixels.Length); + } + + return result; + } + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/RoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/RoundTripTests.cs new file mode 100644 index 000000000..391e8c054 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Pbm/RoundTripTests.cs @@ -0,0 +1,44 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Pbm; + +// ReSharper disable InconsistentNaming +namespace SixLabors.ImageSharp.Tests.Formats.Pbm +{ + [Trait("Format", "Pbm")] + public class RoundTripTests + { + [Theory] + [InlineData(RgbPlain)] + [InlineData(RgbBinary)] + public void PbmColorImageCanRoundTrip(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var originalImage = Image.Load(stream); + using Image encodedImage = this.RoundTrip(originalImage); + + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(originalImage, encodedImage); + } + + private Image RoundTrip(Image originalImage) + where TPixel : unmanaged, IPixel + { + using var decodedStream = new MemoryStream(); + originalImage.SaveAsPbm(decodedStream); + decodedStream.Seek(0, SeekOrigin.Begin); + var encodedImage = (Image)Image.Load(decodedStream); + return encodedImage; + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs index 8bb121349..f21f2c916 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.SaveAsync.cs @@ -71,6 +71,7 @@ namespace SixLabors.ImageSharp.Tests } [Theory] + [InlineData("test.pbm", "image/x-portable-pixmap")] [InlineData("test.png", "image/png")] [InlineData("test.tga", "image/tga")] [InlineData("test.bmp", "image/bmp")] @@ -114,6 +115,7 @@ namespace SixLabors.ImageSharp.Tests } [Theory] + [InlineData("test.pbm")] [InlineData("test.png")] [InlineData("test.tga")] [InlineData("test.bmp")] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e00364913..67f947ff5 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -866,5 +866,16 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] Metadata = { SampleMetadata }; } + + public static class Pbm + { + public const string BlackAndWhitePlain = "Pbm/blackandwhite_plain.pbm"; + public const string BlackAndWhiteBinary = "Pbm/blackandwhite_binary.pbm"; + public const string GrayscaleBinary = "Pbm/rings.pgm"; + public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; + public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; + public const string RgbBinary = "Pbm/00000_00000.ppm"; + public const string RgbPlain = "Pbm/rgb_plain.ppm"; + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs index 4b374b21f..fe512f9dc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tiff; @@ -57,6 +58,7 @@ namespace SixLabors.ImageSharp.Tests var cfg = new Configuration( new JpegConfigurationModule(), new GifConfigurationModule(), + new PbmConfigurationModule(), new TgaConfigurationModule(), new WebpConfigurationModule(), new TiffConfigurationModule()); diff --git a/tests/Images/Input/Pbm/00000_00000.ppm b/tests/Images/Input/Pbm/00000_00000.ppm new file mode 100644 index 000000000..321188762 --- /dev/null +++ b/tests/Images/Input/Pbm/00000_00000.ppm @@ -0,0 +1,4 @@ +P6 +29 30 +255 +KNPJLNVWTl^UtrnryysɻܫmpCARVbmY[aKOPDKKAEDBCBSTVPPRZYTk_{YQUZϧ~ѰɊʇR?NQ[ug\kTQVIMNLNKPPNNNPVUV]Z[xzkfD6ڹŕyݩbkbhberZ[^SSHJHIJENNJJKM[SSn\duQP[G㾵ؐFOL9oc}`ZKHBIJCIOJGJG`QHb`swIHrkϸدңΘˎy^mJH6"RHnnULJIKHP]\EJBxfTuaXA@Y_Ӂtujk``XVPNLJHED@A=?;>:y=9m=8x>7?1S=LDfkSPRJJJiknempmlB6A=@DAECGEJHOHNHNKNOOUW[_clky]`rkrlsorjqf}KHw_cHJJLIFh__{lmgrtw{}ɉ؎䐟쟵Ս|lalVPcQOZML_KJeJKTGKEEDJHBPLJo|xeq_kxbo}tttt``LLMPOTdkyiqgo```YYPWWRXVTRNNIFHIGGHGDJJHȻіoq\fJ\|f_ryXt]S^J;A9HGONIHCwvޮڳًaCBC}Pqe_nTSQOSQSURLLHNMGZWRţ~|kMWWJ[|T^xla~LVldŎ˱խýzy}сMQPZyhl]WW\a`Y[YPOLVSO[VQǎwWJJ:z`deuPOMRԝmwPmǟ}hڝYaG:qkvabkpp^a_QRPSSQ\[VaVUGvmslt@>gk٣ƺvWp^kf©zo|?9UTkmbhhY]\RUTRTT`b`r^daTwut@<ʁȾǕy˶nvճ۠@AWYmq]abX[ZUXXSUVZ\\|sqvpm}~|~x~}@<̔vq~~wǭƘ}AAZ[orX[[WZVUXTTWS[^Zlrthjndciffnhjfw}sD@pv󚗳lurxѵϑ?>^_jmVVTYZSVYQZ]T^aXlgHHX[él[[}Ĉuk۽ʺkl>4RNfg[YWZZUZ]W]`Y^aZĭëcgAAtq֨t~wjµļEOS?yor]\_\Z[[XWYUZ[V]]X­nvkxqyYOF8ɉt̰⡰_gckhplfjYTU^Z[a`bUWWUURXUQlvSk]n^q][BE*ʆhcK]cyX|YThTRV^^cWX_SUXOQPSUR|Y}}db|gI`Oj{Jm^OS?fOy_\K@7RTdrkhӇ裷Ähk]hkSeTOYPPSZ^bNQWLORHMNIQOU?OduOlKp`J_Fsw]d`lNRMI}MFQGVNoisjzcVWOKLRRT[_cMOUSTXJNQNUToĮX^:DO4mNveFlNNrUYx]b|l|jdhaztz~lkz[Y`NKPOORTXZLNOOOOMPPNTRLyWgnUW]M\|je^c}]h~bngmuy}}}{|wljy\[iMLUJJOPRRQRNRRNRSOQTPo|qkshjkdz}y}yziprnnhuoqup{okvjiuRS[FFJJJJOOKMNJNNI[[Wzpy|suvvruoothmrdkq`inbhmcgfekcKTKdla}[cinmasmugcm]Z`JHKKIIUTP^^Yab[cgc[_c[bgJUYDHKMJLTLM^QU]W^Z[`__bXVXJMNIRQLUQXaipmfumphah]V[PKOLHH]ZWge_nqh`hbUTW_ek=JOAGLEDESOMc[^ZW`RSXZZ\cbhQR]LPVNTVT_jfkhnnmb^ce^cWRWIGHQPMff_cfZZaY \ No newline at end of file diff --git a/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm b/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm new file mode 100644 index 0000000000000000000000000000000000000000..8265eaa50621a9a5455776fbe2c5faeb5933b862 GIT binary patch literal 614417 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 UfB_6(00S7n00uCCfxLnL0M1c2-v9sr literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Pbm/blackandwhite_binary.pbm b/tests/Images/Input/Pbm/blackandwhite_binary.pbm new file mode 100644 index 000000000..a25b1d350 --- /dev/null +++ b/tests/Images/Input/Pbm/blackandwhite_binary.pbm @@ -0,0 +1,3 @@ +P4 +# CREATOR: bitmap2pbm Version 1.0.0 +8 4 @0@0 diff --git a/tests/Images/Input/Pbm/blackandwhite_plain.pbm b/tests/Images/Input/Pbm/blackandwhite_plain.pbm new file mode 100644 index 000000000..fea8cafd0 --- /dev/null +++ b/tests/Images/Input/Pbm/blackandwhite_plain.pbm @@ -0,0 +1,10 @@ +P1 +# PBM example +24 7 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 +0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 +0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0 +0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 +0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/grayscale_plain.pgm b/tests/Images/Input/Pbm/grayscale_plain.pgm new file mode 100644 index 000000000..ba4757248 --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain.pgm @@ -0,0 +1,10 @@ +P2 +24 7 +15 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 +0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 +0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 +0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 +0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rgb_plain.ppm b/tests/Images/Input/Pbm/rgb_plain.ppm new file mode 100644 index 000000000..ecd1b915c --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain.ppm @@ -0,0 +1,8 @@ +P3 +# example from the man page +4 4 +15 + 0 0 0 0 0 0 0 0 0 15 0 15 + 0 0 0 0 15 7 0 0 0 0 0 0 + 0 0 0 0 0 0 0 15 7 0 0 0 +15 0 15 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rings.pgm b/tests/Images/Input/Pbm/rings.pgm new file mode 100644 index 0000000000000000000000000000000000000000..e0d4b4ed4d4cfc4da44b131a68f60824957428b6 GIT binary patch literal 40038 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 Wa*AACLyF_*_)AQ-%Z&HTYW)|`Dr-*w literal 0 HcmV?d00001 From 13a35224650a39105eb9b05b61a19c6cd4d8d684 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 24 Nov 2021 21:47:32 +0100 Subject: [PATCH 060/228] fix tests --- .../Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs | 6 ++++-- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 532753c80..48a03e626 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators UnmanagedMemoryHandle[] b = pool.Rent(64); pool.Return(a); Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - Thread.Sleep(20_000); + Thread.Sleep(15_000); // We expect at least 2 Trim actions, first trim 32, then 16 arrays. // 128 - 32 - 16 = 80 @@ -70,10 +70,12 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators GC.WaitForPendingFinalizers(); Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - Thread.Sleep(20_000); + Thread.Sleep(15_000); Assert.True( UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); + GC.KeepAlive(pool1); + GC.KeepAlive(pool2); } [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 9f96dffbd..e6c50ae34 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -253,6 +253,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } + [Theory] [InlineData(300)] [InlineData(600)] [InlineData(1200)] @@ -302,6 +303,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } + [Theory] [InlineData(300)] [InlineData(600)] public void MemoryOwnerFinalizer_ReturnsToPool(int length) From 916b31c4a291e071bb37df39aa49a35012d8cb4a Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 01:04:34 +0100 Subject: [PATCH 061/228] Disable MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed on MacOS + .NET 6 --- .../Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 48a03e626..ed0bed838 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -47,7 +47,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [Fact] + public static readonly bool MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed_Enabled = + !(TestEnvironment.IsOSX && TestEnvironment.NetCoreVersion?.Major == 6); + + // TODO: Investigate failure on MacOS + .net 6.0. All handles are released after GC. + [ConditionalFact(nameof(MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed_Enabled))] public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { RemoteExecutor.Invoke(RunTest).Dispose(); From d045df2f52c626d54764920fb3154755edc09ca2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 13:42:41 +0100 Subject: [PATCH 062/228] implement pool finalization & cleanup --- ...iformUnmanagedMemoryPool.LifetimeGuards.cs | 23 ++++- .../Internals/UniformUnmanagedMemoryPool.cs | 97 ++++++++++++------- .../Internals/UnmanagedBufferLifetimeGuard.cs | 4 +- .../Internals/UnmanagedMemoryHandle.cs | 30 +----- ...iformUnmanagedMemoryPoolMemoryAllocator.cs | 4 +- ...sts.cs => RefCountedLifetimeGuardTests.cs} | 12 ++- .../UniformUnmanagedMemoryPoolTests.cs | 56 +++++++++-- .../MemoryGroupTests.Allocate.cs | 4 +- 8 files changed, 148 insertions(+), 82 deletions(-) rename tests/ImageSharp.Tests/Memory/Allocators/{RefCountingLifetimeGuardTests.cs => RefCountedLifetimeGuardTests.cs} (88%) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs index 032537d38..666b24855 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.LifetimeGuards.cs @@ -8,11 +8,11 @@ namespace SixLabors.ImageSharp.Memory.Internals public UnmanagedBuffer CreateGuardedBuffer( UnmanagedMemoryHandle handle, int lengthInElements, - AllocationOptions options) + bool clear) where T : struct { var buffer = new UnmanagedBuffer(lengthInElements, new ReturnToPoolBufferLifetimeGuard(this, handle)); - if (options.Has(AllocationOptions.Clean)) + if (clear) { buffer.Clear(); } @@ -33,7 +33,16 @@ namespace SixLabors.ImageSharp.Memory.Internals this.handles = handles; } - protected override void Release() => this.pool.Return(this.handles); + protected override void Release() + { + if (!this.pool.Return(this.handles)) + { + foreach (UnmanagedMemoryHandle handle in this.handles) + { + handle.Free(); + } + } + } } private sealed class ReturnToPoolBufferLifetimeGuard : UnmanagedBufferLifetimeGuard @@ -44,7 +53,13 @@ namespace SixLabors.ImageSharp.Memory.Internals : base(handle) => this.pool = pool; - protected override void Release() => this.pool.Return(this.Handle); + protected override void Release() + { + if (!this.pool.Return(this.Handle)) + { + this.Handle.Free(); + } + } } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index 4cccab4af..0c458dc00 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -4,12 +4,16 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.CompilerServices; using System.Threading; namespace SixLabors.ImageSharp.Memory.Internals { internal partial class UniformUnmanagedMemoryPool +#if !NETSTANDARD1_3 + // In case UniformUnmanagedMemoryPool is finalized, we prefer to run it's finalizer after the guard finalizers, + // but we should not rely on this. + : System.Runtime.ConstrainedExecution.CriticalFinalizerObject +#endif { private static int minTrimPeriodMilliseconds = int.MaxValue; private static readonly List> AllPools = new(); @@ -21,6 +25,7 @@ namespace SixLabors.ImageSharp.Memory.Internals private readonly UnmanagedMemoryHandle[] buffers; private int index; private long lastTrimTimestamp; + private int finalized; public UniformUnmanagedMemoryPool(int bufferLength, int capacity) : this(bufferLength, capacity, TrimSettings.Default) @@ -44,19 +49,32 @@ namespace SixLabors.ImageSharp.Memory.Internals } } + // We don't want UniformUnmanagedMemoryPool and MemoryAllocator to be IDisposable, + // since the types don't really match Disposable semantics. + // If a user wants to drop a MemoryAllocator after they finished using it, they should call allocator.ReleaseRetainedResources(), + // which normally should free the already returned (!) buffers. + // However in case if this doesn't happen, we need the retained memory to be freed by the finalizer. + ~UniformUnmanagedMemoryPool() + { + Interlocked.Exchange(ref this.finalized, 1); + this.TrimAll(this.buffers); + } + public int BufferLength { get; } public int Capacity { get; } + private bool Finalized => this.finalized == 1; + /// - /// Rent a single buffer or return if the pool is full. + /// Rent a single buffer. If the pool is full, return . /// public UnmanagedMemoryHandle Rent() { UnmanagedMemoryHandle[] buffersLocal = this.buffers; // Avoid taking the lock if the pool is is over it's limit: - if (this.index == buffersLocal.Length) + if (this.index == buffersLocal.Length || this.Finalized) { return UnmanagedMemoryHandle.NullHandle; } @@ -65,7 +83,7 @@ namespace SixLabors.ImageSharp.Memory.Internals lock (buffersLocal) { // Check again after taking the lock: - if (this.index == buffersLocal.Length) + if (this.index == buffersLocal.Length || this.Finalized) { return UnmanagedMemoryHandle.NullHandle; } @@ -90,7 +108,7 @@ namespace SixLabors.ImageSharp.Memory.Internals UnmanagedMemoryHandle[] buffersLocal = this.buffers; // Avoid taking the lock if the pool is is over it's limit: - if (this.index + bufferCount >= buffersLocal.Length + 1) + if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) { return null; } @@ -99,7 +117,7 @@ namespace SixLabors.ImageSharp.Memory.Internals lock (buffersLocal) { // Check again after taking the lock: - if (this.index + bufferCount >= buffersLocal.Length + 1) + if (this.index + bufferCount >= buffersLocal.Length + 1 || this.Finalized) { return null; } @@ -123,41 +141,49 @@ namespace SixLabors.ImageSharp.Memory.Internals return result; } - public void Return(UnmanagedMemoryHandle bufferHandle) + // The Return methods return false if and only if: + // (1) More buffers are returned than rented OR + // (2) The pool has been finalized. + // This is defensive programming, since neither of the cases should happen normally + // (case 1 would be a programming mistake in the library, case 2 should be prevented by the CriticalFinalizerObject contract), + // so we throw in Debug instead of returning false. + // In Release, the caller should Free() the handles if false is returned to avoid memory leaks. + public bool Return(UnmanagedMemoryHandle bufferHandle) { Guard.IsTrue(bufferHandle.IsValid, nameof(bufferHandle), "Returning NullHandle to the pool is not allowed."); lock (this.buffers) { - // Check again after taking the lock: - if (this.index == 0) + if (this.Finalized || this.index == 0) { - ThrowReturnedMoreBuffersThanRented(); // DEBUG-only exception - bufferHandle.Free(); - return; + this.DebugThrowInvalidReturn(); + return false; } this.buffers[--this.index] = bufferHandle; } + + return true; } - public void Return(Span bufferHandles) + public bool Return(Span bufferHandles) { lock (this.buffers) { - if (this.index - bufferHandles.Length + 1 <= 0) + if (this.Finalized || this.index - bufferHandles.Length + 1 <= 0) { - ThrowReturnedMoreBuffersThanRented(); - DisposeAll(bufferHandles); - return; + this.DebugThrowInvalidReturn(); + return false; } for (int i = bufferHandles.Length - 1; i >= 0; i--) { ref UnmanagedMemoryHandle h = ref bufferHandles[i]; Guard.IsTrue(h.IsValid, nameof(bufferHandles), "Returning NullHandle to the pool is not allowed."); - this.buffers[--this.index] = bufferHandles[i]; + this.buffers[--this.index] = h; } } + + return true; } public void Release() @@ -166,31 +192,30 @@ namespace SixLabors.ImageSharp.Memory.Internals { for (int i = this.index; i < this.buffers.Length; i++) { - UnmanagedMemoryHandle buffer = this.buffers[i]; + ref UnmanagedMemoryHandle buffer = ref this.buffers[i]; if (buffer.IsInvalid) { break; } buffer.Free(); - this.buffers[i] = UnmanagedMemoryHandle.NullHandle; } } } - private static void DisposeAll(Span buffers) + [Conditional("DEBUG")] + private void DebugThrowInvalidReturn() { - foreach (UnmanagedMemoryHandle handle in buffers) + if (this.Finalized) { - handle.Free(); + throw new ObjectDisposedException( + nameof(UniformUnmanagedMemoryPool), + "Invalid handle return to the pool! The pool has been finalized."); } - } - // This indicates a bug in the library, however Return() might be called from a finalizer, - // therefore we should never throw here in production. - [Conditional("DEBUG")] - private static void ThrowReturnedMoreBuffersThanRented() => - throw new InvalidMemoryOperationException("Returned more buffers then rented"); + throw new InvalidOperationException( + "Invalid handle return to the pool! Returning more buffers than rented."); + } private static void UpdateTimer(TrimSettings settings, UniformUnmanagedMemoryPool pool) { @@ -239,13 +264,19 @@ namespace SixLabors.ImageSharp.Memory.Internals private bool Trim() { + if (this.Finalized) + { + return false; + } + UnmanagedMemoryHandle[] buffersLocal = this.buffers; bool isHighPressure = this.IsHighMemoryPressure(); if (isHighPressure) { - return this.TrimHighPressure(buffersLocal); + this.TrimAll(buffersLocal); + return true; } long millisecondsSinceLastTrim = Stopwatch.ElapsedMilliseconds - this.lastTrimTimestamp; @@ -257,7 +288,7 @@ namespace SixLabors.ImageSharp.Memory.Internals return true; } - private bool TrimHighPressure(UnmanagedMemoryHandle[] buffersLocal) + private void TrimAll(UnmanagedMemoryHandle[] buffersLocal) { lock (buffersLocal) { @@ -265,11 +296,8 @@ namespace SixLabors.ImageSharp.Memory.Internals for (int i = this.index; i < buffersLocal.Length && buffersLocal[i].IsValid; i++) { buffersLocal[i].Free(); - buffersLocal[i] = UnmanagedMemoryHandle.NullHandle; } } - - return true; } private bool TrimLowPressure(UnmanagedMemoryHandle[] buffersLocal) @@ -290,7 +318,6 @@ namespace SixLabors.ImageSharp.Memory.Internals for (int i = trimStart; i >= trimStop; i--) { buffersLocal[i].Free(); - buffersLocal[i] = UnmanagedMemoryHandle.NullHandle; } this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs index d59f04c93..5f0759f20 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBufferLifetimeGuard.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Memory.Internals protected UnmanagedBufferLifetimeGuard(UnmanagedMemoryHandle handle) => this.handle = handle; - public UnmanagedMemoryHandle Handle => this.handle; + public ref UnmanagedMemoryHandle Handle => ref this.handle; public sealed class FreeHandle : UnmanagedBufferLifetimeGuard { @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Memory.Internals { } - protected override void Release() => this.handle.Free(); + protected override void Release() => this.Handle.Free(); } } } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs index ce2fab60a..59d4d5bda 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedMemoryHandle.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Memory.Internals public static readonly UnmanagedMemoryHandle NullHandle = default; private IntPtr handle; - private readonly int lengthInBytes; + private int lengthInBytes; private UnmanagedMemoryHandle(IntPtr handle, int lengthInBytes) { @@ -64,29 +64,6 @@ namespace SixLabors.ImageSharp.Memory.Internals public static bool operator !=(UnmanagedMemoryHandle a, UnmanagedMemoryHandle b) => !a.Equals(b); - [MethodImpl(InliningOptions.HotPath)] - public unsafe Span GetSpan() - { - if (this.IsInvalid) - { - ThrowDisposed(); - } - - return new Span(this.Pointer, this.lengthInBytes); - } - - [MethodImpl(InliningOptions.HotPath)] - public unsafe Span GetSpan(int lengthInBytes) - { - DebugGuard.MustBeLessThanOrEqualTo(lengthInBytes, this.lengthInBytes, nameof(lengthInBytes)); - if (this.IsInvalid) - { - ThrowDisposed(); - } - - return new Span(this.Pointer, lengthInBytes); - } - public static UnmanagedMemoryHandle Allocate(int lengthInBytes) { IntPtr handle = AllocateHandle(lengthInBytes); @@ -150,6 +127,8 @@ namespace SixLabors.ImageSharp.Memory.Internals Monitor.PulseAll(lowMemoryMonitor); Monitor.Exit(lowMemoryMonitor); } + + this.lengthInBytes = 0; } public bool Equals(UnmanagedMemoryHandle other) => this.handle.Equals(other.handle); @@ -157,8 +136,5 @@ namespace SixLabors.ImageSharp.Memory.Internals public override bool Equals(object obj) => obj is UnmanagedMemoryHandle other && this.Equals(other); public override int GetHashCode() => this.handle.GetHashCode(); - - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowDisposed() => throw new ObjectDisposedException(nameof(UnmanagedMemoryHandle)); } } diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index b6d1abddf..c63c0b637 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Memory UnmanagedMemoryHandle mem = this.pool.Rent(); if (mem.IsValid) { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options); + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, length, options.Has(AllocationOptions.Clean)); return buffer; } } @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Memory UnmanagedMemoryHandle mem = this.pool.Rent(); if (mem.IsValid) { - UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLength, options); + UnmanagedBuffer buffer = this.pool.CreateGuardedBuffer(mem, (int)totalLength, options.Has(AllocationOptions.Clean)); return MemoryGroup.CreateContiguous(buffer, options.Has(AllocationOptions.Clean)); } } diff --git a/tests/ImageSharp.Tests/Memory/Allocators/RefCountingLifetimeGuardTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs similarity index 88% rename from tests/ImageSharp.Tests/Memory/Allocators/RefCountingLifetimeGuardTests.cs rename to tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs index ab1aab74a..7fb3b7b7b 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/RefCountingLifetimeGuardTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/RefCountedLifetimeGuardTests.cs @@ -9,7 +9,7 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Memory.Allocators { - public class RefCountingLifetimeGuardTests + public class RefCountedLifetimeGuardTests { [Theory] [InlineData(1)] @@ -90,6 +90,16 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.Equal(1, guard.ReleaseInvocationCount); } + [Fact] + public void UnmanagedBufferLifetimeGuard_Handle_IsReturnedByRef() + { + var h = UnmanagedMemoryHandle.Allocate(10); + using var guard = new UnmanagedBufferLifetimeGuard.FreeHandle(h); + Assert.True(guard.Handle.IsValid); + guard.Handle.Free(); + Assert.False(guard.Handle.IsValid); + } + [MethodImpl(MethodImplOptions.NoInlining)] private static void LeakGuard(bool addRef) { diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs index 021d071fe..b8e77e688 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory.Internals; using Xunit; using Xunit.Abstractions; @@ -102,7 +104,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators private static void CheckBuffer(int length, UniformUnmanagedMemoryPool pool, UnmanagedMemoryHandle h) { Assert.False(h.IsInvalid); - Span span = h.GetSpan(); + Span span = GetSpan(h, pool.BufferLength); span.Fill(123); byte[] expected = new byte[length]; @@ -110,6 +112,8 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Assert.True(span.SequenceEqual(expected)); } + private static unsafe Span GetSpan(UnmanagedMemoryHandle h, int length) => new Span(h.Pointer, length); + [Theory] [InlineData(1, 1)] [InlineData(1, 5)] @@ -307,16 +311,16 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Parallel.For(0, Environment.ProcessorCount, (int i) => { - var allArrays = new List(); + var allHandles = new List(); int pauseAt = rnd.Next(100); for (int j = 0; j < 100; j++) { UnmanagedMemoryHandle[] data = pool.Rent(2); - data[0].GetSpan().Fill((byte)i); - data[1].GetSpan().Fill((byte)i); - allArrays.Add(data[0]); - allArrays.Add(data[1]); + GetSpan(data[0], pool.BufferLength).Fill((byte)i); + GetSpan(data[1], pool.BufferLength).Fill((byte)i); + allHandles.Add(data[0]); + allHandles.Add(data[1]); if (j == pauseAt) { @@ -327,12 +331,46 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators Span expected = new byte[8]; expected.Fill((byte)i); - foreach (UnmanagedMemoryHandle array in allArrays) + foreach (UnmanagedMemoryHandle h in allHandles) { - Assert.True(expected.SequenceEqual(array.GetSpan())); - pool.Return(new[] { array }); + Assert.True(expected.SequenceEqual(GetSpan(h, pool.BufferLength))); + pool.Return(new[] { h }); } }); } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void LeakPool_FinalizerShouldFreeRetainedHandles(bool withGuardedBuffers) + { + RemoteExecutor.Invoke(RunTest, withGuardedBuffers.ToString()).Dispose(); + + static void RunTest(string withGuardedBuffersInner) + { + LeakPoolInstance(bool.Parse(withGuardedBuffersInner)); + Assert.Equal(20, UnmanagedMemoryHandle.TotalOutstandingHandles); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(0, UnmanagedMemoryHandle.TotalOutstandingHandles); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void LeakPoolInstance(bool withGuardedBuffers) + { + var pool = new UniformUnmanagedMemoryPool(16, 128); + if (withGuardedBuffers) + { + UnmanagedMemoryHandle h = pool.Rent(); + _ = pool.CreateGuardedBuffer(h, 10, false); + UnmanagedMemoryHandle[] g = pool.Rent(19); + _ = pool.CreateGroupLifetimeGuard(g); + } + else + { + pool.Return(pool.Rent(20)); + } + } + } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs index 257874f1f..adfafcb89 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.Allocate.cs @@ -102,13 +102,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers [Theory] [InlineData(AllocationOptions.None)] [InlineData(AllocationOptions.Clean)] - public void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) + public unsafe void Allocate_FromPool_AllocationOptionsAreApplied(AllocationOptions options) { var pool = new UniformUnmanagedMemoryPool(10, 5); UnmanagedMemoryHandle[] buffers = pool.Rent(5); foreach (UnmanagedMemoryHandle b in buffers) { - b.GetSpan().Fill(42); + new Span(b.Pointer, pool.BufferLength).Fill(42); } pool.Return(buffers); From a8273901c196c33f500d60532c85292ef5412418 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 14:17:19 +0100 Subject: [PATCH 063/228] Docs and null check on Configuration.MemoryAllocator. --- src/ImageSharp/Configuration.cs | 31 +++++++++++++++++--- tests/ImageSharp.Tests/ConfigurationTests.cs | 15 ++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 2c981c31b..31c67dd68 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -26,10 +26,11 @@ namespace SixLabors.ImageSharp /// /// A lazily initialized configuration default instance. /// - private static readonly Lazy Lazy = new Lazy(CreateDefaultInstance); + private static readonly Lazy Lazy = new(CreateDefaultInstance); private const int DefaultStreamProcessingBufferSize = 8096; private int streamProcessingBufferSize = DefaultStreamProcessingBufferSize; private int maxDegreeOfParallelism = Environment.ProcessorCount; + private MemoryAllocator memoryAllocator = MemoryAllocator.Default; /// /// Initializes a new instance of the class. @@ -125,9 +126,31 @@ namespace SixLabors.ImageSharp public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager(); /// - /// Gets or sets the that is currently in use. + /// Gets or sets the that is currently in use. + /// Defaults to . + /// + /// Allocators are expensive, so it is strongly recommended to use only one busy instance per process. + /// In case you need to customize it, you can ensure this by changing /// - public MemoryAllocator MemoryAllocator { get; set; } = MemoryAllocator.Default; + /// + /// It's possible to reduce allocator footprint by assigning a custom instance created with + /// , but note that since the default pooling + /// allocators are expensive, it is strictly recommended to use a single process-wide allocator. + /// You can ensure this by altering the allocator of , or by implementing custom application logic that + /// manages allocator lifetime. + /// + /// If an allocator has to be dropped for some reason, + /// shall be invoked after disposing all associated instances. + /// + public MemoryAllocator MemoryAllocator + { + get => this.memoryAllocator; + set + { + Guard.NotNull(value, nameof(this.MemoryAllocator)); + this.memoryAllocator = value; + } + } /// /// Gets the maximum header size of all the formats. @@ -173,7 +196,7 @@ namespace SixLabors.ImageSharp MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, StreamProcessingBufferSize = this.StreamProcessingBufferSize, ImageFormatsManager = this.ImageFormatsManager, - MemoryAllocator = this.MemoryAllocator, + memoryAllocator = this.memoryAllocator, ImageOperationsProvider = this.ImageOperationsProvider, ReadOrigin = this.ReadOrigin, FileSystem = this.FileSystem, diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index f77db33f0..bc2bf36b5 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -149,6 +149,21 @@ namespace SixLabors.ImageSharp.Tests () => config.StreamProcessingBufferSize = 0); } + [Fact] + public void MemoryAllocator_Setter_Roundtrips() + { + MemoryAllocator customAllocator = new SimpleGcMemoryAllocator(); + var config = new Configuration() { MemoryAllocator = customAllocator }; + Assert.Same(customAllocator, config.MemoryAllocator); + } + + [Fact] + public void MemoryAllocator_SetNull_ThrowsArgumentNullException() + { + var config = new Configuration(); + Assert.Throws(() => config.MemoryAllocator = null); + } + [Fact] public void InheritsDefaultMemoryAllocatorInstance() { From 452f31c9b02488fbc9b1fae4b975701c27a0e749 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 14:32:45 +0100 Subject: [PATCH 064/228] Disable RentReturnRelease_SubsequentRentReturnsDifferentHandles on Mac. --- .../Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs index b8e77e688..00acce64e 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs @@ -245,7 +245,10 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators cleanup.Register(b1); } - [Theory] + public static readonly bool IsNotMacOS = !TestEnvironment.IsOSX; + + // TODO: Investigate MacOS failures + [ConditionalTheory(nameof(IsNotMacOS))] [InlineData(false)] [InlineData(true)] public void RentReturnRelease_SubsequentRentReturnsDifferentHandles(bool multiple) From 308676ef53df1a749cea652c05d14ba189346334 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 14:47:15 +0100 Subject: [PATCH 065/228] promote Debug-InnerLoop hack --- Directory.Build.props | 3 +++ src/ImageSharp/ImageSharp.csproj | 1 - tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5178ee343..fd78f4f19 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,6 +13,9 @@ $(MSBuildThisFileDirectory) + + + $(DefineConstants);DEBUG diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 1062e4b3a..6ad20713d 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -13,7 +13,6 @@ Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET Debug;Release;Debug-InnerLoop;Release-InnerLoop - $(DefineConstants);DEBUG diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 62e23c1cd..97567ba21 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -215,7 +215,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void TransformColorInverse_Works() => RunTransformColorInverseTest(); #if SUPPORTS_RUNTIME_INTRINSICS - [Fact] public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); From 4865adab73c130b0508ad74701cfe58bd2ed1da5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 15:11:28 +0100 Subject: [PATCH 066/228] cleanup & comments --- .../Allocators/Internals/UniformUnmanagedMemoryPool.cs | 2 +- .../UniformUnmanagedMemoryPoolMemoryAllocator.cs | 2 +- .../Memory/Allocators/UnmanagedMemoryAllocator.cs | 9 +++++---- .../DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs | 8 +------- .../Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs | 8 +++++--- .../Memory/DiscontiguousBuffers/MemoryGroup{T}.cs | 8 +------- .../ImageSharp.Tests/Image/LargeImageIntegrationTests.cs | 2 +- 7 files changed, 15 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index 0c458dc00..ced91fec7 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Memory.Internals { internal partial class UniformUnmanagedMemoryPool #if !NETSTANDARD1_3 - // In case UniformUnmanagedMemoryPool is finalized, we prefer to run it's finalizer after the guard finalizers, + // In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers, // but we should not rely on this. : System.Runtime.ConstrainedExecution.CriticalFinalizerObject #endif diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index c63c0b637..d9734baea 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Memory private static long GetDefaultMaxPoolSizeBytes() { #if NETCORE31COMPATIBLE - // On .NET Core 3.1+, determine the pool as portion of the total available memory. + // On 64 bit .NET Core 3.1+, set the pool size to a portion of the total available memory. // There is a bug in GC.GetGCMemoryInfo() on .NET 5 + 32 bit, making TotalAvailableMemoryBytes unreliable: // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 if (Environment.Is64BitProcess || !RuntimeInformation.FrameworkDescription.StartsWith(".NET 5.0")) diff --git a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs index 9b0869c40..74197b0a1 100644 --- a/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UnmanagedMemoryAllocator.cs @@ -7,14 +7,15 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { + /// + /// A implementation that allocates memory on the unmanaged heap + /// without any pooling. + /// internal class UnmanagedMemoryAllocator : MemoryAllocator { private readonly int bufferCapacityInBytes; - public UnmanagedMemoryAllocator(int bufferCapacityInBytes) - { - this.bufferCapacityInBytes = bufferCapacityInBytes; - } + public UnmanagedMemoryAllocator(int bufferCapacityInBytes) => this.bufferCapacityInBytes = bufferCapacityInBytes; protected internal override int GetBufferCapacityInBytes() => this.bufferCapacityInBytes; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs index 2e690ce9b..7c58c9c01 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Consumed.cs @@ -50,13 +50,7 @@ namespace SixLabors.ImageSharp.Memory return ((IList>)this.source).GetEnumerator(); } - protected override void Dispose(bool disposing) - { - if (disposing) - { - this.View.Invalidate(); - } - } + public override void Dispose() => this.View.Invalidate(); } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index a59602efa..3b9241383 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -129,9 +129,9 @@ namespace SixLabors.ImageSharp.Memory return this.memoryOwners.Select(mo => mo.Memory).GetEnumerator(); } - protected override void Dispose(bool disposing) + public override void Dispose() { - if (this.IsDisposed || !disposing) + if (this.IsDisposed) { return; } @@ -193,7 +193,9 @@ namespace SixLabors.ImageSharp.Memory b.View = new MemoryGroupView(b); } - // No-ownership + // When the MemoryGroup points to multiple buffers via `groupLifetimeGuard`, + // the lifetime of the individual buffers is managed by the guard. + // Group buffer IMemoryOwner-s d not manage ownership. private sealed class ObservedBuffer : MemoryManager { private readonly UnmanagedMemoryHandle handle; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 0fcbd6f96..cdd8e6a75 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -45,13 +45,7 @@ namespace SixLabors.ImageSharp.Memory public abstract Memory this[int index] { get; } /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - protected abstract void Dispose(bool disposing); + public abstract void Dispose(); /// public abstract MemoryGroupEnumerator GetEnumerator(); diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs index 1f0963aac..b2ee9d673 100644 --- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs +++ b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void PreferContiguousImageBuffers_CreateImage_MaximumPoolSizeMegabytes() + public void PreferContiguousImageBuffers_CreateImage_BufferIsContiguous() { // Run remotely to avoid large allocation in the test process: RemoteExecutor.Invoke(RunTest).Dispose(); From 8c39628929c3df14b58b66233230b40607b94478 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 15:18:09 +0100 Subject: [PATCH 067/228] make MemoryAllocatorSettings a struct --- src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs index 274e1739c..01ab46c2a 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Memory /// /// Defines options for creating the default . /// - public class MemoryAllocatorSettings + public struct MemoryAllocatorSettings { private int? maximumPoolSizeMegabytes; From 42546d033e96c935e3438e01313604a4e52d0725 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 16:13:32 +0100 Subject: [PATCH 068/228] nits --- src/ImageSharp/Common/Helpers/DebugGuard.cs | 2 +- .../Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 43622066c..23b712c52 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -27,7 +27,7 @@ namespace SixLabors } /// - /// Verifies whether a specific condition is met, throwing an exception if it's false. + /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's false. /// /// Whether the object is disposed. /// The name of the object. diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 5027d94b4..21673215a 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Memory.Internals } } - private class LifetimeGuard : RefCountedLifetimeGuard + private sealed class LifetimeGuard : RefCountedLifetimeGuard { private byte[] array; From 7a2c22a966cee0f54f19f7122bc762e80cd0f6b3 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 25 Nov 2021 20:06:44 +0300 Subject: [PATCH 069/228] Removed obsolete/redundant/unused code from Block8x8F --- .../Formats/Jpeg/Components/Block8x8F.cs | 116 ------------------ .../Formats/Jpg/Block8x8FTests.cs | 50 -------- ...plementationsTests.FastFloatingPointDCT.cs | 2 +- ...ImplementationsTests.StandardIntegerDCT.cs | 4 +- 4 files changed, 3 insertions(+), 169 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 02f5a1324..f85e57d29 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -98,58 +97,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - public static Block8x8F operator *(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val *= value; - result[i] = val; - } - - return result; - } - - public static Block8x8F operator /(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val /= value; - result[i] = val; - } - - return result; - } - - public static Block8x8F operator +(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val += value; - result[i] = val; - } - - return result; - } - - public static Block8x8F operator -(Block8x8F block, float value) - { - Block8x8F result = block; - for (int i = 0; i < Size; i++) - { - float val = result[i]; - val -= value; - result[i] = val; - } - - return result; - } - public static Block8x8F Load(Span data) { Block8x8F result = default; @@ -177,15 +124,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); } - /// - /// Load raw 32bit floating point data from source. - /// - /// Block pointer - /// Source - [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void LoadFrom(Block8x8F* blockPtr, Span source) - => blockPtr->LoadFrom(source); - /// /// Load raw 32bit floating point data from source /// @@ -202,44 +140,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - /// - /// Copy raw 32bit floating point data to dest, - /// - /// Destination - [MethodImpl(InliningOptions.ShortMethod)] - public void ScaledCopyTo(Span dest) - { - ref byte d = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - ref byte s = ref Unsafe.As(ref this); - - Unsafe.CopyBlock(ref d, ref s, Size * sizeof(float)); - } - - /// - /// Convert scalars to byte-s and copy to dest, - /// - /// Pointer to block - /// Destination - [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) - { - float* fPtr = (float*)blockPtr; - for (int i = 0; i < Size; i++) - { - dest[i] = (byte)*fPtr; - fPtr++; - } - } - - /// - /// Copy raw 32bit floating point data to dest. - /// - /// The block pointer. - /// The destination. - [MethodImpl(InliningOptions.ShortMethod)] - public static unsafe void ScaledCopyTo(Block8x8F* blockPtr, Span dest) - => blockPtr->ScaledCopyTo(dest); - /// /// Copy raw 32bit floating point data to dest /// @@ -253,22 +153,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } - /// - /// Copy raw 32bit floating point data to dest - /// - /// Destination - public unsafe void ScaledCopyTo(Span dest) - { - fixed (Vector4* ptr = &this.V0L) - { - var fp = (float*)ptr; - for (int i = 0; i < Size; i++) - { - dest[i] = (int)fp[i]; - } - } - } - public float[] ToArray() { float[] result = new float[Size]; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index 8f5f10f19..ae7e81254 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -114,56 +114,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // PrintLinearData((Span)mirror); } - [Fact] - public unsafe void Load_Store_FloatArray_Ptr() - { - float[] data = new float[Block8x8F.Size]; - float[] mirror = new float[Block8x8F.Size]; - - for (int i = 0; i < Block8x8F.Size; i++) - { - data[i] = i; - } - - this.Measure( - Times, - () => - { - var b = default(Block8x8F); - Block8x8F.LoadFrom(&b, data); - Block8x8F.ScaledCopyTo(&b, mirror); - }); - - Assert.Equal(data, mirror); - - // PrintLinearData((Span)mirror); - } - - [Fact] - public void Load_Store_IntArray() - { - int[] data = new int[Block8x8F.Size]; - int[] mirror = new int[Block8x8F.Size]; - - for (int i = 0; i < Block8x8F.Size; i++) - { - data[i] = i; - } - - this.Measure( - Times, - () => - { - var v = default(Block8x8F); - v.LoadFrom(data); - v.ScaledCopyTo(mirror); - }); - - Assert.Equal(data, mirror); - - // PrintLinearData((Span)mirror); - } - [Fact] public void TransposeInplace() { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs index 8920d42d4..9eb3c0103 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.FastFloatingPointDCT.cs @@ -94,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); Block8x8F actual = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformFDCT_UpscaleBy8(ref source); - actual /= 8; + actual.MultiplyInPlace(0.125f); this.CompareBlocks(expected, actual, 1f); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs index 6e25334f2..02f8ba388 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.StandardIntegerDCT.cs @@ -51,11 +51,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformFDCT(ref source); - source += 128; + source.AddInPlace(128f); Block8x8 temp = source.RoundAsInt16Block(); Block8x8 actual8 = ReferenceImplementations.StandardIntegerDCT.Subtract128_TransformFDCT_Upscale8(ref temp); Block8x8F actual = actual8.AsFloatBlock(); - actual /= 8; + actual.MultiplyInPlace(0.125f); this.CompareBlocks(expected, actual, 1f); } From a02d88a70fab9c67a45f52588e230681e02e0864 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 19:13:41 +0100 Subject: [PATCH 070/228] address some unrelated coverage issues reported by CodeCov for some reason --- tests/ImageSharp.Tests/Image/ImageTests.cs | 22 +++++++++++++++++++ .../DiscontiguousBuffers/MemoryGroupTests.cs | 8 +++++++ 2 files changed, 30 insertions(+) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 7b6787529..2015f8e38 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -5,7 +5,9 @@ using System; using System.IO; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; @@ -324,5 +326,25 @@ namespace SixLabors.ImageSharp.Tests Assert.Throws(() => { var res = genericImage.CloneAs(this.configuration); }); } } + + public class DetectEncoder + { + [Fact] + public void KnownExtension_ReturnsEncoder() + { + using var image = new Image(1, 1); + IImageEncoder encoder = image.DetectEncoder("dummy.png"); + Assert.NotNull(encoder); + Assert.IsType(encoder); + } + + [Fact] + public void UnknownExtension_ReturnsNull() + { + using var image = new Image(1, 1); + IImageEncoder encoder = image.DetectEncoder("dummy.yolo"); + Assert.Null(encoder); + } + } } } diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index a93dbbeb3..13e47bdee 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -119,6 +119,14 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers Assert.True(group[0].Span.SequenceEqual(data0)); Assert.True(group[1].Span.SequenceEqual(data1)); Assert.True(group[2].Span.SequenceEqual(data2)); + + int cnt = 0; + int[][] allData = { data0, data1, data2 }; + foreach (Memory memory in group) + { + Assert.True(memory.Span.SequenceEqual(allData[cnt])); + cnt++; + } } public static TheoryData GetBoundedSlice_SuccessData = new TheoryData() From a3a6d1d93e0c12ba4f8a816dbc73408f468ede5f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 19:55:12 +0100 Subject: [PATCH 071/228] Always disable MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed on Mac. --- .../Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index ed0bed838..84d64c757 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -47,11 +47,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - public static readonly bool MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed_Enabled = - !(TestEnvironment.IsOSX && TestEnvironment.NetCoreVersion?.Major == 6); + public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; - // TODO: Investigate failure on MacOS + .net 6.0. All handles are released after GC. - [ConditionalFact(nameof(MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed_Enabled))] + // TODO: Investigate failures on MacOS. All handles are released after GC. + // (It seems to happen more consistently on .NET 6.) + [ConditionalFact(nameof(IsNotMacOs))] public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { RemoteExecutor.Invoke(RunTest).Dispose(); From 7ec7447808725f761dc7cba83fea676c4420158f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 20:29:29 +0100 Subject: [PATCH 072/228] fix DetectEncoder tests --- tests/ImageSharp.Tests/Image/ImageTests.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 2015f8e38..0a9e2817a 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -339,11 +339,22 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void UnknownExtension_ReturnsNull() + public void UnknownExtension_ThrowsNotSupportedException() { using var image = new Image(1, 1); - IImageEncoder encoder = image.DetectEncoder("dummy.yolo"); - Assert.Null(encoder); + Assert.Throws(() => image.DetectEncoder("dummy.yolo")); + } + + [Fact] + public void NoDetectorRegisteredForKnownExtension_ThrowsNotSupportedException() + { + var configuration = new Configuration(); + var format = new TestFormat(); + configuration.ImageFormatsManager.AddImageFormat(format); + configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format)); + + using var image = new Image(configuration, 1, 1); + Assert.Throws(() => image.DetectEncoder($"dummy.{format.Extension}")); } } } From b87362d91f209689e21e5b3de897fc79bae0670b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 25 Nov 2021 21:40:51 +0100 Subject: [PATCH 073/228] Fix style warning in BinaryDecoder --- src/ImageSharp/Formats/Pbm/BinaryDecoder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs index 1c86b2bd8..c7a09a613 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -176,6 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { stream.Seek(-1, System.IO.SeekOrigin.Current); } + break; } } From 5939bf8ab1dd01d37c80c22fdb39b1036df1af68 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 25 Nov 2021 21:44:01 +0100 Subject: [PATCH 074/228] Restore LangVersion --- src/ImageSharp/ImageSharp.csproj | 1 - tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj | 1 - .../ImageSharp.Tests.ProfilingSandbox.csproj | 1 - tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 1 - 4 files changed, 4 deletions(-) diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index a0a45e8aa..6eed09b39 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -39,7 +39,6 @@ netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 - 9.0 diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj index 4d7af89a5..8f0b4a86f 100644 --- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj +++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj @@ -28,7 +28,6 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 - 9.0 diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj index a141a58b0..1a470fa31 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj +++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj @@ -30,7 +30,6 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 - 9.0 diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index c560b1b78..471287006 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -23,7 +23,6 @@ net5.0;netcoreapp3.1;netcoreapp2.1;net472 - 9.0 From 98f5a428169e9e31b903ba3252bd00223945ab7f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 25 Nov 2021 22:49:29 +0100 Subject: [PATCH 075/228] Add SSE2 version off FTransform --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 217 +++++++++++++++--- 1 file changed, 185 insertions(+), 32 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index aa4ab5767..143d9f17e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Methods for encoding a VP8 frame. /// - internal static class Vp8Encoding + internal static unsafe class Vp8Encoding { private const int KC1 = 20091 + (1 << 16); @@ -382,43 +382,196 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void FTransform(Span src, Span reference, Span output, Span scratch) { - int i; - Span tmp = scratch.Slice(0, 16); - - int srcIdx = 0; - int refIdx = 0; - for (i = 0; i < 4; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) - int d1 = src[srcIdx + 1] - reference[refIdx + 1]; - int d2 = src[srcIdx + 2] - reference[refIdx + 2]; - int d3 = src[srcIdx + 3] - reference[refIdx + 3]; - int a0 = d0 + d3; // 10b [-510,510] - int a1 = d1 + d2; - int a2 = d1 - d2; - int a3 = d0 - d3; - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[2 + (i * 4)] = (a0 - a1) * 8; - tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; - - srcIdx += WebpConstants.Bps; - refIdx += WebpConstants.Bps; +#pragma warning disable SA1503 // Braces should not be omitted + fixed (byte* srcRef = src) + fixed (byte* referenceRef = reference) + { + // Load src. + Vector128 src0 = Sse2.LoadScalarVector128((ulong*)srcRef); + Vector128 src1 = Sse2.LoadScalarVector128((ulong*)(srcRef + WebpConstants.Bps)); + Vector128 src2 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 2))); + Vector128 src3 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 3))); + + // Load ref. + Vector128 ref0 = Sse2.LoadScalarVector128((ulong*)referenceRef); + Vector128 ref1 = Sse2.LoadScalarVector128((ulong*)(referenceRef + WebpConstants.Bps)); + Vector128 ref2 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (WebpConstants.Bps * 2))); + Vector128 ref3 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (+WebpConstants.Bps * 3))); + + // 00 01 02 03 * + // 10 11 12 13 * + // 20 21 22 23 * + // 30 31 32 33 * + // Shuffle. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsInt16(), src1.AsInt16()); + Vector128 srcLow1 = Sse2.UnpackLow(src2.AsInt16(), src3.AsInt16()); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsInt16(), ref1.AsInt16()); + Vector128 refLow1 = Sse2.UnpackLow(ref2.AsInt16(), ref3.AsInt16()); + + // 00 01 10 11 02 03 12 13 * * ... + // 20 21 30 31 22 22 32 33 * * ... + + // Convert both to 16 bit. + Vector128 src0_16b = Sse2.UnpackLow(srcLow0.AsByte(), Vector128.Zero); + Vector128 src1_16b = Sse2.UnpackLow(srcLow1.AsByte(), Vector128.Zero); + Vector128 ref0_16b = Sse2.UnpackLow(refLow0.AsByte(), Vector128.Zero); + Vector128 ref1_16b = Sse2.UnpackLow(refLow1.AsByte(), Vector128.Zero); + + // Compute the difference. + Vector128 row01 = Sse2.Subtract(src0_16b.AsInt16(), ref0_16b.AsInt16()); + Vector128 row23 = Sse2.Subtract(src1_16b.AsInt16(), ref1_16b.AsInt16()); + + // First pass + FTransformPass1SSE2(row01, row23, out Vector128 v01, out Vector128 v32); + + // Second pass + FTransformPass2SSE2(v01, v32, output); + } +#pragma warning restore SA1503 // Braces should not be omitted } - - for (i = 0; i < 4; i++) + else +#endif { - int a0 = tmp[0 + i] + tmp[12 + i]; // 15b - int a1 = tmp[4 + i] + tmp[8 + i]; - int a2 = tmp[4 + i] - tmp[8 + i]; - int a3 = tmp[0 + i] - tmp[12 + i]; - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); - output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + int i; + Span tmp = scratch.Slice(0, 16); + + int srcIdx = 0; + int refIdx = 0; + for (i = 0; i < 4; i++) + { + int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) + int d1 = src[srcIdx + 1] - reference[refIdx + 1]; + int d2 = src[srcIdx + 2] - reference[refIdx + 2]; + int d3 = src[srcIdx + 3] - reference[refIdx + 3]; + int a0 = d0 + d3; // 10b [-510,510] + int a1 = d1 + d2; + int a2 = d1 - d2; + int a3 = d0 - d3; + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + + srcIdx += WebpConstants.Bps; + refIdx += WebpConstants.Bps; + } + + for (i = 0; i < 4; i++) + { + int a0 = tmp[0 + i] + tmp[12 + i]; // 15b + int a1 = tmp[4 + i] + tmp[8 + i]; + int a2 = tmp[4 + i] - tmp[8 + i]; + int a3 = tmp[0 + i] - tmp[12 + i]; + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + } } } +#if SUPPORTS_RUNTIME_INTRINSICS + public static void FTransformPass1SSE2(Vector128 row01, Vector128 row23, out Vector128 out01, out Vector128 out32) + { + var k937 = Vector128.Create(937); + var k1812 = Vector128.Create(1812); + Vector128 k88p = Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16(); + Vector128 k88m = Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16(); + Vector128 k5352_2217p = Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16(); + Vector128 k5352_2217m = Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16(); + + // *in01 = 00 01 10 11 02 03 12 13 + // *in23 = 20 21 30 31 22 23 32 33 + Vector128 shuf01_p = Sse2.ShuffleHigh(row01.AsInt16(), SimdUtils.Shuffle.MmShuffle(2, 3, 0, 1)); + Vector128 shuf32_p = Sse2.ShuffleHigh(row23.AsInt16(), SimdUtils.Shuffle.MmShuffle(2, 3, 0, 1)); + + // 00 01 10 11 03 02 13 12 + // 20 21 30 31 23 22 33 32 + Vector128 s01 = Sse2.UnpackLow(shuf01_p.AsInt64(), shuf32_p.AsInt64()); + Vector128 s32 = Sse2.UnpackHigh(shuf01_p.AsInt64(), shuf32_p.AsInt64()); + + // 00 01 10 11 20 21 30 31 + // 03 02 13 12 23 22 33 32 + Vector128 a01 = Sse2.Add(s01.AsInt16(), s32.AsInt16()); + Vector128 a32 = Sse2.Subtract(s01.AsInt16(), s32.AsInt16()); + + // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] + // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] + Vector128 tmp0 = Sse2.MultiplyAddAdjacent(a01, k88p); // [ (a0 + a1) << 3, ... ] + Vector128 tmp2 = Sse2.MultiplyAddAdjacent(a01, k88m); // [ (a0 - a1) << 3, ... ] + Vector128 tmp11 = Sse2.MultiplyAddAdjacent(a32, k5352_2217p); + Vector128 tmp31 = Sse2.MultiplyAddAdjacent(a32, k5352_2217m); + Vector128 tmp12 = Sse2.Add(tmp11, k1812); + Vector128 tmp32 = Sse2.Add(tmp31, k937); + Vector128 tmp1 = Sse2.ShiftRightArithmetic(tmp12, 9); + Vector128 tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9); + Vector128 s03 = Sse2.PackSignedSaturate(tmp0, tmp2); + Vector128 s12 = Sse2.PackSignedSaturate(tmp1, tmp3); + Vector128 slo = Sse2.UnpackLow(s03, s12); // 0 1 0 1 0 1... + Vector128 shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3 + Vector128 v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32()); + out01 = Sse2.UnpackLow(slo.AsInt32(), shi.AsInt32()); + out32 = Sse2.Shuffle(v23, SimdUtils.Shuffle.MmShuffle(1, 0, 3, 2)); + } + + public static void FTransformPass2SSE2(Vector128 v01, Vector128 v32, Span output) + { + var seven = Vector128.Create((short)7); + Vector128 k5352_2217 = Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16(); + Vector128 k2217_5352 = Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16(); + var k12000PlusOne = Vector128.Create(12000 + (1 << 16)); + var k51000 = Vector128.Create(51000); + + // Same operations are done on the (0,3) and (1,2) pairs. + // a3 = v0 - v3 + // a2 = v1 - v2 + Vector128 a32 = Sse2.Subtract(v01.AsInt16(), v32.AsInt16()); + Vector128 a22 = Sse2.UnpackHigh(a32.AsInt64(), a32.AsInt64()); + + Vector128 b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16()); + Vector128 c1 = Sse2.MultiplyAddAdjacent(b23, k5352_2217); + Vector128 c3 = Sse2.MultiplyAddAdjacent(b23, k2217_5352); + Vector128 d1 = Sse2.Add(c1, k12000PlusOne); + Vector128 d3 = Sse2.Add(c3, k51000); + Vector128 e1 = Sse2.ShiftRightArithmetic(d1, 16); + Vector128 e3 = Sse2.ShiftRightArithmetic(d3, 16); + + // f1 = ((b3 * 5352 + b2 * 2217 + 12000) >> 16) + // f3 = ((b3 * 2217 - b2 * 5352 + 51000) >> 16) + Vector128 f1 = Sse2.PackSignedSaturate(e1, e1); + Vector128 f3 = Sse2.PackSignedSaturate(e3, e3); + + // g1 = f1 + (a3 != 0); + // The compare will return (0xffff, 0) for (==0, !=0). To turn that into the + // desired (0, 1), we add one earlier through k12000_plus_one. + // -> g1 = f1 + 1 - (a3 == 0) + Vector128 g1 = Sse2.Add(f1, Sse2.CompareEqual(a32, Vector128.Zero)); + + // a0 = v0 + v3 + // a1 = v1 + v2 + Vector128 a01 = Sse2.Add(v01, v32); + Vector128 a01Plus7 = Sse2.Add(a01.AsInt16(), seven); + Vector128 a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16(); + Vector128 c0 = Sse2.Add(a01Plus7, a11); + Vector128 c2 = Sse2.Subtract(a01Plus7, a11); + + // d0 = (a0 + a1 + 7) >> 4; + // d2 = (a0 - a1 + 7) >> 4; + Vector128 d0 = Sse2.ShiftRightArithmetic(c0, 4); + Vector128 d2 = Sse2.ShiftRightArithmetic(c2, 4); + + Vector128 d0g1 = Sse2.UnpackLow(d0.AsInt64(), g1.AsInt64()); + Vector128 d2f3 = Sse2.UnpackLow(d2.AsInt64(), f3.AsInt64()); + + ref short outputRef = ref MemoryMarshal.GetReference(output); + Unsafe.As>(ref outputRef) = d0g1.AsInt16(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)) = d2f3.AsInt16(); + } +#endif + public static void FTransformWht(Span input, Span output, Span scratch) { Span tmp = scratch.Slice(0, 16); From 60fd3b0659c6ba464d59eefe0cb09d084397fe0d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 26 Nov 2021 06:51:42 +0300 Subject: [PATCH 076/228] Removed GenericBlock8x8 --- .../Components/Encoder/HuffmanScanEncoder.cs | 5 +- .../LuminanceForwardConverter{TPixel}.cs | 49 +++++-- .../Components/GenericBlock8x8.Generated.cs | 23 ---- .../Components/GenericBlock8x8.Generated.tt | 0 .../Jpeg/Components/GenericBlock8x8.cs | 122 ----------------- .../Formats/Jpg/GenericBlock8x8Tests.cs | 129 ------------------ 6 files changed, 40 insertions(+), 288 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs delete mode 100644 tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index b3cdbf0a0..d71a62ec9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -278,11 +278,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // ReSharper disable once InconsistentNaming int prevDCY = 0; - var pixelConverter = LuminanceForwardConverter.Create(); ImageFrame frame = pixels.Frames.RootFrame; Buffer2D pixelBuffer = frame.PixelBuffer; RowOctet currentRows = default; + var pixelConverter = new LuminanceForwardConverter(frame); + for (int y = 0; y < pixels.Height; y += 8) { cancellationToken.ThrowIfCancellationRequested(); @@ -290,7 +291,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder for (int x = 0; x < pixels.Width; x += 8) { - pixelConverter.Convert(frame, x, y, ref currentRows); + pixelConverter.Convert(x, y, ref currentRows); prevDCY = this.WriteBlock( QuantIndex.Luminance, diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs index fc5b9a868..b6edfefed 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; @@ -15,39 +16,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder internal ref struct LuminanceForwardConverter where TPixel : unmanaged, IPixel { + /// + /// Number of pixels processed per single call + /// + private const int PixelsPerSample = 8 * 8; + + /// + /// of sampling area from given frame pixel buffer. + /// + private static readonly Size SampleSize = new Size(8, 8); + /// /// The Y component /// public Block8x8F Y; /// - /// Temporal 8x8 block to hold TPixel data + /// Temporal 64-pixel span to hold unconverted TPixel data. + /// + private readonly Span pixelSpan; + + /// + /// Temporal 64-byte span to hold converted data. /// - private GenericBlock8x8 pixelBlock; + private readonly Span l8Span; /// - /// Temporal RGB block + /// Sampled pixel buffer size. /// - private GenericBlock8x8 l8Block; + private readonly Size samplingAreaSize; - public static LuminanceForwardConverter Create() + /// + /// for internal operations. + /// + private readonly Configuration config; + + public LuminanceForwardConverter(ImageFrame frame) { - var result = default(LuminanceForwardConverter); - return result; + this.Y = default; + + this.pixelSpan = new TPixel[PixelsPerSample].AsSpan(); + this.l8Span = new L8[PixelsPerSample].AsSpan(); + + this.samplingAreaSize = new Size(frame.Width, frame.Height); + this.config = frame.GetConfiguration(); } /// /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure () /// - public void Convert(ImageFrame frame, int x, int y, ref RowOctet currentRows) + public void Convert(int x, int y, ref RowOctet currentRows) { - this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows); + YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); - Span l8Span = this.l8Block.AsSpanUnsafe(); - PixelOperations.Instance.ToL8(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), l8Span); + PixelOperations.Instance.ToL8(this.config, this.pixelSpan, this.l8Span); ref Block8x8F yBlock = ref this.Y; - ref L8 l8Start = ref l8Span[0]; + ref L8 l8Start = ref MemoryMarshal.GetReference(this.l8Span); for (int i = 0; i < Block8x8F.Size; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs deleted file mode 100644 index 213c48ff3..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -// -namespace SixLabors.ImageSharp.Formats.Jpeg.Components -{ - internal unsafe partial struct GenericBlock8x8 - { - #pragma warning disable 169 - - // It's not allowed use fix-sized buffers with generics, need to place all the fields manually: - private T _y0_x0, _y0_x1, _y0_x2, _y0_x3, _y0_x4, _y0_x5, _y0_x6, _y0_x7; - private T _y1_x0, _y1_x1, _y1_x2, _y1_x3, _y1_x4, _y1_x5, _y1_x6, _y1_x7; - private T _y2_x0, _y2_x1, _y2_x2, _y2_x3, _y2_x4, _y2_x5, _y2_x6, _y2_x7; - private T _y3_x0, _y3_x1, _y3_x2, _y3_x3, _y3_x4, _y3_x5, _y3_x6, _y3_x7; - private T _y4_x0, _y4_x1, _y4_x2, _y4_x3, _y4_x4, _y4_x5, _y4_x6, _y4_x7; - private T _y5_x0, _y5_x1, _y5_x2, _y5_x3, _y5_x4, _y5_x5, _y5_x6, _y5_x7; - private T _y6_x0, _y6_x1, _y6_x2, _y6_x3, _y6_x4, _y6_x5, _y6_x6, _y6_x7; - private T _y7_x0, _y7_x1, _y7_x2, _y7_x3, _y7_x4, _y7_x5, _y7_x6, _y7_x7; - - #pragma warning restore 169 - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.Generated.tt deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs deleted file mode 100644 index 42c01d770..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/GenericBlock8x8.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -// ReSharper disable InconsistentNaming -namespace SixLabors.ImageSharp.Formats.Jpeg.Components -{ - /// - /// A generic 8x8 block implementation, useful for manipulating custom 8x8 pixel data. - /// - [StructLayout(LayoutKind.Sequential)] - internal unsafe partial struct GenericBlock8x8 - where T : unmanaged - { - public const int Size = 64; - - /// - /// FOR TESTING ONLY! - /// Gets or sets a value at the given index - /// - /// The index - /// The value - public T this[int idx] - { - get - { - ref T selfRef = ref Unsafe.As, T>(ref this); - return Unsafe.Add(ref selfRef, idx); - } - - set - { - ref T selfRef = ref Unsafe.As, T>(ref this); - Unsafe.Add(ref selfRef, idx) = value; - } - } - - /// - /// FOR TESTING ONLY! - /// Gets or sets a value in a row+column of the 8x8 block - /// - /// The x position index in the row - /// The column index - /// The value - public T this[int x, int y] - { - get => this[(y * 8) + x]; - set => this[(y * 8) + x] = value; - } - - /// - /// Load a 8x8 region of an image into the block. - /// The "outlying" area of the block will be stretched out with pixels on the right and bottom edge of the image. - /// - public void LoadAndStretchEdges(Buffer2D source, int sourceX, int sourceY, ref RowOctet currentRows) - { - int width = Math.Min(8, source.Width - sourceX); - int height = Math.Min(8, source.Height - sourceY); - - if (width <= 0 || height <= 0) - { - return; - } - - uint byteWidth = (uint)width * (uint)Unsafe.SizeOf(); - int remainderXCount = 8 - width; - - ref byte blockStart = ref Unsafe.As, byte>(ref this); - int blockRowSizeInBytes = 8 * Unsafe.SizeOf(); - - for (int y = 0; y < height; y++) - { - Span row = currentRows[y]; - - ref byte s = ref Unsafe.As(ref row[sourceX]); - ref byte d = ref Unsafe.Add(ref blockStart, y * blockRowSizeInBytes); - - Unsafe.CopyBlock(ref d, ref s, byteWidth); - - ref T last = ref Unsafe.Add(ref Unsafe.As(ref d), width - 1); - - for (int x = 1; x <= remainderXCount; x++) - { - Unsafe.Add(ref last, x) = last; - } - } - - int remainderYCount = 8 - height; - - if (remainderYCount == 0) - { - return; - } - - ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * blockRowSizeInBytes); - - for (int y = 1; y <= remainderYCount; y++) - { - ref byte remStart = ref Unsafe.Add(ref lastRowStart, blockRowSizeInBytes * y); - Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)blockRowSizeInBytes); - } - } - - /// - /// Only for on-stack instances! - /// - public Span AsSpanUnsafe() - { -#if SUPPORTS_CREATESPAN - Span> s = MemoryMarshal.CreateSpan(ref this, 1); - return MemoryMarshal.Cast, T>(s); -#else - return new Span(Unsafe.AsPointer(ref this), Size); -#endif - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs deleted file mode 100644 index d5ee2a2b8..000000000 --- a/tests/ImageSharp.Tests/Formats/Jpg/GenericBlock8x8Tests.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Formats.Jpeg.Components; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Jpg -{ - [Trait("Format", "Jpg")] - public class GenericBlock8x8Tests - { - public static Image CreateTestImage() - where TPixel : unmanaged, IPixel - { - var image = new Image(10, 10); - Buffer2D pixels = image.GetRootFramePixelBuffer(); - for (int i = 0; i < 10; i++) - { - for (int j = 0; j < 10; j++) - { - var rgba = new Rgba32((byte)(i + 1), (byte)(j + 1), 200, 255); - var color = default(TPixel); - color.FromRgba32(rgba); - - pixels[i, j] = color; - } - } - - return image; - } - - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32 /* | PixelTypes.Rgba32 | PixelTypes.Argb32*/)] - public void LoadAndStretchCorners_FromOrigo(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image s = provider.GetImage()) - { - var d = default(GenericBlock8x8); - RowOctet rowOctet = default; - rowOctet.Update(s.GetRootFramePixelBuffer(), 0); - d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 0, 0, ref rowOctet); - - TPixel a = s.Frames.RootFrame[0, 0]; - TPixel b = d[0, 0]; - - Assert.Equal(s[0, 0], d[0, 0]); - Assert.Equal(s[1, 0], d[1, 0]); - Assert.Equal(s[7, 0], d[7, 0]); - Assert.Equal(s[0, 1], d[0, 1]); - Assert.Equal(s[1, 1], d[1, 1]); - Assert.Equal(s[7, 0], d[7, 0]); - Assert.Equal(s[0, 7], d[0, 7]); - Assert.Equal(s[7, 7], d[7, 7]); - } - } - - [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgb24 | PixelTypes.Rgba32)] - public void LoadAndStretchCorners_WithOffset(TestImageProvider provider) - where TPixel : unmanaged, IPixel - { - using (Image s = provider.GetImage()) - { - var d = default(GenericBlock8x8); - RowOctet rowOctet = default; - rowOctet.Update(s.GetRootFramePixelBuffer(), 7); - - d.LoadAndStretchEdges(s.Frames.RootFrame.PixelBuffer, 6, 7, ref rowOctet); - - Assert.Equal(s[6, 7], d[0, 0]); - Assert.Equal(s[6, 8], d[0, 1]); - Assert.Equal(s[7, 8], d[1, 1]); - - Assert.Equal(s[6, 9], d[0, 2]); - Assert.Equal(s[6, 9], d[0, 3]); - Assert.Equal(s[6, 9], d[0, 7]); - - Assert.Equal(s[7, 9], d[1, 2]); - Assert.Equal(s[7, 9], d[1, 3]); - Assert.Equal(s[7, 9], d[1, 7]); - - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[3, 3]); - Assert.Equal(s[9, 9], d[3, 7]); - - Assert.Equal(s[9, 7], d[3, 0]); - Assert.Equal(s[9, 7], d[4, 0]); - Assert.Equal(s[9, 7], d[7, 0]); - - Assert.Equal(s[9, 9], d[3, 2]); - Assert.Equal(s[9, 9], d[4, 2]); - Assert.Equal(s[9, 9], d[7, 2]); - - Assert.Equal(s[9, 9], d[4, 3]); - Assert.Equal(s[9, 9], d[7, 7]); - } - } - - [Fact] - public void Indexer() - { - var block = default(GenericBlock8x8); - Span span = block.AsSpanUnsafe(); - Assert.Equal(64, span.Length); - - for (int i = 0; i < 64; i++) - { - span[i] = new Rgb24((byte)i, (byte)(2 * i), (byte)(3 * i)); - } - - var expected00 = new Rgb24(0, 0, 0); - var expected07 = new Rgb24(7, 14, 21); - var expected11 = new Rgb24(9, 18, 27); - var expected77 = new Rgb24(63, 126, 189); - var expected67 = new Rgb24(62, 124, 186); - - Assert.Equal(expected00, block[0, 0]); - Assert.Equal(expected07, block[7, 0]); - Assert.Equal(expected11, block[1, 1]); - Assert.Equal(expected67, block[6, 7]); - Assert.Equal(expected77, block[7, 7]); - } - } -} From 525cfc89aee92a5d2b939188b0141bae4d1b9223 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 26 Nov 2021 07:13:23 +0300 Subject: [PATCH 077/228] Removed redundant/unused code from Block8x8 --- .../Formats/Jpeg/Components/Block8x8.cs | 68 ------------------- 1 file changed, 68 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 27bb2fc3c..5d669ff8a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -102,74 +102,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components set => this[(y * 8) + x] = value; } - public static bool operator ==(Block8x8 left, Block8x8 right) => left.Equals(right); - - public static bool operator !=(Block8x8 left, Block8x8 right) => !left.Equals(right); - - /// - /// Multiply all elements by a given - /// - public static Block8x8 operator *(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val *= value; - result[i] = (short)val; - } - - return result; - } - - /// - /// Divide all elements by a given - /// - public static Block8x8 operator /(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val /= value; - result[i] = (short)val; - } - - return result; - } - - /// - /// Add an to all elements - /// - public static Block8x8 operator +(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val += value; - result[i] = (short)val; - } - - return result; - } - - /// - /// Subtract an from all elements - /// - public static Block8x8 operator -(Block8x8 block, int value) - { - Block8x8 result = block; - for (int i = 0; i < Size; i++) - { - int val = result[i]; - val -= value; - result[i] = (short)val; - } - - return result; - } - public static Block8x8 Load(Span data) { Unsafe.SkipInit(out Block8x8 result); From 9c3d348b4d5fad512c4aeb03aa8c3558c191eb25 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 26 Nov 2021 08:15:19 +0300 Subject: [PATCH 078/228] Separated Block8x8 into scalar and intrinsic code bases --- .../Jpeg/Components/Block8x8.Intrinsic.cs | 43 +++++++++++++++++++ .../Formats/Jpeg/Components/Block8x8.cs | 30 +------------ src/ImageSharp/ImageSharp.csproj | 9 ---- 3 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs new file mode 100644 index 000000000..8265ad589 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs @@ -0,0 +1,43 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +#if SUPPORTS_RUNTIME_INTRINSICS +using System; +using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +#endif + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components +{ + + internal unsafe partial struct Block8x8 : IEquatable + { + [FieldOffset(0)] + public Vector128 V0; + [FieldOffset(16)] + public Vector128 V1; + [FieldOffset(32)] + public Vector128 V2; + [FieldOffset(48)] + public Vector128 V3; + [FieldOffset(64)] + public Vector128 V4; + [FieldOffset(80)] + public Vector128 V5; + [FieldOffset(96)] + public Vector128 V6; + [FieldOffset(112)] + public Vector128 V7; + + [FieldOffset(0)] + public Vector256 V01; + [FieldOffset(32)] + public Vector256 V23; + [FieldOffset(64)] + public Vector256 V45; + [FieldOffset(96)] + public Vector256 V67; + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 5d669ff8a..e0d300eb5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// // ReSharper disable once InconsistentNaming [StructLayout(LayoutKind.Explicit)] - internal unsafe struct Block8x8 : IEquatable + internal unsafe partial struct Block8x8 : IEquatable { /// /// A number of scalar coefficients in a @@ -36,34 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components private fixed short data[Size]; #pragma warning restore IDE0051 -#if SUPPORTS_RUNTIME_INTRINSICS - [FieldOffset(0)] - public Vector128 V0; - [FieldOffset(16)] - public Vector128 V1; - [FieldOffset(32)] - public Vector128 V2; - [FieldOffset(48)] - public Vector128 V3; - [FieldOffset(64)] - public Vector128 V4; - [FieldOffset(80)] - public Vector128 V5; - [FieldOffset(96)] - public Vector128 V6; - [FieldOffset(112)] - public Vector128 V7; - - [FieldOffset(0)] - public Vector256 V01; - [FieldOffset(32)] - public Vector256 V23; - [FieldOffset(64)] - public Vector256 V45; - [FieldOffset(96)] - public Vector256 V67; -#endif - /// /// Gets or sets a value at the given index /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 6ad20713d..f90e40edb 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -70,11 +70,6 @@ True Block8x8F.Generated.tt - - True - True - GenericBlock8x8.Generated.tt - True True @@ -167,10 +162,6 @@ TextTemplatingFileGenerator Block8x8F.Generated.cs - - TextTemplatingFileGenerator - GenericBlock8x8.Generated.cs - TextTemplatingFileGenerator Block8x8F.Generated.cs From 4bb56eea71a6e3e909d3fcd2255f633a1007c643 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 26 Nov 2021 12:50:55 +0100 Subject: [PATCH 079/228] Define mask and shuffle vectors as static readonly --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 75 +++++++++++-------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 143d9f17e..a3a9c924c 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -66,11 +66,39 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static readonly int[] Vp8I4ModeOffsets = { I4DC4, I4TM4, I4VE4, I4HE4, I4RD4, I4VR4, I4LD4, I4VL4, I4HD4, I4HU4 }; #if SUPPORTS_RUNTIME_INTRINSICS - public static readonly Vector128 K1 = Vector128.Create((short)20091).AsInt16(); +#pragma warning disable SA1310 // Field names should not contain underscore + private static readonly Vector128 K1 = Vector128.Create((short)20091).AsInt16(); - public static readonly Vector128 K2 = Vector128.Create((short)-30068).AsInt16(); + private static readonly Vector128 K2 = Vector128.Create((short)-30068).AsInt16(); - public static readonly Vector128 Four = Vector128.Create((short)4); + private static readonly Vector128 Four = Vector128.Create((short)4); + + private static readonly Vector128 Seven = Vector128.Create((short)7); + + private static readonly Vector128 K88p = Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16(); + + private static readonly Vector128 K88m = Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16(); + + private static readonly Vector128 K5352_2217p = Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16(); + + private static readonly Vector128 K5352_2217m = Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16(); + + private static readonly Vector128 K937 = Vector128.Create(937); + + private static readonly Vector128 K1812 = Vector128.Create(1812); + + private static readonly Vector128 K5352_2217 = Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16(); + + private static readonly Vector128 K2217_5352 = Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16(); + + private static readonly Vector128 K12000PlusOne = Vector128.Create(12000 + (1 << 16)); + + private static readonly Vector128 K51000 = Vector128.Create(51000); + + private static readonly byte MmShuffle2301 = SimdUtils.Shuffle.MmShuffle(2, 3, 0, 1); + + private static readonly byte MmShuffle1032 = SimdUtils.Shuffle.MmShuffle(1, 0, 3, 2); +#pragma warning restore SA1310 // Field names should not contain underscore #endif static Vp8Encoding() @@ -476,17 +504,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS public static void FTransformPass1SSE2(Vector128 row01, Vector128 row23, out Vector128 out01, out Vector128 out32) { - var k937 = Vector128.Create(937); - var k1812 = Vector128.Create(1812); - Vector128 k88p = Vector128.Create(8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0, 8, 0).AsInt16(); - Vector128 k88m = Vector128.Create(8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255, 8, 0, 248, 255).AsInt16(); - Vector128 k5352_2217p = Vector128.Create(232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8).AsInt16(); - Vector128 k5352_2217m = Vector128.Create(169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235).AsInt16(); - // *in01 = 00 01 10 11 02 03 12 13 // *in23 = 20 21 30 31 22 23 32 33 - Vector128 shuf01_p = Sse2.ShuffleHigh(row01.AsInt16(), SimdUtils.Shuffle.MmShuffle(2, 3, 0, 1)); - Vector128 shuf32_p = Sse2.ShuffleHigh(row23.AsInt16(), SimdUtils.Shuffle.MmShuffle(2, 3, 0, 1)); + Vector128 shuf01_p = Sse2.ShuffleHigh(row01, MmShuffle2301); + Vector128 shuf32_p = Sse2.ShuffleHigh(row23, MmShuffle2301); // 00 01 10 11 03 02 13 12 // 20 21 30 31 23 22 33 32 @@ -500,12 +521,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // [d0 + d3 | d1 + d2 | ...] = [a0 a1 | a0' a1' | ... ] // [d0 - d3 | d1 - d2 | ...] = [a3 a2 | a3' a2' | ... ] - Vector128 tmp0 = Sse2.MultiplyAddAdjacent(a01, k88p); // [ (a0 + a1) << 3, ... ] - Vector128 tmp2 = Sse2.MultiplyAddAdjacent(a01, k88m); // [ (a0 - a1) << 3, ... ] - Vector128 tmp11 = Sse2.MultiplyAddAdjacent(a32, k5352_2217p); - Vector128 tmp31 = Sse2.MultiplyAddAdjacent(a32, k5352_2217m); - Vector128 tmp12 = Sse2.Add(tmp11, k1812); - Vector128 tmp32 = Sse2.Add(tmp31, k937); + Vector128 tmp0 = Sse2.MultiplyAddAdjacent(a01, K88p); // [ (a0 + a1) << 3, ... ] + Vector128 tmp2 = Sse2.MultiplyAddAdjacent(a01, K88m); // [ (a0 - a1) << 3, ... ] + Vector128 tmp11 = Sse2.MultiplyAddAdjacent(a32, K5352_2217p); + Vector128 tmp31 = Sse2.MultiplyAddAdjacent(a32, K5352_2217m); + Vector128 tmp12 = Sse2.Add(tmp11, K1812); + Vector128 tmp32 = Sse2.Add(tmp31, K937); Vector128 tmp1 = Sse2.ShiftRightArithmetic(tmp12, 9); Vector128 tmp3 = Sse2.ShiftRightArithmetic(tmp32, 9); Vector128 s03 = Sse2.PackSignedSaturate(tmp0, tmp2); @@ -514,17 +535,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 shi = Sse2.UnpackHigh(s03, s12); // 2 3 2 3 2 3 Vector128 v23 = Sse2.UnpackHigh(slo.AsInt32(), shi.AsInt32()); out01 = Sse2.UnpackLow(slo.AsInt32(), shi.AsInt32()); - out32 = Sse2.Shuffle(v23, SimdUtils.Shuffle.MmShuffle(1, 0, 3, 2)); + out32 = Sse2.Shuffle(v23, MmShuffle1032); } public static void FTransformPass2SSE2(Vector128 v01, Vector128 v32, Span output) { - var seven = Vector128.Create((short)7); - Vector128 k5352_2217 = Vector128.Create(169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20, 169, 8, 232, 20).AsInt16(); - Vector128 k2217_5352 = Vector128.Create(24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8, 24, 235, 169, 8).AsInt16(); - var k12000PlusOne = Vector128.Create(12000 + (1 << 16)); - var k51000 = Vector128.Create(51000); - // Same operations are done on the (0,3) and (1,2) pairs. // a3 = v0 - v3 // a2 = v1 - v2 @@ -532,10 +547,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 a22 = Sse2.UnpackHigh(a32.AsInt64(), a32.AsInt64()); Vector128 b23 = Sse2.UnpackLow(a22.AsInt16(), a32.AsInt16()); - Vector128 c1 = Sse2.MultiplyAddAdjacent(b23, k5352_2217); - Vector128 c3 = Sse2.MultiplyAddAdjacent(b23, k2217_5352); - Vector128 d1 = Sse2.Add(c1, k12000PlusOne); - Vector128 d3 = Sse2.Add(c3, k51000); + Vector128 c1 = Sse2.MultiplyAddAdjacent(b23, K5352_2217); + Vector128 c3 = Sse2.MultiplyAddAdjacent(b23, K2217_5352); + Vector128 d1 = Sse2.Add(c1, K12000PlusOne); + Vector128 d3 = Sse2.Add(c3, K51000); Vector128 e1 = Sse2.ShiftRightArithmetic(d1, 16); Vector128 e3 = Sse2.ShiftRightArithmetic(d3, 16); @@ -553,7 +568,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // a0 = v0 + v3 // a1 = v1 + v2 Vector128 a01 = Sse2.Add(v01, v32); - Vector128 a01Plus7 = Sse2.Add(a01.AsInt16(), seven); + Vector128 a01Plus7 = Sse2.Add(a01.AsInt16(), Seven); Vector128 a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16(); Vector128 c0 = Sse2.Add(a01Plus7, a11); Vector128 c2 = Sse2.Subtract(a01Plus7, a11); From 38fd3a84582afa8e405316cc769c8832f44a35cb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 26 Nov 2021 12:51:25 +0100 Subject: [PATCH 080/228] Avoid bounds checks in IsFlat --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 2fcea8cee..f3b0e8e3d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -744,19 +744,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static bool IsFlat(Span levels, int numBlocks, int thresh) { int score = 0; + ref short levelsRef = ref MemoryMarshal.GetReference(levels); + int offset = 0; while (numBlocks-- > 0) { for (int i = 1; i < 16; i++) { // omit DC, we're only interested in AC - score += levels[i] != 0 ? 1 : 0; + score += Unsafe.Add(ref levelsRef, offset) != 0 ? 1 : 0; if (score > thresh) { return false; } } - levels = levels.Slice(16); + offset += 16; } return true; From 798e9c3ad6e77e3bda0770a16e2e283c7bc45ff1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 26 Nov 2021 14:09:31 +0100 Subject: [PATCH 081/228] Add SSE2 version of FTransform2 --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index a3a9c924c..f657d3252 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -404,8 +404,66 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void FTransform2(Span src, Span reference, Span output, Span output2, Span scratch) { - FTransform(src, reference, output, scratch); - FTransform(src.Slice(4), reference.Slice(4), output2, scratch); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { +#pragma warning disable SA1503 // Braces should not be omitted + fixed (byte* srcRef = src) + fixed (byte* referenceRef = reference) + { + // Load src. + Vector128 src0 = Sse2.LoadScalarVector128((ulong*)srcRef); + Vector128 src1 = Sse2.LoadScalarVector128((ulong*)(srcRef + WebpConstants.Bps)); + Vector128 src2 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 2))); + Vector128 src3 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 3))); + + // Load ref. + Vector128 ref0 = Sse2.LoadScalarVector128((ulong*)referenceRef); + Vector128 ref1 = Sse2.LoadScalarVector128((ulong*)(referenceRef + WebpConstants.Bps)); + Vector128 ref2 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (WebpConstants.Bps * 2))); + Vector128 ref3 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (+WebpConstants.Bps * 3))); + + // Convert both to 16 bit. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsByte(), Vector128.Zero); + Vector128 srcLow1 = Sse2.UnpackLow(src1.AsByte(), Vector128.Zero); + Vector128 srcLow2 = Sse2.UnpackLow(src2.AsByte(), Vector128.Zero); + Vector128 srcLow3 = Sse2.UnpackLow(src3.AsByte(), Vector128.Zero); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsByte(), Vector128.Zero); + Vector128 refLow1 = Sse2.UnpackLow(ref1.AsByte(), Vector128.Zero); + Vector128 refLow2 = Sse2.UnpackLow(ref2.AsByte(), Vector128.Zero); + Vector128 refLow3 = Sse2.UnpackLow(ref3.AsByte(), Vector128.Zero); + + // Compute difference. -> 00 01 02 03 00' 01' 02' 03' + Vector128 diff0 = Sse2.Subtract(srcLow0.AsInt16(), refLow0.AsInt16()); + Vector128 diff1 = Sse2.Subtract(srcLow1.AsInt16(), refLow1.AsInt16()); + Vector128 diff2 = Sse2.Subtract(srcLow2.AsInt16(), refLow2.AsInt16()); + Vector128 diff3 = Sse2.Subtract(srcLow3.AsInt16(), refLow3.AsInt16()); + + // Unpack and shuffle. + // 00 01 02 03 0 0 0 0 + // 10 11 12 13 0 0 0 0 + // 20 21 22 23 0 0 0 0 + // 30 31 32 33 0 0 0 0 + Vector128 shuf01l = Sse2.UnpackLow(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23l = Sse2.UnpackLow(diff2.AsInt32(), diff3.AsInt32()); + Vector128 shuf01h = Sse2.UnpackHigh(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23h = Sse2.UnpackHigh(diff2.AsInt32(), diff3.AsInt32()); + + // First pass. + FTransformPass1SSE2(shuf01l.AsInt16(), shuf23l.AsInt16(), out Vector128 v01l, out Vector128 v32l); + FTransformPass1SSE2(shuf01h.AsInt16(), shuf23h.AsInt16(), out Vector128 v01h, out Vector128 v32h); + + // Second pass. + FTransformPass2SSE2(v01l, v32l, output); + FTransformPass2SSE2(v01h, v32h, output2); + } + } + else +#endif + { + FTransform(src, reference, output, scratch); + FTransform(src.Slice(4), reference.Slice(4), output2, scratch); + } } public static void FTransform(Span src, Span reference, Span output, Span scratch) @@ -567,7 +625,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // a0 = v0 + v3 // a1 = v1 + v2 - Vector128 a01 = Sse2.Add(v01, v32); + Vector128 a01 = Sse2.Add(v01.AsInt16(), v32.AsInt16()); Vector128 a01Plus7 = Sse2.Add(a01.AsInt16(), Seven); Vector128 a11 = Sse2.UnpackHigh(a01.AsInt64(), a01.AsInt64()).AsInt16(); Vector128 c0 = Sse2.Add(a01Plus7, a11); From 0880c586521f0d87616ae579df35b068c186ecad Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 26 Nov 2021 15:18:38 +0100 Subject: [PATCH 082/228] Add FTransform tests --- .../Formats/WebP/Vp8EncodingTests.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index 6bcb4f21f..245e1cdc1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -11,6 +11,57 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class Vp8EncodingTests { + private static void RunFTransform2Test() + { + // arrange + byte[] src = { 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175 }; + byte[] reference = { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129 }; + short[] actualOutput1 = new short[16]; + short[] actualOutput2 = new short[16]; + short[] expectedOutput1 = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; + short[] expectedOutput2 = { 192, -34, 10, 1, -11, 8, 10, -7, 6, 3, -8, 4, 5, -3, -2, 6 }; + + // act + Vp8Encoding.FTransform2(src, reference, actualOutput1, actualOutput2, new int[16]); + + // assert + Assert.True(expectedOutput1.SequenceEqual(actualOutput1)); + Assert.True(expectedOutput2.SequenceEqual(actualOutput2)); + } + + private static void RunFTransformTest() + { + // arrange + byte[] src = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175 + }; + byte[] reference = + { + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129 + }; + short[] actualOutput = new short[16]; + short[] expectedOutput = { 182, 4, 1, 1, 6, 7, -1, -4, 5, 0, -2, 1, 2, 1, 1, 1 }; + + // act + Vp8Encoding.FTransform(src, reference, actualOutput, new int[16]); + + // assert + Assert.True(expectedOutput.SequenceEqual(actualOutput)); + } + private static void RunOneInverseTransformTest() { // arrange @@ -75,6 +126,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.True(dst.SequenceEqual(expected)); } + [Fact] + public void FTransform2_Works() => RunFTransform2Test(); + + [Fact] + public void FTransform_Works() => RunFTransformTest(); + [Fact] public void OneInverseTransform_Works() => RunOneInverseTransformTest(); @@ -82,6 +139,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void TwoInverseTransform_Works() => RunTwoInverseTransformTest(); #if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void FTransform2_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.AllowAll); + + [Fact] + public void FTransform2_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransform2Test, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void FTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void FTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunFTransformTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] public void OneInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.AllowAll); From 81070c4e61060d019f043b012641ebe7dd02a388 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 26 Nov 2021 15:30:36 +0100 Subject: [PATCH 083/228] Add missing #pragma warning restore SA1503 --- src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index f657d3252..d2b9704ab 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -459,6 +459,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } else +#pragma warning restore SA1503 // Braces should not be omitted #endif { FTransform(src, reference, output, scratch); From cb084077281d30a20218ecb1e7f29009d91c191c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 26 Nov 2021 15:58:15 +0100 Subject: [PATCH 084/228] Use nint in for loop --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index f3b0e8e3d..de6f807da 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -726,7 +726,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { uint v = src[0] * 0x01010101u; Span vSpan = BitConverter.GetBytes(v).AsSpan(); - for (int i = 0; i < 16; i++) + for (nint i = 0; i < 16; i++) { if (!src.Slice(0, 4).SequenceEqual(vSpan) || !src.Slice(4, 4).SequenceEqual(vSpan) || !src.Slice(8, 4).SequenceEqual(vSpan) || !src.Slice(12, 4).SequenceEqual(vSpan)) @@ -748,7 +748,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int offset = 0; while (numBlocks-- > 0) { - for (int i = 1; i < 16; i++) + for (nint i = 1; i < 16; i++) { // omit DC, we're only interested in AC score += Unsafe.Add(ref levelsRef, offset) != 0 ? 1 : 0; From 0215e99696d0e11295c5ce7506dbf33c5274174c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 26 Nov 2021 16:19:12 +0100 Subject: [PATCH 085/228] Avoid pinning, avoid using LoadScalarVector128 --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 188 +++++++++--------- 1 file changed, 91 insertions(+), 97 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index d2b9704ab..9fe526dbf 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -407,59 +407,56 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { -#pragma warning disable SA1503 // Braces should not be omitted - fixed (byte* srcRef = src) - fixed (byte* referenceRef = reference) - { - // Load src. - Vector128 src0 = Sse2.LoadScalarVector128((ulong*)srcRef); - Vector128 src1 = Sse2.LoadScalarVector128((ulong*)(srcRef + WebpConstants.Bps)); - Vector128 src2 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 2))); - Vector128 src3 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 3))); - - // Load ref. - Vector128 ref0 = Sse2.LoadScalarVector128((ulong*)referenceRef); - Vector128 ref1 = Sse2.LoadScalarVector128((ulong*)(referenceRef + WebpConstants.Bps)); - Vector128 ref2 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (WebpConstants.Bps * 2))); - Vector128 ref3 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (+WebpConstants.Bps * 3))); - - // Convert both to 16 bit. - Vector128 srcLow0 = Sse2.UnpackLow(src0.AsByte(), Vector128.Zero); - Vector128 srcLow1 = Sse2.UnpackLow(src1.AsByte(), Vector128.Zero); - Vector128 srcLow2 = Sse2.UnpackLow(src2.AsByte(), Vector128.Zero); - Vector128 srcLow3 = Sse2.UnpackLow(src3.AsByte(), Vector128.Zero); - Vector128 refLow0 = Sse2.UnpackLow(ref0.AsByte(), Vector128.Zero); - Vector128 refLow1 = Sse2.UnpackLow(ref1.AsByte(), Vector128.Zero); - Vector128 refLow2 = Sse2.UnpackLow(ref2.AsByte(), Vector128.Zero); - Vector128 refLow3 = Sse2.UnpackLow(ref3.AsByte(), Vector128.Zero); - - // Compute difference. -> 00 01 02 03 00' 01' 02' 03' - Vector128 diff0 = Sse2.Subtract(srcLow0.AsInt16(), refLow0.AsInt16()); - Vector128 diff1 = Sse2.Subtract(srcLow1.AsInt16(), refLow1.AsInt16()); - Vector128 diff2 = Sse2.Subtract(srcLow2.AsInt16(), refLow2.AsInt16()); - Vector128 diff3 = Sse2.Subtract(srcLow3.AsInt16(), refLow3.AsInt16()); - - // Unpack and shuffle. - // 00 01 02 03 0 0 0 0 - // 10 11 12 13 0 0 0 0 - // 20 21 22 23 0 0 0 0 - // 30 31 32 33 0 0 0 0 - Vector128 shuf01l = Sse2.UnpackLow(diff0.AsInt32(), diff1.AsInt32()); - Vector128 shuf23l = Sse2.UnpackLow(diff2.AsInt32(), diff3.AsInt32()); - Vector128 shuf01h = Sse2.UnpackHigh(diff0.AsInt32(), diff1.AsInt32()); - Vector128 shuf23h = Sse2.UnpackHigh(diff2.AsInt32(), diff3.AsInt32()); - - // First pass. - FTransformPass1SSE2(shuf01l.AsInt16(), shuf23l.AsInt16(), out Vector128 v01l, out Vector128 v32l); - FTransformPass1SSE2(shuf01h.AsInt16(), shuf23h.AsInt16(), out Vector128 v01h, out Vector128 v32h); - - // Second pass. - FTransformPass2SSE2(v01l, v32l, output); - FTransformPass2SSE2(v01h, v32h, output2); - } + ref byte srcRef = ref MemoryMarshal.GetReference(src); + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load src. + var src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); + var src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); + var src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); + + // Load ref. + var ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); + var ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); + var ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); + var ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); + + // Convert both to 16 bit. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsByte(), Vector128.Zero); + Vector128 srcLow1 = Sse2.UnpackLow(src1.AsByte(), Vector128.Zero); + Vector128 srcLow2 = Sse2.UnpackLow(src2.AsByte(), Vector128.Zero); + Vector128 srcLow3 = Sse2.UnpackLow(src3.AsByte(), Vector128.Zero); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsByte(), Vector128.Zero); + Vector128 refLow1 = Sse2.UnpackLow(ref1.AsByte(), Vector128.Zero); + Vector128 refLow2 = Sse2.UnpackLow(ref2.AsByte(), Vector128.Zero); + Vector128 refLow3 = Sse2.UnpackLow(ref3.AsByte(), Vector128.Zero); + + // Compute difference. -> 00 01 02 03 00' 01' 02' 03' + Vector128 diff0 = Sse2.Subtract(srcLow0.AsInt16(), refLow0.AsInt16()); + Vector128 diff1 = Sse2.Subtract(srcLow1.AsInt16(), refLow1.AsInt16()); + Vector128 diff2 = Sse2.Subtract(srcLow2.AsInt16(), refLow2.AsInt16()); + Vector128 diff3 = Sse2.Subtract(srcLow3.AsInt16(), refLow3.AsInt16()); + + // Unpack and shuffle. + // 00 01 02 03 0 0 0 0 + // 10 11 12 13 0 0 0 0 + // 20 21 22 23 0 0 0 0 + // 30 31 32 33 0 0 0 0 + Vector128 shuf01l = Sse2.UnpackLow(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23l = Sse2.UnpackLow(diff2.AsInt32(), diff3.AsInt32()); + Vector128 shuf01h = Sse2.UnpackHigh(diff0.AsInt32(), diff1.AsInt32()); + Vector128 shuf23h = Sse2.UnpackHigh(diff2.AsInt32(), diff3.AsInt32()); + + // First pass. + FTransformPass1SSE2(shuf01l.AsInt16(), shuf23l.AsInt16(), out Vector128 v01l, out Vector128 v32l); + FTransformPass1SSE2(shuf01h.AsInt16(), shuf23h.AsInt16(), out Vector128 v01h, out Vector128 v32h); + + // Second pass. + FTransformPass2SSE2(v01l, v32l, output); + FTransformPass2SSE2(v01h, v32h, output2); } else -#pragma warning restore SA1503 // Braces should not be omitted #endif { FTransform(src, reference, output, scratch); @@ -472,52 +469,49 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { -#pragma warning disable SA1503 // Braces should not be omitted - fixed (byte* srcRef = src) - fixed (byte* referenceRef = reference) - { - // Load src. - Vector128 src0 = Sse2.LoadScalarVector128((ulong*)srcRef); - Vector128 src1 = Sse2.LoadScalarVector128((ulong*)(srcRef + WebpConstants.Bps)); - Vector128 src2 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 2))); - Vector128 src3 = Sse2.LoadScalarVector128((ulong*)(srcRef + (WebpConstants.Bps * 3))); - - // Load ref. - Vector128 ref0 = Sse2.LoadScalarVector128((ulong*)referenceRef); - Vector128 ref1 = Sse2.LoadScalarVector128((ulong*)(referenceRef + WebpConstants.Bps)); - Vector128 ref2 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (WebpConstants.Bps * 2))); - Vector128 ref3 = Sse2.LoadScalarVector128((ulong*)(referenceRef + (+WebpConstants.Bps * 3))); - - // 00 01 02 03 * - // 10 11 12 13 * - // 20 21 22 23 * - // 30 31 32 33 * - // Shuffle. - Vector128 srcLow0 = Sse2.UnpackLow(src0.AsInt16(), src1.AsInt16()); - Vector128 srcLow1 = Sse2.UnpackLow(src2.AsInt16(), src3.AsInt16()); - Vector128 refLow0 = Sse2.UnpackLow(ref0.AsInt16(), ref1.AsInt16()); - Vector128 refLow1 = Sse2.UnpackLow(ref2.AsInt16(), ref3.AsInt16()); - - // 00 01 10 11 02 03 12 13 * * ... - // 20 21 30 31 22 22 32 33 * * ... - - // Convert both to 16 bit. - Vector128 src0_16b = Sse2.UnpackLow(srcLow0.AsByte(), Vector128.Zero); - Vector128 src1_16b = Sse2.UnpackLow(srcLow1.AsByte(), Vector128.Zero); - Vector128 ref0_16b = Sse2.UnpackLow(refLow0.AsByte(), Vector128.Zero); - Vector128 ref1_16b = Sse2.UnpackLow(refLow1.AsByte(), Vector128.Zero); - - // Compute the difference. - Vector128 row01 = Sse2.Subtract(src0_16b.AsInt16(), ref0_16b.AsInt16()); - Vector128 row23 = Sse2.Subtract(src1_16b.AsInt16(), ref1_16b.AsInt16()); - - // First pass - FTransformPass1SSE2(row01, row23, out Vector128 v01, out Vector128 v32); - - // Second pass - FTransformPass2SSE2(v01, v32, output); - } -#pragma warning restore SA1503 // Braces should not be omitted + ref byte srcRef = ref MemoryMarshal.GetReference(src); + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load src. + var src0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var src1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps)), 0); + var src2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 2)), 0); + var src3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, WebpConstants.Bps * 3)), 0); + + // Load ref. + var ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0); + var ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0); + var ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0); + var ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0); + + // 00 01 02 03 * + // 10 11 12 13 * + // 20 21 22 23 * + // 30 31 32 33 * + // Shuffle. + Vector128 srcLow0 = Sse2.UnpackLow(src0.AsInt16(), src1.AsInt16()); + Vector128 srcLow1 = Sse2.UnpackLow(src2.AsInt16(), src3.AsInt16()); + Vector128 refLow0 = Sse2.UnpackLow(ref0.AsInt16(), ref1.AsInt16()); + Vector128 refLow1 = Sse2.UnpackLow(ref2.AsInt16(), ref3.AsInt16()); + + // 00 01 10 11 02 03 12 13 * * ... + // 20 21 30 31 22 22 32 33 * * ... + + // Convert both to 16 bit. + Vector128 src0_16b = Sse2.UnpackLow(srcLow0.AsByte(), Vector128.Zero); + Vector128 src1_16b = Sse2.UnpackLow(srcLow1.AsByte(), Vector128.Zero); + Vector128 ref0_16b = Sse2.UnpackLow(refLow0.AsByte(), Vector128.Zero); + Vector128 ref1_16b = Sse2.UnpackLow(refLow1.AsByte(), Vector128.Zero); + + // Compute the difference. + Vector128 row01 = Sse2.Subtract(src0_16b.AsInt16(), ref0_16b.AsInt16()); + Vector128 row23 = Sse2.Subtract(src1_16b.AsInt16(), ref1_16b.AsInt16()); + + // First pass. + FTransformPass1SSE2(row01, row23, out Vector128 v01, out Vector128 v32); + + // Second pass. + FTransformPass2SSE2(v01, v32, output); } else #endif From 9ec8dc708173f14a28d4b76d5bb5c29cb82a38b3 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 26 Nov 2021 20:18:35 +0100 Subject: [PATCH 086/228] remove processor exception wrapping forever, fixes #1827 --- .../Processors/ImageProcessor{TPixel}.cs | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs index e290e7089..b0c81dbd7 100644 --- a/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/ImageProcessor{TPixel}.cs @@ -45,7 +45,6 @@ namespace SixLabors.ImageSharp.Processing.Processors /// void IImageProcessor.Execute() { - // TODO: Try-catch logic temporarily removed, put it back. this.BeforeImageApply(); foreach (ImageFrame sourceFrame in this.Source.Frames) @@ -62,22 +61,9 @@ namespace SixLabors.ImageSharp.Processing.Processors /// the source image. public void Apply(ImageFrame source) { - try - { - this.BeforeFrameApply(source); - this.OnFrameApply(source); - this.AfterFrameApply(source); - } -#if DEBUG - catch (Exception) - { - throw; -#else - catch (Exception ex) - { - throw new ImageProcessingException($"An error occurred when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); -#endif - } + this.BeforeFrameApply(source); + this.OnFrameApply(source); + this.AfterFrameApply(source); } /// From e10126e686b08e4bbcfac3e311f75238a8a1e5cb Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 26 Nov 2021 20:37:47 +0100 Subject: [PATCH 087/228] use standard NETCOREAPP3_1_OR_GREATER directive --- Directory.Build.props | 4 ---- src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs | 2 +- .../Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs | 4 ++-- .../Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs | 2 +- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 1 - .../Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs | 2 +- 6 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index fd78f4f19..26b3cc5af 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -33,8 +33,4 @@ true - - - $(DefineConstants);NETCORE31COMPATIBLE - diff --git a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs index 3a0479359..b0552936e 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/Gen2GcCallback.cs @@ -3,7 +3,7 @@ // Port of BCL internal utility: // https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.Private.CoreLib/src/System/Gen2GcCallback.cs -#if NETCORE31COMPATIBLE +#if NETCOREAPP3_1_OR_GREATER using System; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index ced91fec7..78498b7ea 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Memory.Internals if (trimSettings.Enabled) { UpdateTimer(trimSettings, this); -#if NETCORE31COMPATIBLE +#if NETCOREAPP3_1_OR_GREATER || NETFRAMEWORK Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); #endif this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; @@ -328,7 +328,7 @@ namespace SixLabors.ImageSharp.Memory.Internals private bool IsHighMemoryPressure() { -#if NETCORE31COMPATIBLE +#if NETCOREAPP3_1_OR_GREATER GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); return memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * this.trimSettings.HighPressureThresholdRate; #else diff --git a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs index d9734baea..16a3cb73d 100644 --- a/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Memory private static long GetDefaultMaxPoolSizeBytes() { -#if NETCORE31COMPATIBLE +#if NETCOREAPP3_1_OR_GREATER // On 64 bit .NET Core 3.1+, set the pool size to a portion of the total available memory. // There is a bug in GC.GetGCMemoryInfo() on .NET 5 + 32 bit, making TotalAvailableMemoryBytes unreliable: // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327 diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 6ac4923a4..471287006 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -7,7 +7,6 @@ AnyCPU;x64;x86 SixLabors.ImageSharp.Tests Debug;Release;Debug-InnerLoop;Release-InnerLoop - $(DefineConstants);NETCORE31COMPATIBLE diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 84d64c757..75e57c62b 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } -#if NETCORE31COMPATIBLE +#if NETCOREAPP3_1_OR_GREATER public static readonly bool Is32BitProcess = !Environment.Is64BitProcess; private static readonly List PressureArrays = new(); From ee3265cacf6f57898a5453b29aedfe4b8b6c7cd7 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 26 Nov 2021 20:41:56 +0100 Subject: [PATCH 088/228] add back unsafe optimizations in ErrorDither --- .../Processing/Processors/Dithering/ErrorDither.cs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs index 27bb660e9..5b049e55a 100644 --- a/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs +++ b/src/ImageSharp/Processing/Processors/Dithering/ErrorDither.cs @@ -110,19 +110,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering for (int y = bounds.Top; y < bounds.Bottom; y++) { - // Unsafe optimizations undone temporarily. - // Sporadic local AccessViolationException indicates possible indexing bug. - // ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(source.DangerousGetRowSpan(y)); - // ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); - Span sourceSpan = sourceBuffer.DangerousGetRowSpan(y); - Span destSpan = destination.GetWritablePixelRowSpanUnsafe(y - offsetY); + ref TPixel sourceRowRef = ref MemoryMarshal.GetReference(sourceBuffer.DangerousGetRowSpan(y)); + ref byte destinationRowRef = ref MemoryMarshal.GetReference(destination.GetWritablePixelRowSpanUnsafe(y - offsetY)); for (int x = bounds.Left; x < bounds.Right; x++) { - // TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); - // Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); - TPixel sourcePixel = sourceSpan[x]; - destSpan[x - offsetX] = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); + TPixel sourcePixel = Unsafe.Add(ref sourceRowRef, x); + Unsafe.Add(ref destinationRowRef, x - offsetX) = quantizer.GetQuantizedColor(sourcePixel, out TPixel transformed); this.Dither(source, bounds, sourcePixel, transformed, x, y, scale); } } From a57250e13ab2f4c7aede6e2f4c5f32a9b3e42756 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 26 Nov 2021 21:16:47 +0100 Subject: [PATCH 089/228] oops --- .../Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index 78498b7ea..6504787a8 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Memory.Internals if (trimSettings.Enabled) { UpdateTimer(trimSettings, this); -#if NETCOREAPP3_1_OR_GREATER || NETFRAMEWORK +#if NETCOREAPP3_1_OR_GREATER Gen2GcCallback.Register(s => ((UniformUnmanagedMemoryPool)s).Trim(), this); #endif this.lastTrimTimestamp = Stopwatch.ElapsedMilliseconds; From 9f55e3f115d6fdf3c331c88cf77a83087f98f071 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 26 Nov 2021 22:15:27 +0100 Subject: [PATCH 090/228] fix comment --- src/ImageSharp/Common/Helpers/DebugGuard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Common/Helpers/DebugGuard.cs b/src/ImageSharp/Common/Helpers/DebugGuard.cs index 23b712c52..f438ca9e2 100644 --- a/src/ImageSharp/Common/Helpers/DebugGuard.cs +++ b/src/ImageSharp/Common/Helpers/DebugGuard.cs @@ -27,7 +27,7 @@ namespace SixLabors } /// - /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's false. + /// Verifies whether a condition (indicating disposed state) is met, throwing an ObjectDisposedException if it's true. /// /// Whether the object is disposed. /// The name of the object. From 140b07854e48afc838fe74d7ebac292d043a26eb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 26 Nov 2021 08:50:55 +0300 Subject: [PATCH 091/228] Removed redundant code from Block8x8F and Block8x8 --- .../Jpeg/Components/Block8x8.Intrinsic.cs | 6 +---- .../Formats/Jpeg/Components/Block8x8.cs | 22 +------------------ .../Formats/Jpeg/Components/Block8x8F.cs | 7 ------ .../Formats/Jpg/Block8x8Tests.cs | 14 ------------ 4 files changed, 2 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs index 8265ad589..002d382dc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.Intrinsic.cs @@ -2,16 +2,12 @@ // Licensed under the Apache License, Version 2.0. #if SUPPORTS_RUNTIME_INTRINSICS -using System; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components { - - internal unsafe partial struct Block8x8 : IEquatable + internal unsafe partial struct Block8x8 { [FieldOffset(0)] public Vector128 V0; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index e0d300eb5..4b03f9f7b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// // ReSharper disable once InconsistentNaming [StructLayout(LayoutKind.Explicit)] - internal unsafe partial struct Block8x8 : IEquatable + internal unsafe partial struct Block8x8 { /// /// A number of scalar coefficients in a @@ -164,26 +164,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return sb.ToString(); } - /// - public bool Equals(Block8x8 other) - { - for (int i = 0; i < Size; i++) - { - if (this[i] != other[i]) - { - return false; - } - } - - return true; - } - - /// - public override bool Equals(object obj) => obj is Block8x8 other && this.Equals(other); - - /// - public override int GetHashCode() => (this[0] * 31) + this[1]; - /// /// Returns index of the last non-zero element in given matrix. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index f85e57d29..f25286447 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -104,13 +104,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components return result; } - public static Block8x8F Load(Span data) - { - Block8x8F result = default; - result.LoadFrom(data); - return result; - } - /// /// Load raw 32bit floating point data from source. /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index b13a196cb..094622070 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -70,20 +70,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(data, result); } - [Fact] - public void Equality_WhenTrue() - { - short[] data = Create8x8ShortData(); - var block1 = Block8x8.Load(data); - var block2 = Block8x8.Load(data); - - block1[0] = 42; - block2[0] = 42; - - Assert.Equal(block1, block2); - Assert.Equal(block1.GetHashCode(), block2.GetHashCode()); - } - [Fact] public void Equality_WhenFalse() { From 503b379e89315e7f6eebeb89d23dddf2038f3ca8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 27 Nov 2021 08:34:48 +0300 Subject: [PATCH 092/228] Removed JpegBlockPostProcessor abstraction layer --- .../Decoder/JpegBlockPostProcessor.cs | 82 ------------------- .../Decoder/JpegComponentPostProcessor.cs | 28 ++++++- 2 files changed, 24 insertions(+), 86 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs deleted file mode 100644 index 15f212b40..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder -{ - /// - /// Encapsulates the implementation of processing "raw" jpeg buffers into Jpeg image channels. - /// - [StructLayout(LayoutKind.Sequential)] - internal struct JpegBlockPostProcessor - { - /// - /// Source block - /// - public Block8x8F SourceBlock; - - /// - /// The quantization table as . - /// - public Block8x8F DequantiazationTable; - - /// - /// Defines the horizontal and vertical scale we need to apply to the 8x8 sized block. - /// - private Size subSamplingDivisors; - - /// - /// Initializes a new instance of the struct. - /// - /// The raw jpeg data. - /// The raw component. - public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) - { - int qtIndex = component.QuantizationTableIndex; - this.DequantiazationTable = decoder.QuantizationTables[qtIndex]; - this.subSamplingDivisors = component.SubSamplingDivisors; - - this.SourceBlock = default; - } - - /// - /// Processes 'sourceBlock' producing Jpeg color channel values from spectral values: - /// - Dequantize - /// - Applying IDCT - /// - Level shift by +maximumValue/2, clip to [0, maximumValue] - /// - Copy the resulting color values into 'destArea' scaling up the block by amount defined in . - /// - /// The source block. - /// Reference to the origin of the destination pixel area. - /// The width of the destination pixel buffer. - /// The maximum value derived from the bitdepth. - public void ProcessBlockColorsInto( - ref Block8x8 sourceBlock, - ref float destAreaOrigin, - int destAreaStride, - float maximumValue) - { - ref Block8x8F block = ref this.SourceBlock; - block.LoadFrom(ref sourceBlock); - - // Dequantize: - block.MultiplyInPlace(ref this.DequantiazationTable); - - FastFloatingPointDCT.TransformIDCT(ref block); - - // To conform better to libjpeg we actually NEED TO loose precision here. - // This is because they store blocks as Int16 between all the operations. - // To be "more accurate", we need to emulate this by rounding! - block.NormalizeColorsAndRoundInPlace(maximumValue); - - block.ScaledCopyTo( - ref destAreaOrigin, - destAreaStride, - this.subSamplingDivisors.Width, - this.subSamplingDivisors.Height); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 9a659d621..b26aae8d8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -76,14 +76,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Buffer2D spectralBuffer = this.Component.SpectralBlocks; - var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component); - float maximumValue = this.frame.MaxColorChannelValue; int destAreaStride = this.ColorBuffer.Width; int yBlockStart = step * this.BlockRowsPerStep; + Size subSamplingDivisors = this.Component.SubSamplingDivisors; + + Block8x8F dequantTable = this.RawJpeg.QuantizationTables[this.Component.QuantizationTableIndex]; + Block8x8F workspaceBlock = default; + for (int y = 0; y < this.BlockRowsPerStep; y++) { int yBlock = yBlockStart + y; @@ -103,11 +106,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) { - ref Block8x8 block = ref blockRow[xBlock]; int xBuffer = xBlock * this.blockAreaSize.Width; ref float destAreaOrigin = ref colorBufferRow[xBuffer]; - blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue); + workspaceBlock.LoadFrom(ref blockRow[xBlock]); + + // Dequantize + workspaceBlock.MultiplyInPlace(ref dequantTable); + + // Convert from spectral to color + FastFloatingPointDCT.TransformIDCT(ref workspaceBlock); + + // To conform better to libjpeg we actually NEED TO loose precision here. + // This is because they store blocks as Int16 between all the operations. + // To be "more accurate", we need to emulate this by rounding! + workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); + + // Write to color buffer acording to sampling factors + workspaceBlock.ScaledCopyTo( + ref destAreaOrigin, + destAreaStride, + subSamplingDivisors.Width, + subSamplingDivisors.Height); } } } From 0d3e7fff5339f7ceed1d9c74ebb6513c29c06d9b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 27 Nov 2021 09:07:29 +0300 Subject: [PATCH 093/228] Removed redundant if check, removed used code --- .../Decoder/JpegComponentPostProcessor.cs | 33 ++++--------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index b26aae8d8..e7b0f1b9a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// internal class JpegComponentPostProcessor : IDisposable { - /// - /// Points to the current row in . - /// - private int currentComponentRowInBlocks; - /// /// The size of the area in corresponding to one 8x8 Jpeg block /// @@ -70,9 +65,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public void Dispose() => this.ColorBuffer.Dispose(); /// - /// Invoke for block rows, copy the result into . + /// Convert raw spectral DCT data to color data and copy it to the color buffer . /// - public void CopyBlocksToColorBuffer(int step) + public void CopyBlocksToColorBuffer(int spectralStep) { Buffer2D spectralBuffer = this.Component.SpectralBlocks; @@ -80,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = step * this.BlockRowsPerStep; + int yBlockStart = spectralStep * this.BlockRowsPerStep; Size subSamplingDivisors = this.Component.SubSamplingDivisors; @@ -89,26 +84,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int y = 0; y < this.BlockRowsPerStep; y++) { - int yBlock = yBlockStart + y; - - if (yBlock >= spectralBuffer.Height) - { - break; - } - int yBuffer = y * this.blockAreaSize.Height; Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); - Span blockRow = spectralBuffer.GetRowSpan(yBlock); + Span blockRow = spectralBuffer.GetRowSpan(yBlockStart + y); // see: https://github.com/SixLabors/ImageSharp/issues/824 int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width); for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) { - int xBuffer = xBlock * this.blockAreaSize.Width; - ref float destAreaOrigin = ref colorBufferRow[xBuffer]; - + // Integer to float workspaceBlock.LoadFrom(ref blockRow[xBlock]); // Dequantize @@ -123,8 +109,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder workspaceBlock.NormalizeColorsAndRoundInPlace(maximumValue); // Write to color buffer acording to sampling factors + int xColorBufferStart = xBlock * this.blockAreaSize.Width; workspaceBlock.ScaledCopyTo( - ref destAreaOrigin, + ref colorBufferRow[xColorBufferStart], destAreaStride, subSamplingDivisors.Width, subSamplingDivisors.Height); @@ -140,11 +127,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder spectralBlocks.GetRowSpan(i).Clear(); } } - - public void CopyBlocksToColorBuffer() - { - this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks); - this.currentComponentRowInBlocks += this.BlockRowsPerStep; - } } } From 97a0dccfc9aab9a4a5ebd07d0f5674efcd02336e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 27 Nov 2021 10:05:40 +0300 Subject: [PATCH 094/228] Removed redundant if check (min call) --- .../Jpeg/Components/Decoder/JpegComponentPostProcessor.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index e7b0f1b9a..35f421263 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -89,10 +89,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder Span colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); Span blockRow = spectralBuffer.GetRowSpan(yBlockStart + y); - // see: https://github.com/SixLabors/ImageSharp/issues/824 - int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width); - - for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) + for (int xBlock = 0; xBlock < spectralBuffer.Width; xBlock++) { // Integer to float workspaceBlock.LoadFrom(ref blockRow[xBlock]); From e8b59c6bb4b8f77714170285ef7710b4c0e4e6db Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 27 Nov 2021 10:11:41 +0300 Subject: [PATCH 095/228] Fixed access modifier & docs --- .../Jpeg/Components/Decoder/IRawJpegData.cs | 1 - .../Decoder/JpegComponentPostProcessor.cs | 52 +++++++++---------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs index 0b80acc5d..33815e539 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs @@ -5,7 +5,6 @@ using System; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { - /// /// /// Represents decompressed, unprocessed jpeg data with spectral space -s. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index 35f421263..b87d74720 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -21,6 +21,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// private readonly JpegFrame frame; + /// + /// Gets the maximal number of block rows being processed in one step. + /// + private readonly int blockRowsPerStep; + + /// + /// Gets the component containing decoding meta information. + /// + private readonly IJpegComponent component; + + /// + /// Gets the instance containing decoding meta information. + /// + private readonly IRawJpegData rawJpeg; + /// /// Initializes a new instance of the class. /// @@ -28,39 +43,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { this.frame = frame; - this.Component = component; - this.RawJpeg = rawJpeg; - this.blockAreaSize = this.Component.SubSamplingDivisors * 8; + this.component = component; + this.rawJpeg = rawJpeg; + this.blockAreaSize = this.component.SubSamplingDivisors * 8; this.ColorBuffer = memoryAllocator.Allocate2DOveraligned( postProcessorBufferSize.Width, postProcessorBufferSize.Height, this.blockAreaSize.Height); - this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height; + this.blockRowsPerStep = postProcessorBufferSize.Height / 8 / this.component.SubSamplingDivisors.Height; } - public IRawJpegData RawJpeg { get; } - - /// - /// Gets the - /// - public IJpegComponent Component { get; } - /// /// Gets the temporary working buffer of color values. /// public Buffer2D ColorBuffer { get; } - /// - /// Gets - /// - public Size SizeInBlocks => this.Component.SizeInBlocks; - - /// - /// Gets the maximal number of block rows being processed in one step. - /// - public int BlockRowsPerStep { get; } - /// public void Dispose() => this.ColorBuffer.Dispose(); @@ -69,20 +67,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public void CopyBlocksToColorBuffer(int spectralStep) { - Buffer2D spectralBuffer = this.Component.SpectralBlocks; + Buffer2D spectralBuffer = this.component.SpectralBlocks; float maximumValue = this.frame.MaxColorChannelValue; int destAreaStride = this.ColorBuffer.Width; - int yBlockStart = spectralStep * this.BlockRowsPerStep; + int yBlockStart = spectralStep * this.blockRowsPerStep; - Size subSamplingDivisors = this.Component.SubSamplingDivisors; + Size subSamplingDivisors = this.component.SubSamplingDivisors; - Block8x8F dequantTable = this.RawJpeg.QuantizationTables[this.Component.QuantizationTableIndex]; + Block8x8F dequantTable = this.rawJpeg.QuantizationTables[this.component.QuantizationTableIndex]; Block8x8F workspaceBlock = default; - for (int y = 0; y < this.BlockRowsPerStep; y++) + for (int y = 0; y < this.blockRowsPerStep; y++) { int yBuffer = y * this.blockAreaSize.Height; @@ -118,7 +116,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public void ClearSpectralBuffers() { - Buffer2D spectralBlocks = this.Component.SpectralBlocks; + Buffer2D spectralBlocks = this.component.SpectralBlocks; for (int i = 0; i < spectralBlocks.Height; i++) { spectralBlocks.GetRowSpan(i).Clear(); From 83da0e069459d716bd3df4fcd7d53282419d295d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 27 Nov 2021 15:46:13 +0100 Subject: [PATCH 096/228] Reverse array access order to avoid bounds checks --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 9fe526dbf..ab64a8ddb 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -523,18 +523,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int refIdx = 0; for (i = 0; i < 4; i++) { - int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) - int d1 = src[srcIdx + 1] - reference[refIdx + 1]; - int d2 = src[srcIdx + 2] - reference[refIdx + 2]; int d3 = src[srcIdx + 3] - reference[refIdx + 3]; + int d2 = src[srcIdx + 2] - reference[refIdx + 2]; + int d1 = src[srcIdx + 1] - reference[refIdx + 1]; + int d0 = src[srcIdx] - reference[refIdx]; // 9bit dynamic range ([-255,255]) int a0 = d0 + d3; // 10b [-510,510] int a1 = d1 + d2; int a2 = d1 - d2; int a3 = d0 - d3; - tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] - tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] - tmp[2 + (i * 4)] = (a0 - a1) * 8; tmp[3 + (i * 4)] = ((a3 * 2217) - (a2 * 5352) + 937) >> 9; + tmp[2 + (i * 4)] = (a0 - a1) * 8; + tmp[1 + (i * 4)] = ((a2 * 2217) + (a3 * 5352) + 1812) >> 9; // [-7536,7542] + tmp[0 + (i * 4)] = (a0 + a1) * 8; // 14b [-8160,8160] srcIdx += WebpConstants.Bps; refIdx += WebpConstants.Bps; @@ -652,10 +652,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; - tmp[0 + (i * 4)] = a0 + a1; // 14b - tmp[1 + (i * 4)] = a3 + a2; - tmp[2 + (i * 4)] = a3 - a2; tmp[3 + (i * 4)] = a0 - a1; + tmp[2 + (i * 4)] = a3 - a2; + tmp[1 + (i * 4)] = a3 + a2; + tmp[0 + (i * 4)] = a0 + a1; // 14b inputIdx += 64; } From 0c8c892647d2d4ba2535e9a9ac6bd4ac2213d34b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 27 Nov 2021 22:23:47 +0100 Subject: [PATCH 097/228] Process first round of review comments --- src/ImageSharp/Formats/Pbm/BinaryDecoder.cs | 2 +- src/ImageSharp/Formats/Pbm/BinaryEncoder.cs | 22 +-- .../Pbm/BufferedReadStreamExtensions.cs | 2 +- src/ImageSharp/Formats/Pbm/PbmConstants.cs | 4 +- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 18 ++- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 4 +- src/ImageSharp/Formats/Pbm/PbmEncoder.cs | 23 +++- src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs | 58 ++++---- .../Formats/Pbm/PbmImageFormatDetector.cs | 7 +- src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 129 ++++++++++-------- .../Formats/Pbm/StreamExtensions.cs | 19 --- .../Formats/Pbm/PbmDecoderTests.cs | 55 +++++++- tests/ImageSharp.Tests/TestImages.cs | 2 + ...age_L16_Gene-UP WebSocket RunImageMask.png | 3 + ...ReferenceImage_L8_blackandwhite_binary.png | 3 + ...eReferenceImage_L8_blackandwhite_plain.png | 3 + ...nceImage_L8_grayscale_plain_normalized.png | 3 + .../DecodeReferenceImage_L8_rings.png | 3 + ...DecodeReferenceImage_Rgb24_00000_00000.png | 3 + ...erenceImage_Rgb24_rgb_plain_normalized.png | 3 + .../Input/Pbm/grayscale_plain_normalized.pgm | 10 ++ .../Images/Input/Pbm/rgb_plain_normalized.ppm | 8 ++ 22 files changed, 257 insertions(+), 127 deletions(-) delete mode 100644 src/ImageSharp/Formats/Pbm/StreamExtensions.cs create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png create mode 100644 tests/Images/Input/Pbm/grayscale_plain_normalized.pgm create mode 100644 tests/Images/Input/Pbm/rgb_plain_normalized.ppm diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs index c7a09a613..8b6df295b 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm x++; if (x == width) { - startBit = (bit + 1) % 8; + startBit = (bit + 1) & 7; // Round off to below 8. if (startBit != 0) { stream.Seek(-1, System.IO.SeekOrigin.Current); diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs index 1233c87fc..2bcbaeef7 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs @@ -60,8 +60,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; + int width = image.Width; + int height = image.Height; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -83,8 +83,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; + int width = image.Width; + int height = image.Height; int bytesPerPixel = 2; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); @@ -107,8 +107,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; + int width = image.Width; + int height = image.Height; int bytesPerPixel = 3; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); @@ -131,8 +131,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; + int width = image.Width; + int height = image.Height; int bytesPerPixel = 6; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); @@ -155,8 +155,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; + int width = image.Width; + int height = image.Height; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -186,7 +186,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm if (x == width) { previousValue = value; - startBit = (i + 1) % 8; + startBit = (i + 1) & 7; // Round off to below 8. break; } } diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs index 054731b48..581d3e592 100644 --- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -51,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm while (true) { int current = stream.ReadByte() - 0x30; - if (current < 0 || current > 9) + if ((uint)current > 9) { break; } diff --git a/src/ImageSharp/Formats/Pbm/PbmConstants.cs b/src/ImageSharp/Formats/Pbm/PbmConstants.cs index 0aa9b706a..912ffaf85 100644 --- a/src/ImageSharp/Formats/Pbm/PbmConstants.cs +++ b/src/ImageSharp/Formats/Pbm/PbmConstants.cs @@ -18,11 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// The list of mimetypes that equate to a ppm. /// - public static readonly IEnumerable MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap", "image/x-portable-arbitrarymap" }; + public static readonly IEnumerable MimeTypes = new[] { "image/x-portable-pixmap", "image/x-portable-anymap" }; /// /// The list of file extensions that equate to a ppm. /// - public static readonly IEnumerable FileExtensions = new[] { "ppm", "pbm", "pgm", "pam" }; + public static readonly IEnumerable FileExtensions = new[] { "ppm", "pbm", "pgm" }; } } diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 640ec3823..62cef176d 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -9,7 +9,23 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Pbm { /// - /// Image decoder for generating an image out of a ppm stream. + /// Image decoder for reading PGM, PBM or PPM bitmaps from a stream. These images are from + /// the family of PNM images. + /// + /// + /// PBM + /// Black and white images. + /// + /// + /// PGM + /// Grayscale images. + /// + /// + /// PPM + /// Color images, with RGB pixels. + /// + /// + /// The specification of these images is found at . /// public sealed class PbmDecoder : IImageDecoder, IImageInfoDetector { diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 31969af47..bd99a578a 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -82,9 +82,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// The input stream. private void ProcessHeader(BufferedReadStream stream) { - byte[] buffer = new byte[2]; + Span buffer = stackalloc byte[2]; - int bytesRead = stream.Read(buffer, 0, 2); + int bytesRead = stream.Read(buffer); if (bytesRead != 2 || buffer[0] != 'P') { // Empty or not an PPM image. diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs index 21565d161..fe0f7f9f1 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs @@ -10,7 +10,28 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Pbm { /// - /// Image encoder for writing an image to a stream as PGM, PBM, PPM or PAM bitmap. + /// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from + /// the family of PNM images. + /// + /// The PNM formats are a faily simple image format. They share a plain text header, consisting of: + /// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in + /// plain text decimals seperated by spaces, or binary encoded. + /// + /// + /// PBM + /// Black and white images, with 1 representing black and 0 representing white. + /// + /// + /// PGM + /// Grayscale images, scaling from 0 to max_pixel_value, 0 representing black and max_pixel_value representing white. + /// + /// + /// PPM + /// Color images, with RGB pixels (in that order), with 0 representing black and 2 representing full color. + /// + /// + /// + /// The specification of these images is found at . /// public sealed class PbmEncoder : IImageEncoder, IPbmEncoderOptions { diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs index 527ceb8ee..eb1ba8140 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Buffers; +using System.Buffers.Text; using System.IO; -using System.Text; using System.Threading; using SixLabors.ImageSharp.Advanced; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Pbm @@ -17,7 +15,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// internal sealed class PbmEncoderCore : IImageEncoderInternals { - private const char NewLine = '\n'; + private const byte NewLine = (byte)'\n'; + private const byte Space = (byte)' '; + private const byte P = (byte)'P'; /// /// The global configuration. @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm this.DeduceOptions(image); - string signature = this.DeduceSignature(); + byte signature = this.DeduceSignature(); this.WriteHeader(stream, signature, image.Size()); this.WritePixels(stream, image.Frames.RootFrame); @@ -91,29 +91,29 @@ namespace SixLabors.ImageSharp.Formats.Pbm } } - private string DeduceSignature() + private byte DeduceSignature() { - string signature; + byte signature; if (this.colorType == PbmColorType.BlackAndWhite) { if (this.encoding == PbmEncoding.Plain) { - signature = "P1"; + signature = (byte)'1'; } else { - signature = "P4"; + signature = (byte)'4'; } } else if (this.colorType == PbmColorType.Grayscale) { if (this.encoding == PbmEncoding.Plain) { - signature = "P2"; + signature = (byte)'2'; } else { - signature = "P5"; + signature = (byte)'5'; } } else @@ -121,35 +121,41 @@ namespace SixLabors.ImageSharp.Formats.Pbm // RGB ColorType if (this.encoding == PbmEncoding.Plain) { - signature = "P3"; + signature = (byte)'3'; } else { - signature = "P6"; + signature = (byte)'6'; } } return signature; } - private void WriteHeader(Stream stream, string signature, Size pixelSize) + private void WriteHeader(Stream stream, byte signature, Size pixelSize) { - var builder = new StringBuilder(20); - builder.Append(signature); - builder.Append(NewLine); - builder.Append(pixelSize.Width.ToString()); - builder.Append(NewLine); - builder.Append(pixelSize.Height.ToString()); - builder.Append(NewLine); + Span buffer = stackalloc byte[128]; + + int written = 3; + buffer[0] = P; + buffer[1] = signature; + buffer[2] = NewLine; + + Utf8Formatter.TryFormat(pixelSize.Width, buffer.Slice(written), out int bytesWritten); + written += bytesWritten; + buffer[written++] = Space; + Utf8Formatter.TryFormat(pixelSize.Height, buffer.Slice(written), out bytesWritten); + written += bytesWritten; + buffer[written++] = NewLine; + if (this.colorType != PbmColorType.BlackAndWhite) { - builder.Append(this.maxPixelValue.ToString()); - builder.Append(NewLine); + Utf8Formatter.TryFormat(this.maxPixelValue, buffer.Slice(written), out bytesWritten); + written += bytesWritten; + buffer[written++] = NewLine; } - string headerStr = builder.ToString(); - byte[] headerBytes = Encoding.ASCII.GetBytes(headerStr); - stream.Write(headerBytes, 0, headerBytes.Length); + stream.Write(buffer, 0, written); } /// diff --git a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs index 943424dc9..15bacc4de 100644 --- a/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Pbm/PbmImageFormatDetector.cs @@ -22,9 +22,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm private bool IsSupportedFileFormat(ReadOnlySpan header) { - if (header.Length >= this.HeaderSize) +#pragma warning disable SA1131 // Use readable conditions + if (1 < (uint)header.Length) +#pragma warning restore SA1131 // Use readable conditions { - return header[0] == P && header[1] > Zero && header[1] < Seven; + // Signature should be between P1 and P6. + return header[0] == P && (uint)(header[1] - Zero - 1) < (Seven - Zero - 1); } return false; diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index d90eaf73f..e362f8680 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Buffers.Text; using System.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -20,6 +21,14 @@ namespace SixLabors.ImageSharp.Formats.Pbm private const byte Zero = 0x30; private const byte One = 0x31; + private const int MaxCharsPerPixelBlackAndWhite = 2; + private const int MaxCharsPerPixelGrayscale = 4; + private const int MaxCharsPerPixelGrayscaleWide = 6; + private const int MaxCharsPerPixelRgb = 4 * 3; + private const int MaxCharsPerPixelRgbWide = 6 * 3; + + private static readonly StandardFormat DecimalFormat = StandardFormat.Parse("D"); + /// /// Decode pixels into the PBM plain encoding. /// @@ -63,12 +72,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; - int bytesWritten = -1; + int width = image.Width; + int height = image.Height; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); + Span plainSpan = stackalloc byte[width * MaxCharsPerPixelGrayscale]; for (int y = 0; y < height; y++) { @@ -78,23 +87,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm pixelSpan, rowSpan); + int written = 0; for (int x = 0; x < width; x++) { - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); + Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } } private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; - int bytesWritten = -1; + int width = image.Width; + int height = image.Height; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); + Span plainSpan = stackalloc byte[width * MaxCharsPerPixelGrayscaleWide]; for (int y = 0; y < height; y++) { @@ -104,23 +118,28 @@ namespace SixLabors.ImageSharp.Formats.Pbm pixelSpan, rowSpan); + int written = 0; for (int x = 0; x < width; x++) { - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].PackedValue); + Utf8Formatter.TryFormat(rowSpan[x].PackedValue, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } } private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; - int bytesWritten = -1; + int width = image.Width; + int height = image.Height; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); + Span plainSpan = stackalloc byte[width * MaxCharsPerPixelRgb]; for (int y = 0; y < height; y++) { @@ -130,27 +149,34 @@ namespace SixLabors.ImageSharp.Formats.Pbm pixelSpan, rowSpan); + int written = 0; for (int x = 0; x < width; x++) { - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].R); - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].G); - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].B); + Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } } private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; - int bytesWritten = -1; + int width = image.Width; + int height = image.Height; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); + Span plainSpan = stackalloc byte[width * MaxCharsPerPixelRgbWide]; for (int y = 0; y < height; y++) { @@ -160,27 +186,34 @@ namespace SixLabors.ImageSharp.Formats.Pbm pixelSpan, rowSpan); + int written = 0; for (int x = 0; x < width; x++) { - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].R); - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].G); - WriteWhitespace(stream, ref bytesWritten); - bytesWritten += stream.WriteDecimal(rowSpan[x].B); + Utf8Formatter.TryFormat(rowSpan[x].R, plainSpan.Slice(written), out int bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].G, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; + Utf8Formatter.TryFormat(rowSpan[x].B, plainSpan.Slice(written), out bytesWritten, DecimalFormat); + written += bytesWritten; + plainSpan[written++] = Space; } + + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } } private static void WriteBlackAndWhite(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { - int width = image.Size().Width; - int height = image.Size().Height; - int bytesWritten = -1; + int width = image.Width; + int height = image.Height; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); + Span plainSpan = stackalloc byte[width * MaxCharsPerPixelBlackAndWhite]; for (int y = 0; y < height; y++) { @@ -190,38 +223,16 @@ namespace SixLabors.ImageSharp.Formats.Pbm pixelSpan, rowSpan); + int written = 0; for (int x = 0; x < width; x++) { - WriteWhitespace(stream, ref bytesWritten); - if (rowSpan[x].PackedValue > 127) - { - stream.WriteByte(Zero); - } - else - { - stream.WriteByte(One); - } - - bytesWritten++; + byte value = (rowSpan[x].PackedValue > 127) ? Zero : One; + plainSpan[written++] = value; + plainSpan[written++] = Space; } - } - } - private static void WriteWhitespace(Stream stream, ref int bytesWritten) - { - if (bytesWritten > MaxLineLength) - { - stream.WriteByte(NewLine); - bytesWritten = 1; - } - else if (bytesWritten == -1) - { - bytesWritten = 0; - } - else - { - stream.WriteByte(Space); - bytesWritten++; + plainSpan[written - 1] = NewLine; + stream.Write(plainSpan, 0, written); } } } diff --git a/src/ImageSharp/Formats/Pbm/StreamExtensions.cs b/src/ImageSharp/Formats/Pbm/StreamExtensions.cs deleted file mode 100644 index 9851afee0..000000000 --- a/src/ImageSharp/Formats/Pbm/StreamExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.IO; -using System.Text; - -namespace SixLabors.ImageSharp.Formats.Pbm -{ - internal static class StreamExtensions - { - public static int WriteDecimal(this Stream stream, int value) - { - string str = value.ToString(); - byte[] bytes = Encoding.ASCII.GetBytes(str); - stream.Write(bytes); - return bytes.Length; - } - } -} diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 4ff359387..6c84fba9e 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -3,7 +3,8 @@ using System.IO; using SixLabors.ImageSharp.Formats.Pbm; - +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; @@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] [InlineData(RgbPlain, PbmColorType.Rgb)] [InlineData(RgbBinary, PbmColorType.Rgb)] - public void PpmDecoder_CanDecode(string imagePath, PbmColorType expectedColorType) + public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType) { // Arrange var testFile = TestFile.Create(imagePath); @@ -36,5 +37,55 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm Assert.NotNull(bitmapMetadata); Assert.Equal(expectedColorType, bitmapMetadata.ColorType); } + + [Theory] + [InlineData(BlackAndWhitePlain)] + [InlineData(BlackAndWhiteBinary)] + [InlineData(GrayscalePlain)] + [InlineData(GrayscaleBinary)] + [InlineData(GrayscaleBinaryWide)] + public void ImageLoadL8CanDecode(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + } + + [Theory] + [InlineData(RgbPlain)] + [InlineData(RgbBinary)] + public void ImageLoadRgb24CanDecode(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var image = Image.Load(stream); + + // Assert + Assert.NotNull(image); + } + + [Theory] + [WithFile(BlackAndWhiteBinary, PixelTypes.L8, true)] + [WithFile(GrayscalePlainNormalized, PixelTypes.L8, true)] + [WithFile(GrayscaleBinary, PixelTypes.L8, true)] + [WithFile(GrayscaleBinaryWide, PixelTypes.L16, true)] + [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, false)] + [WithFile(RgbBinary, PixelTypes.Rgb24, false)] + public void DecodeReferenceImage(TestImageProvider provider, bool isGrayscale) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(); + + image.CompareToReferenceOutput(provider, grayscale: isGrayscale); + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 67f947ff5..444be63a2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -874,8 +874,10 @@ namespace SixLabors.ImageSharp.Tests public const string GrayscaleBinary = "Pbm/rings.pgm"; public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; + public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm"; public const string RgbBinary = "Pbm/00000_00000.ppm"; public const string RgbPlain = "Pbm/rgb_plain.ppm"; + public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; } } } diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png new file mode 100644 index 000000000..09bb074a3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec +size 4521 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png new file mode 100644 index 000000000..d1f1515bb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9 +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png new file mode 100644 index 000000000..372261923 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png new file mode 100644 index 000000000..9c86c2fc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 +size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png new file mode 100644 index 000000000..acf751c28 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348 +size 27884 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png new file mode 100644 index 000000000..49cc74f3f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf +size 2250 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png new file mode 100644 index 000000000..421a59849 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 +size 152 diff --git a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm new file mode 100644 index 000000000..fe0329629 --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm @@ -0,0 +1,10 @@ +P2 +24 7 +255 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 51 51 51 51 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 255 255 255 0 +0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 255 0 +0 51 51 51 0 0 0 119 119 119 0 0 0 187 187 187 0 0 0 255 255 255 255 0 +0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 0 0 +0 51 0 0 0 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm new file mode 100644 index 000000000..628931579 --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm @@ -0,0 +1,8 @@ +P3 +# example from the man page +4 4 +255 + 0 0 0 0 0 0 0 0 0 255 0 255 + 0 0 0 0 255 119 0 0 0 0 0 0 + 0 0 0 0 0 0 0 255 119 0 0 0 +255 0 255 0 0 0 0 0 0 0 0 0 \ No newline at end of file From 554c0d7b521b7d0aa411a631456aaa04d604b7b4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 28 Nov 2021 09:29:41 +0100 Subject: [PATCH 098/228] Add AVX2 version of QuantizeBlock --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 75 ++++++++++++++++++- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 2fcea8cee..125645c7d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -25,6 +25,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS private static readonly Vector128 MaxCoeff2047 = Vector128.Create((short)MaxLevel); + private static readonly Vector256 MaxCoeff2047Vec256 = Vector256.Create((short)MaxLevel); + + private static readonly Vector256 Cst256 = Vector256.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13, 2, 3, 8, 9, 10, 11, 4, 5, 254, 255, 6, 7, 12, 13, 14, 15); + + private static readonly Vector256 Cst78 = Vector256.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 254, 255, 0, 1, 254, 255, 254, 255, 254, 255, 254, 255); + private static readonly Vector128 CstLo = Vector128.Create(0, 1, 2, 3, 8, 9, 254, 255, 10, 11, 4, 5, 6, 7, 12, 13); private static readonly Vector128 Cst7 = Vector128.Create(254, 255, 254, 255, 254, 255, 254, 255, 14, 15, 254, 255, 254, 255, 254, 255); @@ -531,7 +537,70 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static int QuantizeBlock(Span input, Span output, ref Vp8Matrix mtx) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse41.IsSupported) + if (Avx2.IsSupported) + { + // Load all inputs. + Vector256 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); + Vector256 iq0 = Unsafe.As>(ref mtx.IQ[0]); + Vector256 q0 = Unsafe.As>(ref mtx.Q[0]); + + // coeff = abs(in) + Vector256 coeff0 = Avx2.Abs(input0); + + // coeff = abs(in) + sharpen + Vector256 sharpen0 = Unsafe.As>(ref mtx.Sharpen[0]); + Avx2.Add(coeff0.AsInt16(), sharpen0); + + // out = (coeff * iQ + B) >> QFIX + // doing calculations with 32b precision (QFIX=17) + // out = (coeff * iQ) + Vector256 coeffiQ0H = Avx2.MultiplyHigh(coeff0, iq0); + Vector256 coeffiQ0L = Avx2.MultiplyLow(coeff0, iq0); + Vector256 out00 = Avx2.UnpackLow(coeffiQ0L, coeffiQ0H); + Vector256 out08 = Avx2.UnpackHigh(coeffiQ0L, coeffiQ0H); + + // out = (coeff * iQ + B) + Vector256 bias00 = Unsafe.As>(ref mtx.Bias[0]); + Vector256 bias08 = Unsafe.As>(ref mtx.Bias[8]); + out00 = Avx2.Add(out00.AsInt32(), bias00.AsInt32()).AsUInt16(); + out08 = Avx2.Add(out08.AsInt32(), bias08.AsInt32()).AsUInt16(); + + // out = QUANTDIV(coeff, iQ, B, QFIX) + out00 = Avx2.ShiftRightArithmetic(out00.AsInt32(), WebpConstants.QFix).AsUInt16(); + out08 = Avx2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); + + // Pack result as 16b. + Vector256 out0 = Avx2.PackSignedSaturate(out00.AsInt32(), out08.AsInt32()); + + // if (coeff > 2047) coeff = 2047 + out0 = Avx2.Min(out0, MaxCoeff2047Vec256); + + // Put the sign back. + out0 = Avx2.Sign(out0, input0); + + // in = out * Q + input0 = Avx2.MultiplyLow(out0, q0.AsInt16()); + ref short inputRef = ref MemoryMarshal.GetReference(input); + Unsafe.As>(ref inputRef) = input0; + + // zigzag the output before storing it. + Vector256 tmp256 = Avx2.Shuffle(out0.AsByte(), Cst256); + Vector256 tmp78 = Avx2.Shuffle(out0.AsByte(), Cst78); + + // Reverse the order of the 16-byte lanes. + Vector256 tmp87 = Avx2.Permute2x128(tmp78, tmp78, 1); + Vector256 outZ = Avx2.Or(tmp256, tmp87).AsInt16(); + + ref short outputRef = ref MemoryMarshal.GetReference(output); + Unsafe.As>(ref outputRef) = outZ; + + Vector256 packedOutput = Avx2.PackSignedSaturate(outZ, outZ); + + // Detect if all 'out' values are zeros or not. + Vector256 cmpeq = Avx2.CompareEqual(packedOutput, Vector256.Zero); + return Avx2.MoveMask(cmpeq) != -1 ? 1 : 0; + } + else if (Sse41.IsSupported) { // Load all inputs. Vector128 input0 = Unsafe.As>(ref MemoryMarshal.GetReference(input)); @@ -579,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy out08 = Sse2.ShiftRightArithmetic(out08.AsInt32(), WebpConstants.QFix).AsUInt16(); out12 = Sse2.ShiftRightArithmetic(out12.AsInt32(), WebpConstants.QFix).AsUInt16(); - // pack result as 16b + // Pack result as 16b. Vector128 out0 = Sse2.PackSignedSaturate(out00.AsInt32(), out04.AsInt32()); Vector128 out8 = Sse2.PackSignedSaturate(out08.AsInt32(), out12.AsInt32()); @@ -587,7 +656,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy out0 = Sse2.Min(out0, MaxCoeff2047); out8 = Sse2.Min(out8, MaxCoeff2047); - // put sign back + // Put the sign back. out0 = Ssse3.Sign(out0, input0); out8 = Ssse3.Sign(out8, input8); From 0e984cf1b4dbadc5bda7175d88e01aa78b0023bf Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 28 Nov 2021 11:17:37 +0100 Subject: [PATCH 099/228] Remove non existing LFS objects --- ...DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png | 3 --- .../DecodeReferenceImage_L8_blackandwhite_binary.png | 3 --- .../DecodeReferenceImage_L8_blackandwhite_plain.png | 3 --- .../DecodeReferenceImage_L8_grayscale_plain_normalized.png | 3 --- .../PbmDecoderTests/DecodeReferenceImage_L8_rings.png | 3 --- .../PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png | 3 --- .../DecodeReferenceImage_Rgb24_rgb_plain_normalized.png | 3 --- 7 files changed, 21 deletions(-) delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png deleted file mode 100644 index 09bb074a3..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec -size 4521 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png deleted file mode 100644 index d1f1515bb..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9 -size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png deleted file mode 100644 index 372261923..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f -size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png deleted file mode 100644 index 9c86c2fc1..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 -size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png deleted file mode 100644 index acf751c28..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348 -size 27884 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png deleted file mode 100644 index 49cc74f3f..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf -size 2250 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png deleted file mode 100644 index 421a59849..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 -size 152 From e21d2c31a25089c0b492e9daec260f30b0199648 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 28 Nov 2021 11:18:35 +0100 Subject: [PATCH 100/228] Put back missing LFS objects --- ...DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png | 3 +++ .../DecodeReferenceImage_L8_blackandwhite_binary.png | 3 +++ .../DecodeReferenceImage_L8_blackandwhite_plain.png | 3 +++ .../DecodeReferenceImage_L8_grayscale_plain_normalized.png | 3 +++ .../PbmDecoderTests/DecodeReferenceImage_L8_rings.png | 3 +++ .../PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png | 3 +++ .../DecodeReferenceImage_Rgb24_rgb_plain_normalized.png | 3 +++ 7 files changed, 21 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png new file mode 100644 index 000000000..09bb074a3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec +size 4521 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png new file mode 100644 index 000000000..d1f1515bb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9 +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png new file mode 100644 index 000000000..372261923 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png new file mode 100644 index 000000000..9c86c2fc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 +size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png new file mode 100644 index 000000000..acf751c28 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348 +size 27884 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png new file mode 100644 index 000000000..49cc74f3f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf +size 2250 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png new file mode 100644 index 000000000..421a59849 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 +size 152 From ec4298abd875c9591a0520cc5734e2583918325e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 28 Nov 2021 13:33:02 +0100 Subject: [PATCH 101/228] Add QuantizeBlock test without AVX --- tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index 80b5f0a53..ef60a7b20 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -47,7 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void QuantizeBlock_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.AllowAll); [Fact] - public void QuantizeBlock_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableHWIntrinsic); + public void QuantizeBlock_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void QuantizeBlock_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunQuantizeBlockTest, HwIntrinsics.DisableAVX2); #endif } } From c0ee67b5b2b51eb51684b0c1fe3ae725331b9874 Mon Sep 17 00:00:00 2001 From: Justin Hopper Date: Sun, 28 Nov 2021 16:32:02 -0600 Subject: [PATCH 102/228] Added missing CancellationToken parameters to Image --- src/ImageSharp/Image.FromFile.cs | 15 +++++++----- src/ImageSharp/Image.FromStream.cs | 39 ++++++++++++++++++------------ 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs index 3a4b459c5..fce0835fb 100644 --- a/src/ImageSharp/Image.FromFile.cs +++ b/src/ImageSharp/Image.FromFile.cs @@ -255,6 +255,7 @@ namespace SixLabors.ImageSharp /// /// The file path to the image. /// The decoder. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The path is null. /// The decoder is null. @@ -262,14 +263,15 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(string path, IImageDecoder decoder) - => LoadAsync(Configuration.Default, path, decoder, default); + public static Task LoadAsync(string path, IImageDecoder decoder, CancellationToken cancellationToken = default) + => LoadAsync(Configuration.Default, path, decoder, cancellationToken); /// /// Create a new instance of the class from the given file. /// /// The file path to the image. /// The decoder. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The path is null. /// The decoder is null. @@ -278,9 +280,9 @@ namespace SixLabors.ImageSharp /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(string path, IImageDecoder decoder) + public static Task> LoadAsync(string path, IImageDecoder decoder, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, path, decoder, default); + => LoadAsync(Configuration.Default, path, decoder, cancellationToken); /// /// Create a new instance of the class from the given file. @@ -342,6 +344,7 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given file. /// /// The file path to the image. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The path is null. /// Image format not recognised. @@ -349,9 +352,9 @@ namespace SixLabors.ImageSharp /// Image format is not supported. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(string path) + public static Task> LoadAsync(string path, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, path, default(CancellationToken)); + => LoadAsync(Configuration.Default, path, cancellationToken); /// /// Create a new instance of the class from the given file. diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index 291d6f7ca..f5e32d8ce 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -44,27 +44,29 @@ namespace SixLabors.ImageSharp /// By reading the header on the provided stream this calculates the images format type. /// /// The image stream to read the header from. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable. /// A representing the asynchronous operation or null if none is found. - public static Task DetectFormatAsync(Stream stream) - => DetectFormatAsync(Configuration.Default, stream); + public static Task DetectFormatAsync(Stream stream, CancellationToken cancellationToken = default) + => DetectFormatAsync(Configuration.Default, stream, cancellationToken); /// /// By reading the header on the provided stream this calculates the images format type. /// /// The configuration. /// The image stream to read the header from. + /// The token to monitor for cancellation requests. /// The configuration is null. /// The stream is null. /// The stream is not readable. /// A representing the asynchronous operation. - public static Task DetectFormatAsync(Configuration configuration, Stream stream) + public static Task DetectFormatAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken = default) => WithSeekableStreamAsync( configuration, stream, (s, _) => InternalDetectFormatAsync(s, configuration), - default); + cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -83,6 +85,7 @@ namespace SixLabors.ImageSharp /// Reads the raw image information from the specified stream without fully decoding it. /// /// The image stream to read the header from. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable. /// Image contains invalid content. @@ -90,8 +93,8 @@ namespace SixLabors.ImageSharp /// A representing the asynchronous operation or null if /// a suitable detector is not found. /// - public static Task IdentifyAsync(Stream stream) - => IdentifyAsync(Configuration.Default, stream); + public static Task IdentifyAsync(Stream stream, CancellationToken cancellationToken = default) + => IdentifyAsync(Configuration.Default, stream, cancellationToken); /// /// Reads the raw image information from the specified stream without fully decoding it. @@ -227,13 +230,14 @@ namespace SixLabors.ImageSharp /// The pixel format is selected by the decoder. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) - => LoadWithFormatAsync(Configuration.Default, stream); + public static Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) + => LoadWithFormatAsync(Configuration.Default, stream, cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -252,12 +256,14 @@ namespace SixLabors.ImageSharp /// The pixel format is selected by the decoder. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(Stream stream) => LoadAsync(Configuration.Default, stream); + public static Task LoadAsync(Stream stream, CancellationToken cancellationToken = default) + => LoadAsync(Configuration.Default, stream, cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -280,14 +286,15 @@ namespace SixLabors.ImageSharp /// /// The stream containing image information. /// The decoder. + /// The token to monitor for cancellation requests. /// The stream is null. /// The decoder is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// A representing the asynchronous operation. - public static Task LoadAsync(Stream stream, IImageDecoder decoder) - => LoadAsync(Configuration.Default, stream, decoder); + public static Task LoadAsync(Stream stream, IImageDecoder decoder, CancellationToken cancellationToken = default) + => LoadAsync(Configuration.Default, stream, decoder, cancellationToken); /// /// Decode a new instance of the class from the given stream. @@ -388,15 +395,16 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given stream. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static Task> LoadAsync(Stream stream) + public static Task> LoadAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => LoadAsync(Configuration.Default, stream); + => LoadAsync(Configuration.Default, stream, cancellationToken); /// /// Create a new instance of the class from the given stream. @@ -417,15 +425,16 @@ namespace SixLabors.ImageSharp /// Create a new instance of the class from the given stream. /// /// The stream containing image information. + /// The token to monitor for cancellation requests. /// The stream is null. /// The stream is not readable or the image format is not supported. /// Image format not recognised. /// Image contains invalid content. /// The pixel format. /// A representing the asynchronous operation. - public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream) + public static async Task<(Image Image, IImageFormat Format)> LoadWithFormatAsync(Stream stream, CancellationToken cancellationToken = default) where TPixel : unmanaged, IPixel - => await LoadWithFormatAsync(Configuration.Default, stream).ConfigureAwait(false); + => await LoadWithFormatAsync(Configuration.Default, stream, cancellationToken).ConfigureAwait(false); /// /// Create a new instance of the class from the given stream. From 81433c2f5254b9eb6e55b96c8898f6b036c4d99f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 29 Nov 2021 17:52:53 +1100 Subject: [PATCH 103/228] Remove more scalar bounds checks --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index ab64a8ddb..f12a1a785 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -542,14 +542,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (i = 0; i < 4; i++) { - int a0 = tmp[0 + i] + tmp[12 + i]; // 15b - int a1 = tmp[4 + i] + tmp[8 + i]; - int a2 = tmp[4 + i] - tmp[8 + i]; - int a3 = tmp[0 + i] - tmp[12 + i]; - output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b - output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); - output[8 + i] = (short)((a0 - a1 + 7) >> 4); + int t12 = tmp[12 + i]; // 15b + int t8 = tmp[8 + i]; + + int a1 = tmp[4 + i] + t8; + int a2 = tmp[4 + i] - t8; + int a0 = tmp[0 + i] + t12; // 15b + int a3 = tmp[0 + i] - t12; + output[12 + i] = (short)(((a3 * 2217) - (a2 * 5352) + 51000) >> 16); + output[8 + i] = (short)((a0 - a1 + 7) >> 4); + output[4 + i] = (short)((((a2 * 2217) + (a3 * 5352) + 12000) >> 16) + (a3 != 0 ? 1 : 0)); + output[0 + i] = (short)((a0 + a1 + 7) >> 4); // 12b } } } @@ -648,9 +652,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int inputIdx = 0; for (i = 0; i < 4; i++) { - int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b int a1 = input[inputIdx + (1 * 16)] + input[inputIdx + (3 * 16)]; int a2 = input[inputIdx + (1 * 16)] - input[inputIdx + (3 * 16)]; + int a0 = input[inputIdx + (0 * 16)] + input[inputIdx + (2 * 16)]; // 13b int a3 = input[inputIdx + (0 * 16)] - input[inputIdx + (2 * 16)]; tmp[3 + (i * 4)] = a0 - a1; tmp[2 + (i * 4)] = a3 - a2; @@ -662,18 +666,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (i = 0; i < 4; i++) { - int a0 = tmp[0 + i] + tmp[8 + i]; // 15b - int a1 = tmp[4 + i] + tmp[12 + i]; - int a2 = tmp[4 + i] - tmp[12 + i]; - int a3 = tmp[0 + i] - tmp[8 + i]; + int t12 = tmp[12 + i]; + int t8 = tmp[8 + i]; + + int a1 = tmp[4 + i] + t12; + int a2 = tmp[4 + i] - t12; + int a0 = tmp[0 + i] + t8; // 15b + int a3 = tmp[0 + i] - t8; + int b0 = a0 + a1; // 16b int b1 = a3 + a2; int b2 = a3 - a2; int b3 = a0 - a1; - output[0 + i] = (short)(b0 >> 1); // 15b - output[4 + i] = (short)(b1 >> 1); - output[8 + i] = (short)(b2 >> 1); + output[12 + i] = (short)(b3 >> 1); + output[8 + i] = (short)(b2 >> 1); + output[4 + i] = (short)(b1 >> 1); + output[0 + i] = (short)(b0 >> 1); // 15b } } From 383f455d3a5cda66ffab6af0f852c944a417b3ae Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 29 Nov 2021 15:07:09 +0100 Subject: [PATCH 104/228] Add AVX2 version of Vp8_Sse4X4 --- .../Formats/Webp/Lossy/LossyUtils.cs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index b8986f66f..8fa4ab7a1 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -32,15 +32,48 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static int Vp8_Sse4X4(Span a, Span b) { #if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + // Load values. + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + var a0 = Vector256.Create( + Unsafe.As>(ref aRef), + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps))); + var a1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)), + Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3))); + var b0 = Vector256.Create( + Unsafe.As>(ref bRef), + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps))); + var b1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)), + Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 3))); + + // Combine pair of lines. + Vector256 a01 = Avx2.UnpackLow(a0.AsInt32(), a1.AsInt32()); + Vector256 b01 = Avx2.UnpackLow(b0.AsInt32(), b1.AsInt32()); + + // Convert to 16b. + Vector256 a01s = Avx2.UnpackLow(a01.AsByte(), Vector256.Zero); + Vector256 b01s = Avx2.UnpackLow(b01.AsByte(), Vector256.Zero); + + // subtract, square and accumulate. + Vector256 d0 = Avx2.SubtractSaturate(a01s, b01s); + Vector256 e0 = Avx2.MultiplyAddAdjacent(d0.AsInt16(), d0.AsInt16()); + + return Numerics.ReduceSum(e0); + } + if (Sse2.IsSupported) { // Load values. ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); Vector128 a0 = Unsafe.As>(ref aRef); Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps)); Vector128 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 2)); Vector128 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, WebpConstants.Bps * 3)); - ref byte bRef = ref MemoryMarshal.GetReference(b); Vector128 b0 = Unsafe.As>(ref bRef); Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps)); Vector128 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, WebpConstants.Bps * 2)); @@ -67,7 +100,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Numerics.ReduceSum(sum); } - else #endif { return Vp8_SseNxN(a, b, 4, 4); From b52abeffc0ed37d7bd684944366cc1f2e08690bf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 29 Nov 2021 15:07:29 +0100 Subject: [PATCH 105/228] Remove not needed allocation --- src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index fcd61f2c0..eb8d92110 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -358,7 +358,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int kThreshold = 8 + ((17 - 8) * q / 100); int k; Span dc = stackalloc uint[16]; - Span tmp = stackalloc ushort[16]; uint m; uint m2; for (k = 0; k < 16; k += 4) From 837a37931d84165bb8b94375543796631c2426b0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 29 Nov 2021 15:18:01 +0100 Subject: [PATCH 106/228] Add test for Vp8Sse4X4 without AVX2 --- tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 907b18300..defd5af6c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -104,7 +104,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); [Fact] - public void Vp8Sse4X4_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableHWIntrinsic); + public void Vp8Sse4X4_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableSSE2); + + [Fact] + public void Vp8Sse4X4_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.DisableAVX2); [Fact] public void Mean16x4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunMean16x4Test, HwIntrinsics.AllowAll); From 8e515c14231a9b52d0dbc43959ced23283691458 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 29 Nov 2021 16:29:29 +0100 Subject: [PATCH 107/228] Rename ITransform to ITransformTwo to better reflect that actually two transforms take place --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 4 ++-- src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs | 2 +- tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index de6f807da..40ee923a0 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs @@ -329,7 +329,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy LossyUtils.TransformWht(dcTmp, tmp, scratch); for (n = 0; n < 16; n += 2) { - Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), scratch); + Vp8Encoding.ITransformTwo(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), scratch); } return nz; @@ -375,7 +375,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy for (n = 0; n < 8; n += 2) { - Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), scratch); + Vp8Encoding.ITransformTwo(reference.Slice(WebpLookupTables.Vp8ScanUv[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8ScanUv[n]), scratch); } return nz << 16; diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index f12a1a785..75acc4103 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Transforms (Paragraph 14.4) // Does two inverse transforms. - public static void ITransform(Span reference, Span input, Span dst, Span scratch) + public static void ITransformTwo(Span reference, Span input, Span dst, Span scratch) { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index 245e1cdc1..2a43cb38b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp int[] scratch = new int[16]; // act - Vp8Encoding.ITransform(reference, input, dst, scratch); + Vp8Encoding.ITransformTwo(reference, input, dst, scratch); // assert Assert.True(dst.SequenceEqual(expected)); From 7d1c3b579677ec4160689bb50e2b1fae86d76fab Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 29 Nov 2021 19:50:56 +0100 Subject: [PATCH 108/228] Remove test images, so they can be moved to LFS --- ...mage_L16_Gene-UP WebSocket RunImageMask.png | 3 --- ...eReferenceImage_L8_blackandwhite_binary.png | 3 --- ...deReferenceImage_L8_blackandwhite_plain.png | 3 --- ...enceImage_L8_grayscale_plain_normalized.png | 3 --- .../DecodeReferenceImage_L8_rings.png | 3 --- .../DecodeReferenceImage_Rgb24_00000_00000.png | 3 --- ...ferenceImage_Rgb24_rgb_plain_normalized.png | 3 --- tests/Images/Input/Pbm/00000_00000.ppm | 4 ---- .../Pbm/Gene-UP WebSocket RunImageMask.pgm | Bin 614417 -> 0 bytes .../Images/Input/Pbm/blackandwhite_binary.pbm | 3 --- tests/Images/Input/Pbm/blackandwhite_plain.pbm | 10 ---------- tests/Images/Input/Pbm/grayscale_plain.pgm | 10 ---------- .../Input/Pbm/grayscale_plain_normalized.pgm | 10 ---------- tests/Images/Input/Pbm/rgb_plain.ppm | 8 -------- .../Images/Input/Pbm/rgb_plain_normalized.ppm | 8 -------- tests/Images/Input/Pbm/rings.pgm | Bin 40038 -> 0 bytes 16 files changed, 74 deletions(-) delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png delete mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png delete mode 100644 tests/Images/Input/Pbm/00000_00000.ppm delete mode 100644 tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm delete mode 100644 tests/Images/Input/Pbm/blackandwhite_binary.pbm delete mode 100644 tests/Images/Input/Pbm/blackandwhite_plain.pbm delete mode 100644 tests/Images/Input/Pbm/grayscale_plain.pgm delete mode 100644 tests/Images/Input/Pbm/grayscale_plain_normalized.pgm delete mode 100644 tests/Images/Input/Pbm/rgb_plain.ppm delete mode 100644 tests/Images/Input/Pbm/rgb_plain_normalized.ppm delete mode 100644 tests/Images/Input/Pbm/rings.pgm diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png deleted file mode 100644 index 09bb074a3..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec -size 4521 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png deleted file mode 100644 index d1f1515bb..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9 -size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png deleted file mode 100644 index 372261923..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f -size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png deleted file mode 100644 index 9c86c2fc1..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 -size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png deleted file mode 100644 index acf751c28..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348 -size 27884 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png deleted file mode 100644 index 49cc74f3f..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf -size 2250 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png deleted file mode 100644 index 421a59849..000000000 --- a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 -size 152 diff --git a/tests/Images/Input/Pbm/00000_00000.ppm b/tests/Images/Input/Pbm/00000_00000.ppm deleted file mode 100644 index 321188762..000000000 --- a/tests/Images/Input/Pbm/00000_00000.ppm +++ /dev/null @@ -1,4 +0,0 @@ -P6 -29 30 -255 -KNPJLNVWTl^UtrnryysɻܫmpCARVbmY[aKOPDKKAEDBCBSTVPPRZYTk_{YQUZϧ~ѰɊʇR?NQ[ug\kTQVIMNLNKPPNNNPVUV]Z[xzkfD6ڹŕyݩbkbhberZ[^SSHJHIJENNJJKM[SSn\duQP[G㾵ؐFOL9oc}`ZKHBIJCIOJGJG`QHb`swIHrkϸدңΘˎy^mJH6"RHnnULJIKHP]\EJBxfTuaXA@Y_Ӂtujk``XVPNLJHED@A=?;>:y=9m=8x>7?1S=LDfkSPRJJJiknempmlB6A=@DAECGEJHOHNHNKNOOUW[_clky]`rkrlsorjqf}KHw_cHJJLIFh__{lmgrtw{}ɉ؎䐟쟵Ս|lalVPcQOZML_KJeJKTGKEEDJHBPLJo|xeq_kxbo}tttt``LLMPOTdkyiqgo```YYPWWRXVTRNNIFHIGGHGDJJHȻіoq\fJ\|f_ryXt]S^J;A9HGONIHCwvޮڳًaCBC}Pqe_nTSQOSQSURLLHNMGZWRţ~|kMWWJ[|T^xla~LVldŎ˱խýzy}сMQPZyhl]WW\a`Y[YPOLVSO[VQǎwWJJ:z`deuPOMRԝmwPmǟ}hڝYaG:qkvabkpp^a_QRPSSQ\[VaVUGvmslt@>gk٣ƺvWp^kf©zo|?9UTkmbhhY]\RUTRTT`b`r^daTwut@<ʁȾǕy˶nvճ۠@AWYmq]abX[ZUXXSUVZ\\|sqvpm}~|~x~}@<̔vq~~wǭƘ}AAZ[orX[[WZVUXTTWS[^Zlrthjndciffnhjfw}sD@pv󚗳lurxѵϑ?>^_jmVVTYZSVYQZ]T^aXlgHHX[él[[}Ĉuk۽ʺkl>4RNfg[YWZZUZ]W]`Y^aZĭëcgAAtq֨t~wjµļEOS?yor]\_\Z[[XWYUZ[V]]X­nvkxqyYOF8ɉt̰⡰_gckhplfjYTU^Z[a`bUWWUURXUQlvSk]n^q][BE*ʆhcK]cyX|YThTRV^^cWX_SUXOQPSUR|Y}}db|gI`Oj{Jm^OS?fOy_\K@7RTdrkhӇ裷Ähk]hkSeTOYPPSZ^bNQWLORHMNIQOU?OduOlKp`J_Fsw]d`lNRMI}MFQGVNoisjzcVWOKLRRT[_cMOUSTXJNQNUToĮX^:DO4mNveFlNNrUYx]b|l|jdhaztz~lkz[Y`NKPOORTXZLNOOOOMPPNTRLyWgnUW]M\|je^c}]h~bngmuy}}}{|wljy\[iMLUJJOPRRQRNRRNRSOQTPo|qkshjkdz}y}yziprnnhuoqup{okvjiuRS[FFJJJJOOKMNJNNI[[Wzpy|suvvruoothmrdkq`inbhmcgfekcKTKdla}[cinmasmugcm]Z`JHKKIIUTP^^Yab[cgc[_c[bgJUYDHKMJLTLM^QU]W^Z[`__bXVXJMNIRQLUQXaipmfumphah]V[PKOLHH]ZWge_nqh`hbUTW_ek=JOAGLEDESOMc[^ZW`RSXZZ\cbhQR]LPVNTVT_jfkhnnmb^ce^cWRWIGHQPMff_cfZZaY \ No newline at end of file diff --git a/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm b/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm deleted file mode 100644 index 8265eaa50621a9a5455776fbe2c5faeb5933b862..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 614417 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 UfB_6(00S7n00uCCfxLnL0M1c2-v9sr diff --git a/tests/Images/Input/Pbm/blackandwhite_binary.pbm b/tests/Images/Input/Pbm/blackandwhite_binary.pbm deleted file mode 100644 index a25b1d350..000000000 --- a/tests/Images/Input/Pbm/blackandwhite_binary.pbm +++ /dev/null @@ -1,3 +0,0 @@ -P4 -# CREATOR: bitmap2pbm Version 1.0.0 -8 4 @0@0 diff --git a/tests/Images/Input/Pbm/blackandwhite_plain.pbm b/tests/Images/Input/Pbm/blackandwhite_plain.pbm deleted file mode 100644 index fea8cafd0..000000000 --- a/tests/Images/Input/Pbm/blackandwhite_plain.pbm +++ /dev/null @@ -1,10 +0,0 @@ -P1 -# PBM example -24 7 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 -0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 -0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0 -0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 -0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/grayscale_plain.pgm b/tests/Images/Input/Pbm/grayscale_plain.pgm deleted file mode 100644 index ba4757248..000000000 --- a/tests/Images/Input/Pbm/grayscale_plain.pgm +++ /dev/null @@ -1,10 +0,0 @@ -P2 -24 7 -15 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 -0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 -0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 -0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 -0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm deleted file mode 100644 index fe0329629..000000000 --- a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm +++ /dev/null @@ -1,10 +0,0 @@ -P2 -24 7 -255 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0 51 51 51 51 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 255 255 255 0 -0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 255 0 -0 51 51 51 0 0 0 119 119 119 0 0 0 187 187 187 0 0 0 255 255 255 255 0 -0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 0 0 -0 51 0 0 0 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 0 0 0 0 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rgb_plain.ppm b/tests/Images/Input/Pbm/rgb_plain.ppm deleted file mode 100644 index ecd1b915c..000000000 --- a/tests/Images/Input/Pbm/rgb_plain.ppm +++ /dev/null @@ -1,8 +0,0 @@ -P3 -# example from the man page -4 4 -15 - 0 0 0 0 0 0 0 0 0 15 0 15 - 0 0 0 0 15 7 0 0 0 0 0 0 - 0 0 0 0 0 0 0 15 7 0 0 0 -15 0 15 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm deleted file mode 100644 index 628931579..000000000 --- a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm +++ /dev/null @@ -1,8 +0,0 @@ -P3 -# example from the man page -4 4 -255 - 0 0 0 0 0 0 0 0 0 255 0 255 - 0 0 0 0 255 119 0 0 0 0 0 0 - 0 0 0 0 0 0 0 255 119 0 0 0 -255 0 255 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rings.pgm b/tests/Images/Input/Pbm/rings.pgm deleted file mode 100644 index e0d4b4ed4d4cfc4da44b131a68f60824957428b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40038 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 Wa*AACLyF_*_)AQ-%Z&HTYW)|`Dr-*w From 2b00a22768ba81c709ecfec765e6374ca4c42349 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 29 Nov 2021 20:12:52 +0100 Subject: [PATCH 109/228] Put the test images back into LFS --- ...mage_L16_Gene-UP WebSocket RunImageMask.png | 3 +++ ...eReferenceImage_L8_blackandwhite_binary.png | 3 +++ ...deReferenceImage_L8_blackandwhite_plain.png | 3 +++ ...enceImage_L8_grayscale_plain_normalized.png | 3 +++ .../DecodeReferenceImage_L8_rings.png | 3 +++ .../DecodeReferenceImage_Rgb24_00000_00000.png | 3 +++ ...ferenceImage_Rgb24_rgb_plain_normalized.png | 3 +++ tests/Images/Input/Pbm/00000_00000.ppm | 4 ++++ .../Pbm/Gene-UP WebSocket RunImageMask.pgm | Bin 0 -> 614417 bytes .../Images/Input/Pbm/blackandwhite_binary.pbm | 3 +++ tests/Images/Input/Pbm/blackandwhite_plain.pbm | 10 ++++++++++ tests/Images/Input/Pbm/grayscale_plain.pgm | 10 ++++++++++ .../Input/Pbm/grayscale_plain_normalized.pgm | 10 ++++++++++ tests/Images/Input/Pbm/rgb_plain.ppm | 8 ++++++++ .../Images/Input/Pbm/rgb_plain_normalized.ppm | 8 ++++++++ tests/Images/Input/Pbm/rings.pgm | Bin 0 -> 40038 bytes 16 files changed, 74 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png create mode 100644 tests/Images/Input/Pbm/00000_00000.ppm create mode 100644 tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm create mode 100644 tests/Images/Input/Pbm/blackandwhite_binary.pbm create mode 100644 tests/Images/Input/Pbm/blackandwhite_plain.pbm create mode 100644 tests/Images/Input/Pbm/grayscale_plain.pgm create mode 100644 tests/Images/Input/Pbm/grayscale_plain_normalized.pgm create mode 100644 tests/Images/Input/Pbm/rgb_plain.ppm create mode 100644 tests/Images/Input/Pbm/rgb_plain_normalized.ppm create mode 100644 tests/Images/Input/Pbm/rings.pgm diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png new file mode 100644 index 000000000..09bb074a3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L16_Gene-UP WebSocket RunImageMask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78fc668be9f82c01c277cb2560253b04a1ff74a5af4daaf19327591420a71fec +size 4521 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png new file mode 100644 index 000000000..d1f1515bb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_binary.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1339a8170408a7bcde261617cc599587c8f25c4dc94f780976ee1638879888e9 +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png new file mode 100644 index 000000000..372261923 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_blackandwhite_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82d0397f38971cf90d7c064db332093e686196e244ece1196cca2071d27f0a6f +size 147 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png new file mode 100644 index 000000000..9c86c2fc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 +size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png new file mode 100644 index 000000000..acf751c28 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_rings.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:388c86b3dd472ef17fb911ae424b81baeeeff74c4161cf5825eab50698d54348 +size 27884 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png new file mode 100644 index 000000000..49cc74f3f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_00000_00000.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e3fc46b9f0546941ef95be7b750fb29376a679a921f2581403882b0e76e9caf +size 2250 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png new file mode 100644 index 000000000..421a59849 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain_normalized.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 +size 152 diff --git a/tests/Images/Input/Pbm/00000_00000.ppm b/tests/Images/Input/Pbm/00000_00000.ppm new file mode 100644 index 000000000..321188762 --- /dev/null +++ b/tests/Images/Input/Pbm/00000_00000.ppm @@ -0,0 +1,4 @@ +P6 +29 30 +255 +KNPJLNVWTl^UtrnryysɻܫmpCARVbmY[aKOPDKKAEDBCBSTVPPRZYTk_{YQUZϧ~ѰɊʇR?NQ[ug\kTQVIMNLNKPPNNNPVUV]Z[xzkfD6ڹŕyݩbkbhberZ[^SSHJHIJENNJJKM[SSn\duQP[G㾵ؐFOL9oc}`ZKHBIJCIOJGJG`QHb`swIHrkϸدңΘˎy^mJH6"RHnnULJIKHP]\EJBxfTuaXA@Y_Ӂtujk``XVPNLJHED@A=?;>:y=9m=8x>7?1S=LDfkSPRJJJiknempmlB6A=@DAECGEJHOHNHNKNOOUW[_clky]`rkrlsorjqf}KHw_cHJJLIFh__{lmgrtw{}ɉ؎䐟쟵Ս|lalVPcQOZML_KJeJKTGKEEDJHBPLJo|xeq_kxbo}tttt``LLMPOTdkyiqgo```YYPWWRXVTRNNIFHIGGHGDJJHȻіoq\fJ\|f_ryXt]S^J;A9HGONIHCwvޮڳًaCBC}Pqe_nTSQOSQSURLLHNMGZWRţ~|kMWWJ[|T^xla~LVldŎ˱խýzy}сMQPZyhl]WW\a`Y[YPOLVSO[VQǎwWJJ:z`deuPOMRԝmwPmǟ}hڝYaG:qkvabkpp^a_QRPSSQ\[VaVUGvmslt@>gk٣ƺvWp^kf©zo|?9UTkmbhhY]\RUTRTT`b`r^daTwut@<ʁȾǕy˶nvճ۠@AWYmq]abX[ZUXXSUVZ\\|sqvpm}~|~x~}@<̔vq~~wǭƘ}AAZ[orX[[WZVUXTTWS[^Zlrthjndciffnhjfw}sD@pv󚗳lurxѵϑ?>^_jmVVTYZSVYQZ]T^aXlgHHX[él[[}Ĉuk۽ʺkl>4RNfg[YWZZUZ]W]`Y^aZĭëcgAAtq֨t~wjµļEOS?yor]\_\Z[[XWYUZ[V]]X­nvkxqyYOF8ɉt̰⡰_gckhplfjYTU^Z[a`bUWWUURXUQlvSk]n^q][BE*ʆhcK]cyX|YThTRV^^cWX_SUXOQPSUR|Y}}db|gI`Oj{Jm^OS?fOy_\K@7RTdrkhӇ裷Ähk]hkSeTOYPPSZ^bNQWLORHMNIQOU?OduOlKp`J_Fsw]d`lNRMI}MFQGVNoisjzcVWOKLRRT[_cMOUSTXJNQNUToĮX^:DO4mNveFlNNrUYx]b|l|jdhaztz~lkz[Y`NKPOORTXZLNOOOOMPPNTRLyWgnUW]M\|je^c}]h~bngmuy}}}{|wljy\[iMLUJJOPRRQRNRRNRSOQTPo|qkshjkdz}y}yziprnnhuoqup{okvjiuRS[FFJJJJOOKMNJNNI[[Wzpy|suvvruoothmrdkq`inbhmcgfekcKTKdla}[cinmasmugcm]Z`JHKKIIUTP^^Yab[cgc[_c[bgJUYDHKMJLTLM^QU]W^Z[`__bXVXJMNIRQLUQXaipmfumphah]V[PKOLHH]ZWge_nqh`hbUTW_ek=JOAGLEDESOMc[^ZW`RSXZZ\cbhQR]LPVNTVT_jfkhnnmb^ce^cWRWIGHQPMff_cfZZaY \ No newline at end of file diff --git a/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm b/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm new file mode 100644 index 0000000000000000000000000000000000000000..8265eaa50621a9a5455776fbe2c5faeb5933b862 GIT binary patch literal 614417 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 UfB_6(00S7n00uCCfxLnL0M1c2-v9sr literal 0 HcmV?d00001 diff --git a/tests/Images/Input/Pbm/blackandwhite_binary.pbm b/tests/Images/Input/Pbm/blackandwhite_binary.pbm new file mode 100644 index 000000000..a25b1d350 --- /dev/null +++ b/tests/Images/Input/Pbm/blackandwhite_binary.pbm @@ -0,0 +1,3 @@ +P4 +# CREATOR: bitmap2pbm Version 1.0.0 +8 4 @0@0 diff --git a/tests/Images/Input/Pbm/blackandwhite_plain.pbm b/tests/Images/Input/Pbm/blackandwhite_plain.pbm new file mode 100644 index 000000000..fea8cafd0 --- /dev/null +++ b/tests/Images/Input/Pbm/blackandwhite_plain.pbm @@ -0,0 +1,10 @@ +P1 +# PBM example +24 7 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 +0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 +0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0 +0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 +0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/grayscale_plain.pgm b/tests/Images/Input/Pbm/grayscale_plain.pgm new file mode 100644 index 000000000..ba4757248 --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain.pgm @@ -0,0 +1,10 @@ +P2 +24 7 +15 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 +0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 +0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 +0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 +0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm new file mode 100644 index 000000000..fe0329629 --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm @@ -0,0 +1,10 @@ +P2 +24 7 +255 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +0 51 51 51 51 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 255 255 255 0 +0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 255 0 +0 51 51 51 0 0 0 119 119 119 0 0 0 187 187 187 0 0 0 255 255 255 255 0 +0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 0 0 +0 51 0 0 0 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 0 0 0 0 +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rgb_plain.ppm b/tests/Images/Input/Pbm/rgb_plain.ppm new file mode 100644 index 000000000..ecd1b915c --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain.ppm @@ -0,0 +1,8 @@ +P3 +# example from the man page +4 4 +15 + 0 0 0 0 0 0 0 0 0 15 0 15 + 0 0 0 0 15 7 0 0 0 0 0 0 + 0 0 0 0 0 0 0 15 7 0 0 0 +15 0 15 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm new file mode 100644 index 000000000..628931579 --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm @@ -0,0 +1,8 @@ +P3 +# example from the man page +4 4 +255 + 0 0 0 0 0 0 0 0 0 255 0 255 + 0 0 0 0 255 119 0 0 0 0 0 0 + 0 0 0 0 0 0 0 255 119 0 0 0 +255 0 255 0 0 0 0 0 0 0 0 0 \ No newline at end of file diff --git a/tests/Images/Input/Pbm/rings.pgm b/tests/Images/Input/Pbm/rings.pgm new file mode 100644 index 0000000000000000000000000000000000000000..e0d4b4ed4d4cfc4da44b131a68f60824957428b6 GIT binary patch literal 40038 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 Wa*AACLyF_*_)AQ-%Z&HTYW)|`Dr-*w literal 0 HcmV?d00001 From 23ee80be3104c55df1ee6c7b1a3265ed278cf267 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 29 Nov 2021 19:43:44 +0100 Subject: [PATCH 110/228] Add SSE2 version of TransformTwo --- .../Formats/Webp/Lossy/LossyUtils.cs | 161 +++++++++++++++++- .../Formats/Webp/Lossy/Vp8Encoding.cs | 4 +- 2 files changed, 160 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index b8986f66f..a46c0de4a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -17,6 +17,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { #if SUPPORTS_RUNTIME_INTRINSICS private static readonly Vector128 Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); + + private static readonly Vector128 K1 = Vector128.Create((short)20091); + + private static readonly Vector128 K2 = Vector128.Create((short)-30068); + + private static readonly Vector128 Four = Vector128.Create((short)4); + #endif // Note: method name in libwebp reference implementation is called VP8SSE16x16. @@ -788,10 +795,160 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } #endif + // Transforms (Paragraph 14.4). + // Does two transforms. public static void TransformTwo(Span src, Span dst, Span scratch) { - TransformOne(src, dst, scratch); - TransformOne(src.Slice(16), dst.Slice(4), scratch); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + + // Load and concatenate the transform coefficients (we'll do two transforms + // in parallel). In the case of only one transform, the second half of the + // vectors will just contain random value we'll never use nor store. + ref short srcRef = ref MemoryMarshal.GetReference(src); + var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); + + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + var inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 16)), 0); + var inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 20)), 0); + var inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 24)), 0); + var inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 28)), 0); + + in0 = Sse2.UnpackLow(in0, inb0); + in1 = Sse2.UnpackLow(in1, inb1); + in2 = Sse2.UnpackLow(in2, inb2); + in3 = Sse2.UnpackLow(in3, inb3); + + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); + Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); + + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3.AsInt16(), c4); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); + Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); + Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); + Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 dc = Sse2.Add(t0.AsInt16(), Four); + a = Sse2.Add(dc, t2.AsInt16()); + b = Sse2.Subtract(dc, t2.AsInt16()); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); + c2 = Sse2.MultiplyHigh(t1.AsInt16(), K1); + c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + c4 = Sse2.Subtract(c1, c2); + c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1); + d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); + d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + d4 = Sse2.Add(d1, d2); + d = Sse2.Add(d3, d4); + + // Second pass. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'dst' and store. + // Load the reference(s). + // Load eight bytes/pixels per line. + ref byte dstRef = ref MemoryMarshal.GetReference(dst); + Vector128 dst0 = Vector128.Create(Unsafe.As(ref dstRef), 0).AsByte(); + Vector128 dst1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps)), 0).AsByte(); + Vector128 dst2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2)), 0).AsByte(); + Vector128 dst3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3)), 0).AsByte(); + + // Convert to 16b. + dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); + dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); + dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); + dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); + + // Add the inverse transform(s). + dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); + dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); + dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); + dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); + + // Unsigned saturate to 8b. + dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); + dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); + dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); + dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); + + // Store the results. + // Store eight bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + Unsafe.As>(ref outputRef) = dst0.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = dst1.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = dst2.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = dst3.GetLower(); + } + else +#endif + { + TransformOne(src, dst, scratch); + TransformOne(src.Slice(16), dst.Slice(4), scratch); + } } public static void TransformOne(Span src, Span dst, Span scratch) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 75acc4103..65b1d07d3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -208,10 +208,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); - // Unsigned saturate to 8b. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - // Store eight bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); Unsafe.As>(ref outputRef) = ref0.GetLower(); Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1.GetLower(); Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2.GetLower(); From 9599efe5201dc516673000adecf1dd84b6976cf5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 29 Nov 2021 20:51:17 +0100 Subject: [PATCH 111/228] Update .gitattributes file --- .gitattributes | 8 ++++++-- shared-infrastructure | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 70ced6903..355b64dce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -87,7 +87,6 @@ *.eot binary *.exe binary *.otf binary -*.pbm binary *.pdf binary *.ppt binary *.pptx binary @@ -95,7 +94,6 @@ *.snk binary *.ttc binary *.ttf binary -*.wbmp binary *.woff binary *.woff2 binary *.xls binary @@ -126,3 +124,9 @@ *.dds filter=lfs diff=lfs merge=lfs -text *.ktx filter=lfs diff=lfs merge=lfs -text *.ktx2 filter=lfs diff=lfs merge=lfs -text +*.pam filter=lfs diff=lfs merge=lfs -text +*.pbm filter=lfs diff=lfs merge=lfs -text +*.pgm filter=lfs diff=lfs merge=lfs -text +*.ppm filter=lfs diff=lfs merge=lfs -text +*.pnm filter=lfs diff=lfs merge=lfs -text +*.wbmp filter=lfs diff=lfs merge=lfs -text diff --git a/shared-infrastructure b/shared-infrastructure index a042aba17..59ce17f5a 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 +Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 From 07cef0c5d1172288d56978eea67e22df39a0e0ac Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 29 Nov 2021 22:27:01 +0100 Subject: [PATCH 112/228] Add SSE2 version of TransformOne --- .../Formats/Webp/Lossy/LossyUtils.cs | 204 ++++++++++++++---- 1 file changed, 161 insertions(+), 43 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index a46c0de4a..8f21f19ed 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -820,8 +820,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x // Load and concatenate the transform coefficients (we'll do two transforms - // in parallel). In the case of only one transform, the second half of the - // vectors will just contain random value we'll never use nor store. + // in parallel). ref short srcRef = ref MemoryMarshal.GetReference(src); var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); @@ -883,7 +882,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); - c2 = Sse2.MultiplyHigh(t1.AsInt16(), K1); + c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); c4 = Sse2.Subtract(c1, c2); c = Sse2.Add(c3, c4); @@ -953,49 +952,168 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void TransformOne(Span src, Span dst, Span scratch) { - Span tmp = scratch.Slice(0, 16); - int tmpOffset = 0; - for (int srcOffset = 0; srcOffset < 4; srcOffset++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - // vertical pass - int srcOffsetPlus4 = srcOffset + 4; - int srcOffsetPlus8 = srcOffset + 8; - int srcOffsetPlus12 = srcOffset + 12; - int a = src[srcOffset] + src[srcOffsetPlus8]; - int b = src[srcOffset] - src[srcOffsetPlus8]; - int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); - int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); - tmp[tmpOffset++] = a + d; - tmp[tmpOffset++] = b + c; - tmp[tmpOffset++] = b - c; - tmp[tmpOffset++] = a - d; - } + // Load and concatenate the transform coefficients. + ref short srcRef = ref MemoryMarshal.GetReference(src); + var in0 = Vector128.Create(Unsafe.As(ref srcRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, 12)), 0); - // Each pass is expanding the dynamic range by ~3.85 (upper bound). - // The exact value is (2. + (20091 + 35468) / 65536). - // After the second pass, maximum interval is [-3794, 3794], assuming - // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. - // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. - tmpOffset = 0; - int dstOffset = 0; - for (int i = 0; i < 4; i++) + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + + // Vertical pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 a = Sse2.Add(in0.AsInt16(), in2.AsInt16()); + Vector128 b = Sse2.Subtract(in0.AsInt16(), in2.AsInt16()); + + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3.AsInt16(), c4); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a.AsInt16(), d); + Vector128 tmp1 = Sse2.Add(b.AsInt16(), c); + Vector128 tmp2 = Sse2.Subtract(b.AsInt16(), c); + Vector128 tmp3 = Sse2.Subtract(a.AsInt16(), d); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(tmp0, tmp1, tmp2, tmp3, out Vector128 t0, out Vector128 t1, out Vector128 t2, out Vector128 t3); + + // Horizontal pass and subsequent transpose. + // First pass, c and d calculations are longer because of the "trick" multiplications. + Vector128 dc = Sse2.Add(t0.AsInt16(), Four); + a = Sse2.Add(dc, t2.AsInt16()); + b = Sse2.Subtract(dc, t2.AsInt16()); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); + c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); + c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + c4 = Sse2.Subtract(c1, c2); + c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1); + d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); + d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + d4 = Sse2.Add(d1, d2); + d = Sse2.Add(d3, d4); + + // Second pass. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + Vector128 shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + Vector128 shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + Vector128 shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + Vector128 shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + + // Transpose the two 4x4. + Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'dst' and store. + // Load the reference(s). + // Load four bytes/pixels per line. + ref byte dstRef = ref MemoryMarshal.GetReference(dst); + Vector128 dst0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref dstRef)).AsByte(); + Vector128 dst1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps))).AsByte(); + Vector128 dst2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 2))).AsByte(); + Vector128 dst3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref dstRef, WebpConstants.Bps * 3))).AsByte(); + + // Convert to 16b. + dst0 = Sse2.UnpackLow(dst0, Vector128.Zero); + dst1 = Sse2.UnpackLow(dst1, Vector128.Zero); + dst2 = Sse2.UnpackLow(dst2, Vector128.Zero); + dst3 = Sse2.UnpackLow(dst3, Vector128.Zero); + + // Add the inverse transform(s). + dst0 = Sse2.Add(dst0.AsInt16(), t0.AsInt16()).AsByte(); + dst1 = Sse2.Add(dst1.AsInt16(), t1.AsInt16()).AsByte(); + dst2 = Sse2.Add(dst2.AsInt16(), t2.AsInt16()).AsByte(); + dst3 = Sse2.Add(dst3.AsInt16(), t3.AsInt16()).AsByte(); + + // Unsigned saturate to 8b. + dst0 = Sse2.PackUnsignedSaturate(dst0.AsInt16(), dst0.AsInt16()); + dst1 = Sse2.PackUnsignedSaturate(dst1.AsInt16(), dst1.AsInt16()); + dst2 = Sse2.PackUnsignedSaturate(dst2.AsInt16(), dst2.AsInt16()); + dst3 = Sse2.PackUnsignedSaturate(dst3.AsInt16(), dst3.AsInt16()); + + // Store the results. + // Store four bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + int output0 = Sse2.ConvertToInt32(dst0.AsInt32()); + int output1 = Sse2.ConvertToInt32(dst1.AsInt32()); + int output2 = Sse2.ConvertToInt32(dst2.AsInt32()); + int output3 = Sse2.ConvertToInt32(dst3.AsInt32()); + Unsafe.As(ref outputRef) = output0; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = output1; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = output2; + Unsafe.As(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = output3; + } + else +#endif { - // horizontal pass - int tmpOffsetPlus4 = tmpOffset + 4; - int tmpOffsetPlus8 = tmpOffset + 8; - int tmpOffsetPlus12 = tmpOffset + 12; - int dc = tmp[tmpOffset] + 4; - int a = dc + tmp[tmpOffsetPlus8]; - int b = dc - tmp[tmpOffsetPlus8]; - int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); - int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); - Store(dst.Slice(dstOffset), 0, 0, a + d); - Store(dst.Slice(dstOffset), 1, 0, b + c); - Store(dst.Slice(dstOffset), 2, 0, b - c); - Store(dst.Slice(dstOffset), 3, 0, a - d); - tmpOffset++; - - dstOffset += WebpConstants.Bps; + Span tmp = scratch.Slice(0, 16); + int tmpOffset = 0; + for (int srcOffset = 0; srcOffset < 4; srcOffset++) + { + // vertical pass + int srcOffsetPlus4 = srcOffset + 4; + int srcOffsetPlus8 = srcOffset + 8; + int srcOffsetPlus12 = srcOffset + 12; + int a = src[srcOffset] + src[srcOffsetPlus8]; + int b = src[srcOffset] - src[srcOffsetPlus8]; + int c = Mul2(src[srcOffsetPlus4]) - Mul1(src[srcOffsetPlus12]); + int d = Mul1(src[srcOffsetPlus4]) + Mul2(src[srcOffsetPlus12]); + tmp[tmpOffset++] = a + d; + tmp[tmpOffset++] = b + c; + tmp[tmpOffset++] = b - c; + tmp[tmpOffset++] = a - d; + } + + // Each pass is expanding the dynamic range by ~3.85 (upper bound). + // The exact value is (2. + (20091 + 35468) / 65536). + // After the second pass, maximum interval is [-3794, 3794], assuming + // an input in [-2048, 2047] interval. We then need to add a dst value in the [0, 255] range. + // In the worst case scenario, the input to clip_8b() can be as large as [-60713, 60968]. + tmpOffset = 0; + int dstOffset = 0; + for (int i = 0; i < 4; i++) + { + // horizontal pass + int tmpOffsetPlus4 = tmpOffset + 4; + int tmpOffsetPlus8 = tmpOffset + 8; + int tmpOffsetPlus12 = tmpOffset + 12; + int dc = tmp[tmpOffset] + 4; + int a = dc + tmp[tmpOffsetPlus8]; + int b = dc - tmp[tmpOffsetPlus8]; + int c = Mul2(tmp[tmpOffsetPlus4]) - Mul1(tmp[tmpOffsetPlus12]); + int d = Mul1(tmp[tmpOffsetPlus4]) + Mul2(tmp[tmpOffsetPlus12]); + Store(dst.Slice(dstOffset), 0, 0, a + d); + Store(dst.Slice(dstOffset), 1, 0, b + c); + Store(dst.Slice(dstOffset), 2, 0, b - c); + Store(dst.Slice(dstOffset), 3, 0, a - d); + tmpOffset++; + + dstOffset += WebpConstants.Bps; + } } } From 61c30de89e277a321d0a8a2dd189256cd1503a5d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 30 Nov 2021 11:56:56 +1100 Subject: [PATCH 113/228] Stub out approach --- src/ImageSharp/Formats/Png/PngDecoder.cs | 17 +++++++- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 42 +++++++++++--------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 479c24b7e..ea898b050 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -20,12 +20,25 @@ namespace SixLabors.ImageSharp.Formats.Png public Image Decode(Configuration configuration, Stream stream) where TPixel : unmanaged, IPixel { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.Decode(configuration, stream); } /// - public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream); + public Image Decode(Configuration configuration, Stream stream) + { + PngDecoderCore decoder = new(configuration, true); + IImageInfo info = decoder.Identify(configuration, stream); + stream.Position = 0; + + switch (info.Metadata.GetPngMetadata().ColorType) + { + default: + break; + } + + return this.Decode(configuration, stream); + } /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cf3cd7eb1..e741db72c 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -124,6 +124,13 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + internal PngDecoderCore(Configuration configuration, bool ignoreMetadata) + { + this.Configuration = configuration ?? Configuration.Default; + this.memoryAllocator = this.Configuration.MemoryAllocator; + this.ignoreMetadata = ignoreMetadata; + } + /// public Configuration Configuration { get; } @@ -168,12 +175,12 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.Palette: - var pal = new byte[chunk.Length]; + byte[] pal = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(pal); this.palette = pal; break; case PngChunkType.Transparency: - var alpha = new byte[chunk.Length]; + byte[] alpha = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); @@ -190,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Png case PngChunkType.Exif: if (!this.ignoreMetadata) { - var exifData = new byte[chunk.Length]; + byte[] exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } @@ -263,7 +270,7 @@ namespace SixLabors.ImageSharp.Formats.Png case PngChunkType.Exif: if (!this.ignoreMetadata) { - var exifData = new byte[chunk.Length]; + byte[] exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); metadata.ExifProfile = new ExifProfile(exifData); } @@ -364,11 +371,10 @@ namespace SixLabors.ImageSharp.Formats.Png /// The metadata to read to. /// The data containing physical data. private void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan data) - { + // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. // For example, a gamma of 1/2.2 would be stored as 45455. - pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F; - } + => pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F; /// /// Initializes the image and various buffers needed for processing @@ -477,19 +483,17 @@ namespace SixLabors.ImageSharp.Formats.Png private void ReadScanlines(PngChunk chunk, ImageFrame image, PngMetadata pngMetadata) where TPixel : unmanaged, IPixel { - using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk)) - { - deframeStream.AllocateNewBytes(chunk.Length, true); - DeflateStream dataStream = deframeStream.CompressedStream; + using var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk); + deframeStream.AllocateNewBytes(chunk.Length, true); + DeflateStream dataStream = deframeStream.CompressedStream; - if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) - { - this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); - } - else - { - this.DecodePixelData(dataStream, image, pngMetadata); - } + if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) + { + this.DecodeInterlacedPixelData(dataStream, image, pngMetadata); + } + else + { + this.DecodePixelData(dataStream, image, pngMetadata); } } From cc28bc00df506f5a9b3623e4ce999f9911965edd Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 30 Nov 2021 11:58:23 +1100 Subject: [PATCH 114/228] Use Rgb24 for Async --- src/ImageSharp/Formats/Jpeg/JpegDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index be03a7e7b..18212ffc7 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) .ConfigureAwait(false); /// From eb120ba1a516132e8a5679c4a26c807c269e3e8f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 30 Nov 2021 08:31:23 +0300 Subject: [PATCH 115/228] gfoidl const Size struct jit inlining in color converters --- .../Encoder/LuminanceForwardConverter{TPixel}.cs | 10 +++++----- .../Components/Encoder/RgbForwardConverter{TPixel}.cs | 10 +++++----- .../Encoder/YCbCrForwardConverter420{TPixel}.cs | 10 +++++----- .../Encoder/YCbCrForwardConverter444{TPixel}.cs | 10 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs index b6edfefed..6c402fcfd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs @@ -21,11 +21,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// private const int PixelsPerSample = 8 * 8; - /// - /// of sampling area from given frame pixel buffer. - /// - private static readonly Size SampleSize = new Size(8, 8); - /// /// The Y component /// @@ -62,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.config = frame.GetConfiguration(); } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(8, 8); + /// /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure () /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs index 0be1076e2..789277d7d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs @@ -26,11 +26,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// private const int RgbSpanByteSize = PixelsPerSample * 3; - /// - /// of sampling area from given frame pixel buffer. - /// - private static readonly Size SampleSize = new Size(8, 8); - /// /// The Red component. /// @@ -81,6 +76,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder this.config = frame.GetConfiguration(); } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(8, 8); + /// /// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs index bfeafcbb3..3a878f3c6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs @@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// private const int RgbSpanByteSize = PixelsPerSample * 3; - /// - /// of sampling area from given frame pixel buffer - /// - private static readonly Size SampleSize = new Size(16, 8); - /// /// The left Y component /// @@ -102,6 +97,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(16, 8); + public void Convert(int x, int y, ref RowOctet currentRows, int idx) { YCbCrForwardConverter.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs index 2dbd1a2dc..5f7725bdd 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs @@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// private const int RgbSpanByteSize = PixelsPerSample * 3; - /// - /// of sampling area from given frame pixel buffer - /// - private static readonly Size SampleSize = new Size(8, 8); - /// /// The Y component /// @@ -96,6 +91,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } } + /// + /// Gets size of sampling area from given frame pixel buffer. + /// + private static Size SampleSize => new(8, 8); + /// /// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (, , ) /// From 65bd0de7a21bb554f86f11009697e35957d19619 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 30 Nov 2021 17:44:23 +1100 Subject: [PATCH 116/228] Update PngDecoder.cs --- src/ImageSharp/Formats/Png/PngDecoder.cs | 63 ++++++++++++++++++++---- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index ea898b050..5637d5c7b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -31,39 +31,82 @@ namespace SixLabors.ImageSharp.Formats.Png IImageInfo info = decoder.Identify(configuration, stream); stream.Position = 0; - switch (info.Metadata.GetPngMetadata().ColorType) + PngMetadata meta = info.Metadata.GetPngMetadata(); + PngColorType color = meta.ColorType.GetValueOrDefault(); + PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); + return color switch { - default: - break; - } + PngColorType.Grayscale => (bits == PngBitDepth.Bit16) + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream), - return this.Decode(configuration, stream); + PngColorType.Rgb => this.Decode(configuration, stream), + + PngColorType.Palette => this.Decode(configuration, stream), + + PngColorType.GrayscaleWithAlpha => (bits == PngBitDepth.Bit16) + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream), + + PngColorType.RgbWithAlpha => (bits == PngBitDepth.Bit16) + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream), + + _ => this.Decode(configuration, stream), + }; } /// public Task> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.DecodeAsync(configuration, stream, cancellationToken); } /// public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); + { + PngDecoderCore decoder = new(configuration, true); + IImageInfo info = await decoder.IdentifyAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + stream.Position = 0; + + PngMetadata meta = info.Metadata.GetPngMetadata(); + PngColorType color = meta.ColorType.GetValueOrDefault(); + PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); + return color switch + { + PngColorType.Grayscale => (bits == PngBitDepth.Bit16) + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), + + PngColorType.Rgb => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), + + PngColorType.Palette => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), + + PngColorType.GrayscaleWithAlpha => (bits == PngBitDepth.Bit16) + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), + + PngColorType.RgbWithAlpha => (bits == PngBitDepth.Bit16) + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), + + _ => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), + }; + } /// public IImageInfo Identify(Configuration configuration, Stream stream) { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.Identify(configuration, stream); } /// public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { - var decoder = new PngDecoderCore(configuration, this); + PngDecoderCore decoder = new(configuration, this); return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } From 15f7889811baf9929931ce35ed509c81ca2e5eba Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 30 Nov 2021 10:36:10 +0300 Subject: [PATCH 117/228] Removed benchmarking code from test suite - we already have those in benchmark project --- .../Formats/Jpg/JpegColorConverterTests.cs | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 4e0970e3b..d6dc57e83 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -340,32 +340,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } - // Benchmark, for local execution only - // [Theory] - // [InlineData(false)] - // [InlineData(true)] - public void BenchmarkYCbCr(bool simd) - { - int count = 2053; - int times = 50000; - - JpegColorConverter.ComponentValues values = CreateRandomValues(3, count, 1); - var result = new Vector4[count]; - - JpegColorConverter converter = simd ? (JpegColorConverter)new JpegColorConverter.FromYCbCrVector4(8) : new JpegColorConverter.FromYCbCrBasic(8); - - // Warm up: - converter.ConvertToRgbInplace(values); - - using (new MeasureGuard(this.Output, $"{converter.GetType().Name} x {times}")) - { - for (int i = 0; i < times; i++) - { - converter.ConvertToRgbInplace(values); - } - } - } - private static JpegColorConverter.ComponentValues CreateRandomValues( int componentCount, int inputBufferLength, From 717b166e41252a36bd04ba528c1b8a7f7238b095 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 30 Nov 2021 10:42:08 +0300 Subject: [PATCH 118/228] Removed unnecessary small allocation from color post processing, removed test benchmark call from sandbox project --- .../ColorConverters/JpegColorConverter.cs | 17 +++++++++++++++++ .../Decoder/JpegComponentPostProcessor.cs | 3 +++ .../Decoder/SpectralConverter{TPixel}.cs | 6 +----- .../Program.cs | 6 ------ 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs index 11ea4cda8..dad46861e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs @@ -197,6 +197,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// public readonly Span Component3; + /// + /// Initializes a new instance of the struct. + /// + /// The 1-4 sized list of component post processors. + /// The row to convert + public ComponentValues(IReadOnlyList componentProcessors, int row) + { + this.ComponentCount = componentProcessors.Count; + + this.Component0 = componentProcessors[0].GetColorBufferRowSpan(row); + + // In case of grayscale, Component1 and Component2 point to Component0 memory area + this.Component1 = this.ComponentCount > 1 ? componentProcessors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? componentProcessors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? componentProcessors[3].GetColorBufferRowSpan(row) : Span.Empty; + } + /// /// Initializes a new instance of the struct. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs index b87d74720..3e04e80b7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs @@ -122,5 +122,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder spectralBlocks.GetRowSpan(i).Clear(); } } + + public Span GetColorBufferRowSpan(int row) => + this.ColorBuffer.GetRowSpan(row); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 2e965e0ac..722570919 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Linq; -using System.Numerics; using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; @@ -23,7 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private JpegColorConverter colorConverter; - // private IMemoryOwner rgbaBuffer; private IMemoryOwner rgbBuffer; private IMemoryOwner paddedProxyPixelRow; @@ -121,11 +119,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); - var buffers = new Buffer2D[this.componentProcessors.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep); - buffers[i] = this.componentProcessors[i].ColorBuffer; } int width = this.pixelBuffer.Width; @@ -134,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int y = yy - this.pixelRowCounter; - var values = new JpegColorConverter.ComponentValues(buffers, y); + var values = new JpegColorConverter.ComponentValues(this.componentProcessors, y); this.colorConverter.ConvertToRgbInplace(values); values = values.Slice(0, width); // slice away Jpeg padding diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs index e6e82b981..079d24c47 100644 --- a/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs +++ b/tests/ImageSharp.Tests.ProfilingSandbox/Program.cs @@ -47,12 +47,6 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox benchmarks.EncodeJpeg_SingleMidSize(); } - private static void RunJpegColorProfilingTests() - { - new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(false); - new JpegColorConverterTests(new ConsoleOutput()).BenchmarkYCbCr(true); - } - private static void RunResizeProfilingTest() { var test = new ResizeProfilingBenchmarks(new ConsoleOutput()); From 13602169c0d1ac194f5efc93db419f32189905ad Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 30 Nov 2021 12:53:34 +0300 Subject: [PATCH 119/228] Moved cancellation token to a more appropriate place --- .../Components/Decoder/SpectralConverter{TPixel}.cs | 11 +++-------- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 7 +++++-- .../Compression/Decompressors/JpegTiffCompression.cs | 6 +++--- .../Decompressors/RgbJpegSpectralConverter.cs | 4 ++-- .../Formats/Jpg/SpectralToPixelConversionTests.cs | 5 +++-- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 722570919..ad19f476f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -16,8 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { private readonly Configuration configuration; - private readonly CancellationToken cancellationToken; - private JpegComponentPostProcessor[] componentProcessors; private JpegColorConverter colorConverter; @@ -32,13 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private int pixelRowCounter; - public SpectralConverter(Configuration configuration, CancellationToken cancellationToken) - { + public SpectralConverter(Configuration configuration) => this.configuration = configuration; - this.cancellationToken = cancellationToken; - } - public Buffer2D GetPixelBuffer() + public Buffer2D GetPixelBuffer(CancellationToken cancellationToken) { if (!this.Converted) { @@ -46,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int step = 0; step < steps; step++) { - this.cancellationToken.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); this.ConvertStride(step); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 73763f4ab..4be6731cc 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - using var spectralConverter = new SpectralConverter(this.Configuration, cancellationToken); + using var spectralConverter = new SpectralConverter(this.Configuration); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken); @@ -185,7 +185,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitIptcProfile(); this.InitDerivedMetadataProperties(); - return new Image(this.Configuration, spectralConverter.GetPixelBuffer(), this.Metadata); + return new Image( + this.Configuration, + spectralConverter.GetPixelBuffer(cancellationToken), + this.Metadata); } /// diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs index 9a0607584..c64fc8ad1 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs @@ -55,17 +55,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder()); - // TODO: Should we pass through the CancellationToken from the tiff decoder? // If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space. // There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB). using SpectralConverter spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ? - new RgbJpegSpectralConverter(this.configuration, CancellationToken.None) : new SpectralConverter(this.configuration, CancellationToken.None); + new RgbJpegSpectralConverter(this.configuration) : new SpectralConverter(this.configuration); var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None); jpegDecoder.LoadTables(this.jpegTables, scanDecoder); scanDecoder.ResetInterval = 0; jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); - CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer()); + // TODO: Should we pass through the CancellationToken from the tiff decoder? + CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None)); } else { diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index aefec7fa3..7b3b67a64 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -22,8 +22,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// /// The configuration. /// The cancellation token. - public RgbJpegSpectralConverter(Configuration configuration, CancellationToken cancellationToken) - : base(configuration, cancellationToken) + public RgbJpegSpectralConverter(Configuration configuration) + : base(configuration) { } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs index f0f92d763..0071c623c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/SpectralToPixelConversionTests.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; +using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.IO; @@ -43,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); // Decoding - using var converter = new SpectralConverter(Configuration.Default, cancellationToken: default); + using var converter = new SpectralConverter(Configuration.Default); var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); var scanDecoder = new HuffmanScanDecoder(bufferedStream, converter, cancellationToken: default); decoder.ParseStream(bufferedStream, scanDecoder, cancellationToken: default); @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg provider.Utility.TestName = JpegDecoderTests.DecodeBaselineJpegOutputName; // Comparison - using (Image image = new Image(Configuration.Default, converter.GetPixelBuffer(), new ImageMetadata())) + using (Image image = new Image(Configuration.Default, converter.GetPixelBuffer(CancellationToken.None), new ImageMetadata())) using (Image referenceImage = provider.GetReferenceOutputImage(appendPixelTypeToFileName: false)) { ImageSimilarityReport report = ImageComparer.Exact.CompareImagesOrFrames(referenceImage, image); From 83caa30b9627dd888d4c81e83826c99a28c08ef2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 30 Nov 2021 11:14:41 +0100 Subject: [PATCH 120/228] Add tests for TransformOne and TransformTwo --- .../Formats/WebP/LossyUtilsTests.cs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 907b18300..5c58ea67b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -11,8 +11,74 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class LossyUtilsTests { + private static void RunTransformTwoTest() + { + // arrange + short[] src = { 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 23, 0, 0, -23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = + { + 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, + 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, + 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, + 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, + 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 103, 103, 103, 103, + 103, 103, 103, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + byte[] expected = + { + 105, 105, 105, 105, 105, 103, 100, 98, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, + 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 108, 105, 102, 100, 0, 0, 0, 0, 169, + 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, + 105, 105, 111, 109, 106, 103, 0, 0, 0, 0, 169, 169, 169, 169, 171, 171, 171, 171, 171, 171, 171, + 171, 0, 0, 0, 0, 103, 103, 103, 103, 105, 105, 105, 105, 113, 111, 108, 106, 0, 0, 0, 0, 169, 169, + 169, 169, 171, 171, 171, 171, 171, 171, 171, 171, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int[] scratch = new int[16]; + + // act + LossyUtils.TransformTwo(src, dst, scratch); + + // assert + Assert.True(expected.SequenceEqual(dst)); + } + + private static void RunTransformOneTest() + { + // arrange + short[] src = { -176, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = + { + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129 + }; + byte[] expected = + { + 111, 111, 111, 111, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 108, 108, 108, 108, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, + 0, 0, 0, 129, 104, 104, 104, 104, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 101, 101, 101, 101, + 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 129 + }; + int[] scratch = new int[16]; + + // act + LossyUtils.TransformOne(src, dst, scratch); + + // assert + Assert.True(expected.SequenceEqual(dst)); + } + private static void RunVp8Sse4X4Test() { + // arrange byte[] a = { 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, @@ -35,8 +101,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp int expected = 27; + // act int actual = LossyUtils.Vp8_Sse4X4(a, b); + // assert Assert.Equal(expected, actual); } @@ -65,6 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp private static void RunHadamardTransformTest() { + // arrange byte[] a = { 27, 27, 28, 29, 29, 28, 27, 27, 27, 28, 28, 29, 29, 28, 28, 27, 129, 129, 129, 129, 129, 129, 129, @@ -86,10 +155,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp ushort[] w = { 38, 32, 20, 9, 32, 28, 17, 7, 20, 17, 10, 4, 9, 7, 4, 2 }; int expected = 2; + // act int actual = LossyUtils.Vp8Disto4X4(a, b, w, new int[16]); + + // assert Assert.Equal(expected, actual); } + [Fact] + public void RunTransformTwo_Works() => RunTransformTwoTest(); + + [Fact] + public void RunTransformOne_Works() => RunTransformOneTest(); + [Fact] public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test(); @@ -100,6 +178,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void HadamardTransform_Works() => RunHadamardTransformTest(); #if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void TransformTwo_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.AllowAll); + + [Fact] + public void TransformTwo_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformTwoTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void TransformOne_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.AllowAll); + + [Fact] + public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); From e9a25c74e0e1b8685306693f6987c38f8fd9201b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 30 Nov 2021 13:22:48 +0300 Subject: [PATCH 121/228] Added xml docs to the SpectralConverter --- .../Components/Decoder/SpectralConverter.cs | 13 ++- .../Decoder/SpectralConverter{TPixel}.cs | 83 +++++++++++++++---- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index 4e74f6226..1eb74ff80 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -6,11 +6,8 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { /// - /// Converter used to convert jpeg spectral data. + /// Converter used to convert jpeg spectral data to color pixels. /// - /// - /// This is tightly coupled with and . - /// internal abstract class SpectralConverter { /// @@ -30,12 +27,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData); /// - /// Called once per spectral stride for each component in . - /// This is called only for baseline interleaved jpegs. + /// Converts single spectral jpeg stride to color stride in baseline + /// decoding mode. /// /// + /// Called once per decoded spectral stride in + /// only for baseline interleaved jpeg images. /// Spectral 'stride' doesn't particularly mean 'single stride'. - /// Actual stride height depends on the subsampling factor of the given component. + /// Actual stride height depends on the subsampling factor of the given image. /// public abstract void ConvertStrideBaseline(); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index ad19f476f..0003437e7 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -11,28 +11,77 @@ using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { + /// + /// + /// Color decoding scheme: + /// + /// + /// 1. Decode spectral data to Jpeg color space + /// 2. Convert from Jpeg color space to RGB + /// 3. Convert from RGB to target pixel space + /// + /// + /// internal class SpectralConverter : SpectralConverter, IDisposable where TPixel : unmanaged, IPixel { + /// + /// instance associated with current + /// decoding routine. + /// private readonly Configuration configuration; + /// + /// Jpeg component converters from decompressed spectral to color data. + /// private JpegComponentPostProcessor[] componentProcessors; + /// + /// Color converter from jpeg color space to target pixel color space. + /// private JpegColorConverter colorConverter; + /// + /// Intermediate buffer of RGB components used in color conversion. + /// private IMemoryOwner rgbBuffer; + /// + /// Proxy buffer used in packing from RGB to target TPixel pixels. + /// private IMemoryOwner paddedProxyPixelRow; + /// + /// Resulting 2D pixel buffer. + /// private Buffer2D pixelBuffer; + /// + /// How many pixel rows are processed in one 'stride'. + /// private int pixelRowsPerStep; + /// + /// How many pixel rows were processed. + /// private int pixelRowCounter; + /// + /// Initializes a new instance of the class. + /// + /// The configuration. public SpectralConverter(Configuration configuration) => this.configuration = configuration; + /// + /// Gets converted pixel buffer. + /// + /// + /// For non-baseline interleaved jpeg this method does a 'lazy' spectral + /// conversion from spectral to color. + /// + /// Cancellation token. + /// Pixel buffer. public Buffer2D GetPixelBuffer(CancellationToken cancellationToken) { if (!this.Converted) @@ -95,21 +144,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } - /// - public void Dispose() - { - if (this.componentProcessors != null) - { - foreach (JpegComponentPostProcessor cpp in this.componentProcessors) - { - cpp.Dispose(); - } - } - - this.rgbBuffer?.Dispose(); - this.paddedProxyPixelRow?.Dispose(); - } - + /// + /// Converts single spectral jpeg stride to color stride. + /// + /// Spectral stride index. private void ConvertStride(int spectralStep) { int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); @@ -155,5 +193,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.pixelRowCounter += this.pixelRowsPerStep; } + + /// + public void Dispose() + { + if (this.componentProcessors != null) + { + foreach (JpegComponentPostProcessor cpp in this.componentProcessors) + { + cpp.Dispose(); + } + } + + this.rgbBuffer?.Dispose(); + this.paddedProxyPixelRow?.Dispose(); + } } } From 7d524bda2be5eeafef4cd6d5d723e93159a50092 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 30 Nov 2021 14:03:39 +0300 Subject: [PATCH 122/228] Fixed compilation error --- .../Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index 7b3b67a64..3b5833c10 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -21,7 +21,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors /// This Spectral converter will always convert the pixel data to RGB color. /// /// The configuration. - /// The cancellation token. public RgbJpegSpectralConverter(Configuration configuration) : base(configuration) { From ad4b0c509f077673fe78a17bdf9229e04d98603b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 30 Nov 2021 19:10:28 +0100 Subject: [PATCH 123/228] Add SSE2 version of DoFilter2 --- .../Formats/Webp/Lossy/LossyUtils.cs | 121 +++++++++++++++++- 1 file changed, 116 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 8fa4ab7a1..04632ded8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -932,13 +932,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Simple In-loop filtering (Paragraph 15.2) public static void SimpleVFilter16(Span p, int offset, int stride, int thresh) { - int thresh2 = (2 * thresh) + 1; - int end = 16 + offset; - for (int i = offset; i < end; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // Load. + ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); + + Vector128 p1 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, 2 * stride)); + Vector128 p0 = Unsafe.As>(ref Unsafe.Subtract(ref pRef, stride)); + Vector128 q0 = Unsafe.As>(ref pRef); + Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, stride)); + + DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); + + // Store. + ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset); + Unsafe.As>(ref Unsafe.Subtract(ref outputRef, stride)) = p0.AsSByte(); + Unsafe.As>(ref outputRef) = q0.AsSByte(); + } + else +#endif { - if (NeedsFilter(p, i, stride, thresh2)) + int thresh2 = (2 * thresh) + 1; + int end = 16 + offset; + for (int i = offset; i < end; i++) { - DoFilter2(p, i, stride); + if (NeedsFilter(p, i, stride, thresh2)) + { + DoFilter2(p, i, stride); + } } } } @@ -1185,6 +1207,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } + // Applies filter on 2 pixels (p0 and q0) private static void DoFilter2(Span p, int offset, int step) { // 4 pixels in, 2 pixels out. @@ -1199,6 +1222,47 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy p[offset] = WebpLookupTables.Clip1(q0 - a1); } +#if SUPPORTS_RUNTIME_INTRINSICS + // Applies filter on 2 pixels (p0 and q0) + private static void DoFilter2Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int thresh) + { + var signBit = Vector128.Create((byte)0x80); + + // Convert p1/q1 to byte (for GetBaseDeltaSse2). + Vector128 p1s = Sse2.Xor(p1, signBit); + Vector128 q1s = Sse2.Xor(q1, signBit); + Vector128 mask = NeedsFilterSse2(p1, p0, q0, q1, thresh); + + // Flip sign. + p0 = Sse2.Xor(p0, signBit); + q0 = Sse2.Xor(q0, signBit); + + Vector128 a = GetBaseDeltaSse2(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); + + // Mask filter values we don't care about. + a = Sse2.And(a, mask); + + DoSimpleFilterSse2(ref p0, ref q0, a); + + // Flip sign. + p0 = Sse2.Xor(p0, signBit); + q0 = Sse2.Xor(q0, signBit); + } + + private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) + { + Vector128 three = Vector128.Create((byte)3).AsSByte(); + Vector128 four = Vector128.Create((byte)4).AsSByte(); + Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), three); + Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), four); + + v4 = SignedShift8bSse2(v4.AsByte()).AsSByte(); // v4 >> 3 + v3 = SignedShift8bSse2(v3.AsByte()).AsSByte(); // v3 >> 3 + q0 = Sse2.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4 + p0 = Sse2.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3 + } +#endif + private static void DoFilter4(Span p, int offset, int step) { // 4 pixels in, 4 pixels out. @@ -1275,6 +1339,53 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy WebpLookupTables.Abs0(q2 - q1) <= it && WebpLookupTables.Abs0(q1 - q0) <= it; } +#if SUPPORTS_RUNTIME_INTRINSICS + private static Vector128 NeedsFilterSse2(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh) + { + var mthresh = Vector128.Create((byte)thresh); + Vector128 t1 = Abs(p1, q1); // abs(p1 - q1) + var fe = Vector128.Create((byte)0xFE); + Vector128 t2 = Sse2.And(t1, fe); // set lsb of each byte to zero. + Vector128 t3 = Sse2.ShiftRightLogical(t2.AsInt16(), 1); // abs(p1 - q1) / 2 + + Vector128 t4 = Abs(p0, q0); // abs(p0 - q0) + Vector128 t5 = Sse2.AddSaturate(t4, t4); // abs(p0 - q0) * 2 + Vector128 t6 = Sse2.AddSaturate(t5.AsByte(), t3.AsByte()); // abs(p0-q0)*2 + abs(p1-q1)/2 + + Vector128 t7 = Sse2.SubtractSaturate(t6, mthresh.AsByte()); // mask <= m_thresh + + return Sse2.CompareEqual(t7, Vector128.Zero); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 GetBaseDeltaSse2(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) + { + // Beware of addition order, for saturation! + Vector128 p1q1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1 + Vector128 q0p0 = Sse2.SubtractSaturate(q0, p0); // q0 - p0 + Vector128 s1 = Sse2.AddSaturate(p1q1, q0p0); // p1 - q1 + 1 * (q0 - p0) + Vector128 s2 = Sse2.AddSaturate(q0p0, s1); // p1 - q1 + 2 * (q0 - p0) + Vector128 s3 = Sse2.AddSaturate(q0p0, s2); // p1 - q1 + 3 * (q0 - p0) + + return s3; + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 SignedShift8bSse2(Vector128 x) + { + Vector128 low0 = Sse2.UnpackLow(Vector128.Zero, x); + Vector128 high0 = Sse2.UnpackHigh(Vector128.Zero, x); + Vector128 low1 = Sse2.ShiftRightArithmetic(low0.AsInt16(), 3 + 8); + Vector128 high1 = Sse2.ShiftRightArithmetic(high0.AsInt16(), 3 + 8); + + return Sse2.PackSignedSaturate(low1, high1); + } + + // Compute abs(p - q) = subs(p - q) OR subs(q - p) + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 Abs(Vector128 p, Vector128 q) => Sse2.Or(Sse2.SubtractSaturate(q, p), Sse2.SubtractSaturate(p, q)); +#endif + [MethodImpl(InliningOptions.ShortMethod)] private static bool Hev(Span p, int offset, int step, int thresh) { From cf8e1841fc757faf77b87751d13715891be43d6c Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 30 Nov 2021 21:23:04 +0100 Subject: [PATCH 124/228] Revert stack allocation in Plain Encoder --- src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index e362f8680..b67f0a077 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -15,7 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// internal class PlainEncoder { - private const int MaxLineLength = 70; private const byte NewLine = 0x0a; private const byte Space = 0x20; private const byte Zero = 0x30; @@ -77,7 +76,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - Span plainSpan = stackalloc byte[width * MaxCharsPerPixelGrayscale]; + IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscale); + Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) { @@ -108,7 +108,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - Span plainSpan = stackalloc byte[width * MaxCharsPerPixelGrayscaleWide]; + IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscaleWide); + Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) { @@ -139,7 +140,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - Span plainSpan = stackalloc byte[width * MaxCharsPerPixelRgb]; + IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgb); + Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) { @@ -176,7 +178,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - Span plainSpan = stackalloc byte[width * MaxCharsPerPixelRgbWide]; + IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgbWide); + Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) { @@ -213,7 +216,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - Span plainSpan = stackalloc byte[width * MaxCharsPerPixelBlackAndWhite]; + IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelBlackAndWhite); + Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) { From 7617b38fec1246c664c172cc3e5cd2468fd86535 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 1 Dec 2021 07:39:28 +0300 Subject: [PATCH 125/228] Base implementation for new converters --- ...egColorConverter.Avx2JpegColorConverter.cs | 18 --- ...gColorConverter.BasicJpegColorConverter.cs | 18 --- ...2.cs => JpegColorConverter.FromCmykAvx.cs} | 28 ++--- ...s => JpegColorConverter.FromCmykScalar.cs} | 9 +- .../JpegColorConverter.FromCmykVector8.cs | 25 ++--- ...=> JpegColorConverter.FromGrayScaleAvx.cs} | 22 ++-- .../JpegColorConverter.FromGrayScaleBasic.cs | 53 --------- .../JpegColorConverter.FromGrayScaleScalar.cs | 34 ++++++ ...JpegColorConverter.FromGrayScaleVector8.cs | 38 +++++++ ...x2.cs => JpegColorConverter.FromRgbAvx.cs} | 28 ++--- .../JpegColorConverter.FromRgbBasic.cs | 32 ------ .../JpegColorConverter.FromRgbScalar.cs | 26 +++++ .../JpegColorConverter.FromRgbVector8.cs | 11 +- ....cs => JpegColorConverter.FromYCbCrAvx.cs} | 33 ++---- .../JpegColorConverter.FromYCbCrBasic.cs | 42 ------- .../JpegColorConverter.FromYCbCrScalar.cs | 50 +++++++++ .../JpegColorConverter.FromYCbCrVector4.cs | 88 --------------- .../JpegColorConverter.FromYCbCrVector8.cs | 22 ++-- ...2.cs => JpegColorConverter.FromYccKAvx.cs} | 30 ++--- ...s => JpegColorConverter.FromYccKScalar.cs} | 13 +-- .../JpegColorConverter.FromYccKVector8.cs | 28 +++-- ...olorConverter.Vector8JpegColorConverter.cs | 18 --- .../ColorConverters/JpegColorConverterAvx.cs | 32 ++++++ ...Converter.cs => JpegColorConverterBase.cs} | 104 +++++++++--------- .../JpegColorConverterScalar.cs | 22 ++++ ...nverter.cs => JpegColorConverterVector.cs} | 27 +++-- .../Components/Decoder/SpectralConverter.cs | 2 +- .../Decoder/SpectralConverter{TPixel}.cs | 4 +- .../Decompressors/RgbJpegSpectralConverter.cs | 2 +- .../ColorConversion/CmykColorConversion.cs | 12 +- .../GrayscaleColorConversion.cs | 8 +- .../ColorConversion/RgbColorConversion.cs | 12 +- .../ColorConversion/YCbCrColorConversion.cs | 16 +-- .../ColorConversion/YccKColorConverter.cs | 12 +- .../Formats/Jpg/JpegColorConverterTests.cs | 76 +++++-------- 35 files changed, 432 insertions(+), 563 deletions(-) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromCmykAvx2.cs => JpegColorConverter.FromCmykAvx.cs} (58%) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromCmykBasic.cs => JpegColorConverter.FromCmykScalar.cs} (83%) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromGrayScaleAvx2.cs => JpegColorConverter.FromGrayScaleAvx.cs} (62%) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector8.cs rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromRgbAvx2.cs => JpegColorConverter.FromRgbAvx.cs} (56%) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromYCbCrAvx2.cs => JpegColorConverter.FromYCbCrAvx.cs} (71%) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromYccKAvx2.cs => JpegColorConverter.FromYccKAvx.cs} (79%) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromYccKBasic.cs => JpegColorConverter.FromYccKScalar.cs} (81%) delete mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.cs => JpegColorConverterBase.cs} (67%) create mode 100644 src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.VectorizedJpegColorConverter.cs => JpegColorConverterVector.cs} (64%) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs deleted file mode 100644 index 90ebce3b8..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Avx2JpegColorConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal abstract class Avx2JpegColorConverter : VectorizedJpegColorConverter - { - protected Avx2JpegColorConverter(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision, 8) - { - } - - protected sealed override bool IsAvailable => SimdUtils.HasAvx2; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs deleted file mode 100644 index ed2e2cd76..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.BasicJpegColorConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal abstract class BasicJpegColorConverter : JpegColorConverter - { - protected BasicJpegColorConverter(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision) - { - } - - protected override bool IsAvailable => true; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs similarity index 58% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 216c12735..2671dec70 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -1,38 +1,33 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykAvx2 : Avx2JpegColorConverter + internal sealed class FromCmykAvx : AvxColorConverter { - public FromCmykAvx2(int precision) + public FromCmykAvx(int precision) : base(JpegColorSpace.Cmyk, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 c0Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector256 c1Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); ref Vector256 c2Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); ref Vector256 c3Base = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); // Used for the color conversion var scale = Vector256.Create(1 / this.MaximumValue); @@ -50,11 +45,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters m = Avx.Multiply(Avx.Multiply(m, k), scale); y = Avx.Multiply(Avx.Multiply(y, k), scale); } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs similarity index 83% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs index b0ad50301..057d7846a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -1,16 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykBasic : BasicJpegColorConverter + internal sealed class FromCmykScalar : ScalarJpegColorConverter { - public FromCmykBasic(int precision) + public FromCmykScalar(int precision) : base(JpegColorSpace.Cmyk, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs index 0da4c9ec2..685e25aad 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs @@ -1,17 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykVector8 : Vector8JpegColorConverter + internal sealed class FromCmykVector8 : VectorizedJpegColorConverter { public FromCmykVector8(int precision) : base(JpegColorSpace.Cmyk, precision) @@ -21,17 +19,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters protected override void ConvertCoreVectorizedInplace(in ComponentValues values) { ref Vector cBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector mBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); ref Vector yBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); ref Vector kBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); var scale = new Vector(1 / this.MaximumValue); - // Walking 8 elements at one step: nint n = values.Component0.Length / 8; for (nint i = 0; i < n; i++) { @@ -40,14 +37,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector y = ref Unsafe.Add(ref yBase, i); Vector k = Unsafe.Add(ref kBase, i) * scale; - c = (c * k) * scale; - m = (m * k) * scale; - y = (y * k) * scale; + c = c * k * scale; + m = m * k * scale; + y = y * k * scale; } } protected override void ConvertCoreInplace(in ComponentValues values) => - FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue); + FromCmykScalar.ConvertCoreInplace(values, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs similarity index 62% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index eca6b6292..38b159bba 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -1,30 +1,25 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayscaleAvx2 : Avx2JpegColorConverter + internal sealed class FromGrayscaleAvx : AvxColorConverter { - public FromGrayscaleAvx2(int precision) + public FromGrayscaleAvx(int precision) : base(JpegColorSpace.Grayscale, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); @@ -37,11 +32,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); c0 = Avx.Multiply(c0, scale); } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromGrayscaleBasic.ScaleValues(values.Component0, this.MaximumValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs deleted file mode 100644 index 76d57bf06..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromGrayscaleBasic : BasicJpegColorConverter - { - public FromGrayscaleBasic(int precision) - : base(JpegColorSpace.Grayscale, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) => - ScaleValues(values.Component0, this.MaximumValue); - - internal static void ScaleValues(Span values, float maxValue) - { - Span vecValues = MemoryMarshal.Cast(values); - - var scaleVector = new Vector4(1 / maxValue); - - for (int i = 0; i < vecValues.Length; i++) - { - vecValues[i] *= scaleVector; - } - - values = values.Slice(vecValues.Length * 4); - if (!values.IsEmpty) - { - float scaleValue = 1f / maxValue; - values[0] *= scaleValue; - - if ((uint)values.Length > 1) - { - values[1] *= scaleValue; - - if ((uint)values.Length > 2) - { - values[2] *= scaleValue; - } - } - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs new file mode 100644 index 000000000..18ac5ff99 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -0,0 +1,34 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromGrayscaleScalar : ScalarJpegColorConverter + { + public FromGrayscaleScalar(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + public override void ConvertToRgbInplace(in ComponentValues values) => + ConvertCoreInplace(values.Component0, this.MaximumValue); + + internal static void ConvertCoreInplace(Span values, float maxValue) + { + ref float valuesRef = ref MemoryMarshal.GetReference(values); + float scale = 1 / maxValue; + + for (nint i = 0; i < values.Length; i++) + { + Unsafe.Add(ref valuesRef, i) *= scale; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector8.cs new file mode 100644 index 000000000..6aa0b59a9 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector8.cs @@ -0,0 +1,38 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromGrayScaleVector8 : VectorizedJpegColorConverter + { + public FromGrayScaleVector8(int precision) + : base(JpegColorSpace.Grayscale, precision) + { + } + + protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + { + ref Vector cBase = + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + + var scale = new Vector(1 / this.MaximumValue); + + nint n = values.Component0.Length / 8; + for (nint i = 0; i < n; i++) + { + ref Vector c0 = ref Unsafe.Add(ref cBase, i); + c0 *= scale; + } + } + + protected override void ConvertCoreInplace(in ComponentValues values) => + FromGrayscaleScalar.ConvertCoreInplace(values.Component0, this.MaximumValue); + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs similarity index 56% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs index 557e4e417..31c573903 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -1,36 +1,31 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; -using static SixLabors.ImageSharp.SimdUtils; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbAvx2 : Avx2JpegColorConverter + internal sealed class FromRgbAvx : AvxColorConverter { - public FromRgbAvx2(int precision) + public FromRgbAvx(int precision) : base(JpegColorSpace.RGB, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 rBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector256 gBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component1)); ref Vector256 bBase = - ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); + ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component2)); // Used for the color conversion var scale = Vector256.Create(1 / this.MaximumValue); @@ -44,11 +39,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters g = Avx.Multiply(g, scale); b = Avx.Multiply(b, scale); } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs deleted file mode 100644 index 1425e7b58..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromRgbBasic : BasicJpegColorConverter - { - public FromRgbBasic(int precision) - : base(JpegColorSpace.RGB, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - { - ConvertCoreInplace(values, this.MaximumValue); - } - - internal static void ConvertCoreInplace(ComponentValues values, float maxValue) - { - FromGrayscaleBasic.ScaleValues(values.Component0, maxValue); - FromGrayscaleBasic.ScaleValues(values.Component1, maxValue); - FromGrayscaleBasic.ScaleValues(values.Component2, maxValue); - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs new file mode 100644 index 000000000..83861d1e2 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromRgbScalar : ScalarJpegColorConverter + { + public FromRgbScalar(int precision) + : base(JpegColorSpace.RGB, precision) + { + } + + public override void ConvertToRgbInplace(in ComponentValues values) => + ConvertCoreInplace(values, this.MaximumValue); + + internal static void ConvertCoreInplace(ComponentValues values, float maxValue) + { + FromGrayscaleScalar.ConvertCoreInplace(values.Component0, maxValue); + FromGrayscaleScalar.ConvertCoreInplace(values.Component1, maxValue); + FromGrayscaleScalar.ConvertCoreInplace(values.Component2, maxValue); + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs index a00361d97..0dc440b7d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs @@ -1,17 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbVector8 : Vector8JpegColorConverter + internal sealed class FromRgbVector8 : VectorizedJpegColorConverter { public FromRgbVector8(int precision) : base(JpegColorSpace.RGB, precision) @@ -29,7 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var scale = new Vector(1 / this.MaximumValue); - // Walking 8 elements at one step: nint n = values.Component0.Length / 8; for (nint i = 0; i < n; i++) { @@ -43,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } protected override void ConvertCoreInplace(in ComponentValues values) => - FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue); + FromRgbScalar.ConvertCoreInplace(values, this.MaximumValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs similarity index 71% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 5aae1faa2..1bf1c4461 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -1,31 +1,27 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using static SixLabors.ImageSharp.SimdUtils; -#endif // ReSharper disable ImpureMethodCallOnReadonlyValueField namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrAvx2 : Avx2JpegColorConverter + internal sealed class FromYCbCrAvx : AvxColorConverter { - public FromYCbCrAvx2(int precision) + public FromYCbCrAvx(int precision) : base(JpegColorSpace.YCbCr, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector256 c1Base = @@ -36,15 +32,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // Used for the color conversion var chromaOffset = Vector256.Create(-this.HalfValue); var scale = Vector256.Create(1 / this.MaximumValue); - var rCrMult = Vector256.Create(1.402F); - var gCbMult = Vector256.Create(-0.344136F); - var gCrMult = Vector256.Create(-0.714136F); - var bCbMult = Vector256.Create(1.772F); - - // Used for packing. - var va = Vector256.Create(1F); - ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32); - Vector256 vcontrol = Unsafe.As>(ref control); + var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); // Walking 8 elements at one step: nint n = values.Component0.Length / 8; @@ -64,7 +55,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); Vector256 b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); @@ -77,11 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c1 = g; c2 = b; } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs deleted file mode 100644 index 990d29aa0..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromYCbCrBasic : BasicJpegColorConverter - { - public FromYCbCrBasic(int precision) - : base(JpegColorSpace.YCbCr, precision) - { - } - - public override void ConvertToRgbInplace(in ComponentValues values) - => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - - internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) - { - Span c0 = values.Component0; - Span c1 = values.Component1; - Span c2 = values.Component2; - - var scale = 1 / maxValue; - - for (int i = 0; i < c0.Length; i++) - { - float y = c0[i]; - float cb = c1[i] - halfValue; - float cr = c2[i] - halfValue; - - c0[i] = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero) * scale; - c1[i] = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero) * scale; - c2[i] = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero) * scale; - } - } - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs new file mode 100644 index 000000000..73c73970d --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + internal sealed class FromYCbCrScalar : ScalarJpegColorConverter + { + // TODO: comments, derived from ITU-T Rec. T.871 + internal const float RCrMult = 1.402f; + internal const float GCbMult = (float)(0.114 * 1.772 / 0.587); + internal const float GCrMult = (float)(0.299 * 1.402 / 0.587); + internal const float BCbMult = 1.772f; + + public FromYCbCrScalar(int precision) + : base(JpegColorSpace.YCbCr, precision) + { + } + + public override void ConvertToRgbInplace(in ComponentValues values) + => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + + internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue) + { + Span c0 = values.Component0; + Span c1 = values.Component1; + Span c2 = values.Component2; + + float scale = 1 / maxValue; + + for (int i = 0; i < c0.Length; i++) + { + float y = c0[i]; + float cb = c1[i] - halfValue; + float cr = c2[i] - halfValue; + + // r = y + (1.402F * cr); + // g = y - (0.344136F * cb) - (0.714136F * cr); + // b = y + (1.772F * cb); + c0[i] = MathF.Round(y + (RCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c1[i] = MathF.Round(y - (GCbMult * cb) - (GCrMult * cr), MidpointRounding.AwayFromZero) * scale; + c2[i] = MathF.Round(y + (BCbMult * cb), MidpointRounding.AwayFromZero) * scale; + } + } + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs deleted file mode 100644 index 1ebc3e879..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal sealed class FromYCbCrVector4 : VectorizedJpegColorConverter - { - public FromYCbCrVector4(int precision) - : base(JpegColorSpace.YCbCr, precision, 8) - { - } - - protected override bool IsAvailable => SimdUtils.HasVector4; - - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) - { - DebugGuard.IsTrue(values.Component0.Length % 8 == 0, nameof(values), "Length should be divisible by 8!"); - - ref Vector4Pair c0Base = - ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component0)); - ref Vector4Pair c1Base = - ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component1)); - ref Vector4Pair c2Base = - ref Unsafe.As(ref MemoryMarshal.GetReference(values.Component2)); - - var chromaOffset = new Vector4(-this.HalfValue); - var maxValue = this.MaximumValue; - - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; - - for (nint i = 0; i < n; i++) - { - // y = yVals[i]; - ref Vector4Pair c0 = ref Unsafe.Add(ref c0Base, i); - - // cb = cbVals[i] - halfValue); - ref Vector4Pair c1 = ref Unsafe.Add(ref c1Base, i); - c1.AddInplace(chromaOffset); - - // cr = crVals[i] - halfValue; - ref Vector4Pair c2 = ref Unsafe.Add(ref c2Base, i); - c2.AddInplace(chromaOffset); - - // r = y + (1.402F * cr); - Vector4Pair r = c0; - Vector4Pair tmp = c2; - tmp.MultiplyInplace(1.402F); - r.AddInplace(ref tmp); - - // g = y - (0.344136F * cb) - (0.714136F * cr); - Vector4Pair g = c0; - tmp = c1; - tmp.MultiplyInplace(-0.344136F); - g.AddInplace(ref tmp); - tmp = c2; - tmp.MultiplyInplace(-0.714136F); - g.AddInplace(ref tmp); - - // b = y + (1.772F * cb); - Vector4Pair b = c0; - tmp = c1; - tmp.MultiplyInplace(1.772F); - b.AddInplace(ref tmp); - - r.RoundAndDownscalePreVector8(maxValue); - g.RoundAndDownscalePreVector8(maxValue); - b.RoundAndDownscalePreVector8(maxValue); - - c0 = r; - c1 = g; - c2 = b; - } - } - - protected override void ConvertCoreInplace(in ComponentValues values) - => FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs index a077b9ed8..da71e2466 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs @@ -1,18 +1,16 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; // ReSharper disable ImpureMethodCallOnReadonlyValueField namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrVector8 : Vector8JpegColorConverter + internal sealed class FromYCbCrVector8 : VectorizedJpegColorConverter { public FromYCbCrVector8(int precision) : base(JpegColorSpace.YCbCr, precision) @@ -30,10 +28,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var chromaOffset = new Vector(-this.HalfValue); - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; var scale = new Vector(1 / this.MaximumValue); + var rCrMult = new Vector(FromYCbCrScalar.RCrMult); + var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); + var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); + var bCbMult = new Vector(FromYCbCrScalar.BCbMult); + nint n = values.Component0.Length / 8; for (nint i = 0; i < n; i++) { // y = yVals[i]; @@ -49,10 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: - Vector r = y + (cr * new Vector(1.402F)); - Vector g = y - (cb * new Vector(0.344136F)) - (cr * new Vector(0.714136F)); - Vector b = y + (cb * new Vector(1.772F)); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); r = r.FastRound(); g = g.FastRound(); @@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } protected override void ConvertCoreInplace(in ComponentValues values) => - FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + FromYCbCrScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs similarity index 79% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs index a3500a096..53912ee07 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -1,30 +1,26 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; -using System.Numerics; +#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; using static SixLabors.ImageSharp.SimdUtils; -#endif namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKAvx2 : Avx2JpegColorConverter + internal sealed class FromYccKAvx : AvxColorConverter { - public FromYccKAvx2(int precision) + public FromYccKAvx(int precision) : base(JpegColorSpace.Ycck, precision) { } - protected override void ConvertCoreVectorizedInplace(in ComponentValues values) + public override void ConvertToRgbInplace(in ComponentValues values) { -#if SUPPORTS_RUNTIME_INTRINSICS ref Vector256 c0Base = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component0)); ref Vector256 c1Base = @@ -38,10 +34,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var chromaOffset = Vector256.Create(-this.HalfValue); var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); var max = Vector256.Create(this.MaximumValue); - var rCrMult = Vector256.Create(1.402F); - var gCbMult = Vector256.Create(-0.344136F); - var gCrMult = Vector256.Create(-0.714136F); - var bCbMult = Vector256.Create(1.772F); + var rCrMult = Vector256.Create(FromYCbCrScalar.RCrMult); + var gCbMult = Vector256.Create(-FromYCbCrScalar.GCbMult); + var gCrMult = Vector256.Create(-FromYCbCrScalar.GCrMult); + var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); // Walking 8 elements at one step: nint n = values.Component0.Length / 8; @@ -62,7 +58,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: Vector256 r = HwIntrinsics.MultiplyAdd(y, cr, rCrMult); Vector256 g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); @@ -80,11 +75,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters c1 = g; c2 = b; } -#endif } - - protected override void ConvertCoreInplace(in ComponentValues values) => - FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs similarity index 81% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs index 4833f4868..4657e50cf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -1,16 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKBasic : BasicJpegColorConverter + internal sealed class FromYccKScalar : ScalarJpegColorConverter { - public FromYccKBasic(int precision) + public FromYccKScalar(int precision) : base(JpegColorSpace.Ycck, precision) { } @@ -25,9 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters Span c2 = values.Component2; Span c3 = values.Component3; - var v = new Vector4(0, 0, 0, 1F); - - var scale = 1 / (maxValue * maxValue); + float scale = 1 / (maxValue * maxValue); for (int i = 0; i < values.Component0.Length; i++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs index f830e5042..152a0793c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs @@ -1,17 +1,15 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKVector8 : Vector8JpegColorConverter + internal sealed class FromYccKVector8 : VectorizedJpegColorConverter { public FromYccKVector8(int precision) : base(JpegColorSpace.Ycck, precision) @@ -30,13 +28,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); var chromaOffset = new Vector(-this.HalfValue); - - // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; - + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); var max = new Vector(this.MaximumValue); - var scale = new Vector(1f) / (max * max); + var rCrMult = new Vector(FromYCbCrScalar.RCrMult); + var gCbMult = new Vector(-FromYCbCrScalar.GCbMult); + var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); + var bCbMult = new Vector(FromYCbCrScalar.BCbMult); + nint n = values.Component0.Length / 8; for (nint i = 0; i < n; i++) { // y = yVals[i]; @@ -55,10 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // r = y + (1.402F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr); // b = y + (1.772F * cb); - // Adding & multiplying 8 elements at one time: - Vector r = y + (cr * new Vector(1.402F)); - Vector g = y - (cb * new Vector(0.344136F)) - (cr * new Vector(0.714136F)); - Vector b = y + (cb * new Vector(1.772F)); + Vector r = y + (cr * rCrMult); + Vector g = y + (cb * gCbMult) + (cr * gCrMult); + Vector b = y + (cb * bCbMult); r = (max - r.FastRound()) * scaledK; g = (max - g.FastRound()) * scaledK; @@ -71,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } protected override void ConvertCoreInplace(in ComponentValues values) => - FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); + FromYccKScalar.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs deleted file mode 100644 index 3e9b889db..000000000 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.Vector8JpegColorConverter.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters -{ - internal abstract partial class JpegColorConverter - { - internal abstract class Vector8JpegColorConverter : VectorizedJpegColorConverter - { - protected Vector8JpegColorConverter(JpegColorSpace colorSpace, int precision) - : base(colorSpace, precision, 8) - { - } - - protected sealed override bool IsAvailable => SimdUtils.HasVector8; - } - } -} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs new file mode 100644 index 000000000..ff82b36dc --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics.X86; + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + /// + /// abstract base for implementations + /// based on instructions. + /// + /// + /// Converters of this family would expect input buffers lengths to be + /// divisible by 8 without a remainder. + /// This is guaranteed by real-life data as jpeg stores pixels via 8x8 blocks. + /// DO NOT pass test data of invalid size to these converters as they + /// potentially won't do a bound check and return a false positive result. + /// + internal abstract class AvxColorConverter : JpegColorConverterBase + { + protected AvxColorConverter(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) + { + } + + protected override bool IsAvailable => Avx.IsSupported; + } + } +} +#endif diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs similarity index 67% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index dad46861e..8767f5efc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -4,26 +4,24 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Tuples; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { /// - /// Encapsulates the conversion of Jpeg channels to RGBA values packed in buffer. + /// Encapsulates the conversion of color channels from jpeg image to RGB channels. /// - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { /// /// The available converters /// - private static readonly JpegColorConverter[] Converters = CreateConverters(); + private static readonly JpegColorConverterBase[] Converters = CreateConverters(); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - protected JpegColorConverter(JpegColorSpace colorSpace, int precision) + protected JpegColorConverterBase(JpegColorSpace colorSpace, int precision) { this.ColorSpace = colorSpace; this.Precision = precision; @@ -32,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } /// - /// Gets a value indicating whether this is available + /// Gets a value indicating whether this is available /// on the current runtime and CPU architecture. /// protected abstract bool IsAvailable { get; } @@ -58,11 +56,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters private float HalfValue { get; } /// - /// Returns the corresponding to the given + /// Returns the corresponding to the given /// - public static JpegColorConverter GetConverter(JpegColorSpace colorSpace, int precision) + public static JpegColorConverterBase GetConverter(JpegColorSpace colorSpace, int precision) { - JpegColorConverter converter = Array.Find( + JpegColorConverterBase converter = Array.Find( Converters, c => c.ColorSpace == colorSpace && c.Precision == precision); @@ -82,11 +80,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters public abstract void ConvertToRgbInplace(in ComponentValues values); /// - /// Returns the s for all supported colorspaces and precisions. + /// Returns the s for all supported colorspaces and precisions. /// - private static JpegColorConverter[] CreateConverters() + private static JpegColorConverterBase[] CreateConverters() { - var converters = new List(); + var converters = new List(); // 8-bit converters converters.AddRange(GetYCbCrConverters(8)); @@ -106,63 +104,63 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters } /// - /// Returns the s for the YCbCr colorspace. + /// Returns the s for the YCbCr colorspace. /// - private static IEnumerable GetYCbCrConverters(int precision) + private static IEnumerable GetYCbCrConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromYCbCrAvx2(precision); + yield return new FromYCbCrAvx(precision); #endif yield return new FromYCbCrVector8(precision); - yield return new FromYCbCrVector4(precision); - yield return new FromYCbCrBasic(precision); + yield return new FromYCbCrScalar(precision); } /// - /// Returns the s for the YccK colorspace. + /// Returns the s for the YccK colorspace. /// - private static IEnumerable GetYccKConverters(int precision) + private static IEnumerable GetYccKConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromYccKAvx2(precision); + yield return new FromYccKAvx(precision); #endif yield return new FromYccKVector8(precision); - yield return new FromYccKBasic(precision); + yield return new FromYccKScalar(precision); } /// - /// Returns the s for the CMYK colorspace. + /// Returns the s for the CMYK colorspace. /// - private static IEnumerable GetCmykConverters(int precision) + private static IEnumerable GetCmykConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromCmykAvx2(precision); + yield return new FromCmykAvx(precision); #endif yield return new FromCmykVector8(precision); - yield return new FromCmykBasic(precision); + yield return new FromCmykScalar(precision); } /// - /// Returns the s for the gray scale colorspace. + /// Returns the s for the gray scale colorspace. /// - private static IEnumerable GetGrayScaleConverters(int precision) + private static IEnumerable GetGrayScaleConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromGrayscaleAvx2(precision); + yield return new FromGrayscaleAvx(precision); #endif - yield return new FromGrayscaleBasic(precision); + yield return new FromGrayScaleVector8(precision); + yield return new FromGrayscaleScalar(precision); } /// - /// Returns the s for the RGB colorspace. + /// Returns the s for the RGB colorspace. /// - private static IEnumerable GetRgbConverters(int precision) + private static IEnumerable GetRgbConverters(int precision) { #if SUPPORTS_RUNTIME_INTRINSICS - yield return new FromRgbAvx2(precision); + yield return new FromRgbAvx(precision); #endif yield return new FromRgbVector8(precision); - yield return new FromRgbBasic(precision); + yield return new FromRgbScalar(precision); } /// @@ -200,35 +198,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// /// Initializes a new instance of the struct. /// - /// The 1-4 sized list of component post processors. - /// The row to convert - public ComponentValues(IReadOnlyList componentProcessors, int row) + /// List of component buffers. + /// Row to convert + public ComponentValues(IReadOnlyList> componentBuffers, int row) { - this.ComponentCount = componentProcessors.Count; + DebugGuard.MustBeGreaterThan(componentBuffers.Count, 0, nameof(componentBuffers)); + + this.ComponentCount = componentBuffers.Count; - this.Component0 = componentProcessors[0].GetColorBufferRowSpan(row); + this.Component0 = componentBuffers[0].GetRowSpan(row); // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentProcessors[1].GetColorBufferRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentProcessors[2].GetColorBufferRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentProcessors[3].GetColorBufferRowSpan(row) : Span.Empty; + this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span.Empty; } /// /// Initializes a new instance of the struct. /// - /// The 1-4 sized list of component buffers. - /// The row to convert - public ComponentValues(IReadOnlyList> componentBuffers, int row) + /// List of component color processors. + /// Row to convert + public ComponentValues(IReadOnlyList processors, int row) { - this.ComponentCount = componentBuffers.Count; + DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); - this.Component0 = componentBuffers[0].GetRowSpan(row); + this.ComponentCount = processors.Count; + + this.Component0 = processors[0].ColorBuffer.GetRowSpan(row); // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span.Empty; + this.Component1 = this.ComponentCount > 1 ? processors[1].ColorBuffer.GetRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].ColorBuffer.GetRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].ColorBuffer.GetRowSpan(row) : Span.Empty; } internal ComponentValues( diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs new file mode 100644 index 000000000..89d6c4544 --- /dev/null +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters +{ + internal abstract partial class JpegColorConverterBase + { + /// + /// abstract base for implementations + /// based on scalar instructions. + /// + internal abstract class ScalarJpegColorConverter : JpegColorConverterBase + { + protected ScalarJpegColorConverter(JpegColorSpace colorSpace, int precision) + : base(colorSpace, precision) + { + } + + protected override bool IsAvailable => true; + } + } +} diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs similarity index 64% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index fc4fb7786..3a40fad0c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -1,27 +1,36 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { - internal abstract partial class JpegColorConverter + internal abstract partial class JpegColorConverterBase { - internal abstract class VectorizedJpegColorConverter : JpegColorConverter + /// + /// abstract base for implementations + /// based on API. + /// + /// + /// Converters of this family can work with data of any size. + /// Even though real life data is guaranteed to be of size + /// divisible by 8 newer SIMD instructions like AVX512 won't work with + /// such data out of the box. These converters have fallback code + /// for 'remainder' data. + /// + internal abstract class VectorizedJpegColorConverter : JpegColorConverterBase { - private readonly int vectorSize; - - protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision, int vectorSize) + protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision) : base(colorSpace, precision) { - this.vectorSize = vectorSize; } + protected sealed override bool IsAvailable => SimdUtils.HasVector8; + public override void ConvertToRgbInplace(in ComponentValues values) { int length = values.Component0.Length; - int remainder = values.Component0.Length % this.vectorSize; + int remainder = values.Component0.Length % 8; int simdCount = length - remainder; if (simdCount > 0) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index 1eb74ff80..acd98bcfc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -57,6 +57,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The jpeg frame with the color space to convert to. /// The raw JPEG data. /// The color converter. - protected virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); + protected virtual JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(jpegData.ColorSpace, frame.Precision); } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 0003437e7..7137c5203 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Color converter from jpeg color space to target pixel color space. /// - private JpegColorConverter colorConverter; + private JpegColorConverterBase colorConverter; /// /// Intermediate buffer of RGB components used in color conversion. @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { int y = yy - this.pixelRowCounter; - var values = new JpegColorConverter.ComponentValues(this.componentProcessors, y); + var values = new JpegColorConverterBase.ComponentValues(this.componentProcessors, y); this.colorConverter.ConvertToRgbInplace(values); values = values.Slice(0, width); // slice away Jpeg padding diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs index 3b5833c10..001480542 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs @@ -27,6 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors } /// - protected override JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision); + protected override JpegColorConverterBase GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverterBase.GetConverter(JpegColorSpace.RGB, frame.Precision); } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 490beec6f..2642c21f1 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykBasic(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykVector8(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromCmykAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykAvx2(8).ConvertToRgbInplace(values); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 7b62e1434..1fc85e967 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -17,17 +17,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromGrayscaleBasic(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromGrayscaleAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromGrayscaleAvx2(8).ConvertToRgbInplace(values); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index af03b31e5..517630a50 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbBasic(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbVector8(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromRgbAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbAvx2(8).ConvertToRgbInplace(values); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index 18daa364c..3b142d925 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -17,33 +17,33 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrBasic(8).ConvertToRgbInplace(values); } [Benchmark(Baseline = true)] public void SimdVector() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrVector4(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrVector4(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrVector8(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYCbCrAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrAvx2(8).ConvertToRgbInplace(values); } } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index 08e5e50d1..b26ac0622 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -17,25 +17,25 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg [Benchmark(Baseline = true)] public void Scalar() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKBasic(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVector8() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKVector8(8).ConvertToRgbInplace(values); } [Benchmark] public void SimdVectorAvx2() { - var values = new JpegColorConverter.ComponentValues(this.Input, 0); + var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverter.FromYccKAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKAvx2(8).ConvertToRgbInplace(values); } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index d6dc57e83..fc3529513 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -45,25 +45,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) { ValidateConversion( - new JpegColorConverter.FromYCbCrBasic(8), - 3, - inputBufferLength, - resultBufferLength, - seed); - } - - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrVector4(int inputBufferLength, int resultBufferLength, int seed) - { - if (!SimdUtils.HasVector4) - { - this.Output.WriteLine("No SSE present, skipping test!"); - return; - } - - ValidateConversion( - new JpegColorConverter.FromYCbCrVector4(8), + new JpegColorConverterBase.FromYCbCrScalar(8), 3, inputBufferLength, resultBufferLength, @@ -81,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromYCbCrVector8(8), + new JpegColorConverterBase.FromYCbCrVector8(8), 3, inputBufferLength, resultBufferLength, @@ -99,7 +81,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromYCbCrAvx2(8), + new JpegColorConverterBase.FromYCbCrAvx(8), 3, inputBufferLength, resultBufferLength, @@ -123,7 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FromCmykBasic(int inputBufferLength, int resultBufferLength, int seed) { ValidateConversion( - new JpegColorConverter.FromCmykBasic(8), + new JpegColorConverterBase.FromCmykScalar(8), 4, inputBufferLength, resultBufferLength, @@ -141,7 +123,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromCmykVector8(8), + new JpegColorConverterBase.FromCmykVector8(8), 4, inputBufferLength, resultBufferLength, @@ -159,7 +141,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromCmykAvx2(8), + new JpegColorConverterBase.FromCmykAvx(8), 4, inputBufferLength, resultBufferLength, @@ -183,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FromGrayscaleBasic(int inputBufferLength, int resultBufferLength, int seed) { ValidateConversion( - new JpegColorConverter.FromGrayscaleBasic(8), + new JpegColorConverterBase.FromGrayscaleScalar(8), 1, inputBufferLength, resultBufferLength, @@ -201,7 +183,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromGrayscaleAvx2(8), + new JpegColorConverterBase.FromGrayscaleAvx(8), 1, inputBufferLength, resultBufferLength, @@ -225,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FromRgbBasic(int inputBufferLength, int resultBufferLength, int seed) { ValidateConversion( - new JpegColorConverter.FromRgbBasic(8), + new JpegColorConverterBase.FromRgbScalar(8), 3, inputBufferLength, resultBufferLength, @@ -243,7 +225,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromRgbVector8(8), + new JpegColorConverterBase.FromRgbVector8(8), 3, inputBufferLength, resultBufferLength, @@ -261,7 +243,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromRgbAvx2(8), + new JpegColorConverterBase.FromRgbAvx(8), 3, inputBufferLength, resultBufferLength, @@ -285,7 +267,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void FromYccKBasic(int inputBufferLength, int resultBufferLength, int seed) { ValidateConversion( - new JpegColorConverter.FromYccKBasic(8), + new JpegColorConverterBase.FromYccKScalar(8), 4, inputBufferLength, resultBufferLength, @@ -303,7 +285,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromYccKVector8(8), + new JpegColorConverterBase.FromYccKVector8(8), 4, inputBufferLength, resultBufferLength, @@ -321,7 +303,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverter.FromYccKAvx2(8), + new JpegColorConverterBase.FromYccKAvx(8), 4, inputBufferLength, resultBufferLength, @@ -340,7 +322,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } - private static JpegColorConverter.ComponentValues CreateRandomValues( + private static JpegColorConverterBase.ComponentValues CreateRandomValues( int componentCount, int inputBufferLength, int seed, @@ -365,7 +347,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg buffers[i] = new Buffer2D(source, values.Length, 1); } - return new JpegColorConverter.ComponentValues(buffers, 0); + return new JpegColorConverterBase.ComponentValues(buffers, 0); } private static void ValidateConversion( @@ -376,7 +358,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int seed) { ValidateConversion( - JpegColorConverter.GetConverter(colorSpace, 8), + JpegColorConverterBase.GetConverter(colorSpace, 8), componentCount, inputBufferLength, resultBufferLength, @@ -384,14 +366,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } private static void ValidateConversion( - JpegColorConverter converter, + JpegColorConverterBase converter, int componentCount, int inputBufferLength, int resultBufferLength, int seed) { - JpegColorConverter.ComponentValues original = CreateRandomValues(componentCount, inputBufferLength, seed); - JpegColorConverter.ComponentValues values = Copy(original); + JpegColorConverterBase.ComponentValues original = CreateRandomValues(componentCount, inputBufferLength, seed); + JpegColorConverterBase.ComponentValues values = Copy(original); converter.ConvertToRgbInplace(values); @@ -400,20 +382,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Validate(converter.ColorSpace, original, values, i); } - static JpegColorConverter.ComponentValues Copy(JpegColorConverter.ComponentValues values) + static JpegColorConverterBase.ComponentValues Copy(JpegColorConverterBase.ComponentValues values) { Span c0 = values.Component0.ToArray(); Span c1 = values.ComponentCount > 1 ? values.Component1.ToArray().AsSpan() : c0; Span c2 = values.ComponentCount > 2 ? values.Component2.ToArray().AsSpan() : c0; Span c3 = values.ComponentCount > 3 ? values.Component3.ToArray().AsSpan() : Span.Empty; - return new JpegColorConverter.ComponentValues(values.ComponentCount, c0, c1, c2, c3); + return new JpegColorConverterBase.ComponentValues(values.ComponentCount, c0, c1, c2, c3); } } private static void Validate( JpegColorSpace colorSpace, - in JpegColorConverter.ComponentValues original, - in JpegColorConverter.ComponentValues result, + in JpegColorConverterBase.ComponentValues original, + in JpegColorConverterBase.ComponentValues result, int i) { switch (colorSpace) @@ -439,7 +421,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } - private static void ValidateYCbCr(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateYCbCr(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { float y = values.Component0[i]; float cb = values.Component1[i]; @@ -452,7 +434,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, ColorSpaceComparer); } - private static void ValidateCyyK(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); @@ -477,7 +459,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, ColorSpaceComparer); } - private static void ValidateRgb(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { float r = values.Component0[i]; float g = values.Component1[i]; @@ -489,7 +471,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, ColorSpaceComparer); } - private static void ValidateGrayScale(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { float y = values.Component0[i]; var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); @@ -498,7 +480,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual, ColorSpaceComparer); } - private static void ValidateCmyk(in JpegColorConverter.ComponentValues values, in JpegColorConverter.ComponentValues result, int i) + private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { var v = new Vector4(0, 0, 0, 1F); var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); From 311748ef61bbe51efb17b6dfa43516c0eb14445c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 1 Dec 2021 08:11:55 +0300 Subject: [PATCH 126/228] Fixed compilation errors --- .../Jpeg/ColorConversion/CmykColorConversion.cs | 8 +++++--- .../ColorConversion/GrayscaleColorConversion.cs | 10 ++++++---- .../Jpeg/ColorConversion/RgbColorConversion.cs | 10 ++++++---- .../Jpeg/ColorConversion/YCbCrColorConversion.cs | 16 +++++----------- .../Jpeg/ColorConversion/YccKColorConverter.cs | 8 +++++--- .../Formats/Jpg/JpegColorConverterTests.cs | 10 ++++++++++ 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 2642c21f1..36e666957 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -30,12 +30,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg new JpegColorConverterBase.FromCmykVector8(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs index 1fc85e967..2fdb47077 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -19,15 +19,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromGrayscaleBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromGrayscaleScalar(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromGrayscaleAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromGrayscaleAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 517630a50..69c1a3974 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -30,12 +30,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg new JpegColorConverterBase.FromRgbVector8(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index 3b142d925..656cae104 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -19,15 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrBasic(8).ConvertToRgbInplace(values); - } - - [Benchmark(Baseline = true)] - public void SimdVector() - { - var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - - new JpegColorConverterBase.FromYCbCrVector4(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -38,12 +30,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg new JpegColorConverterBase.FromYCbCrVector8(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] - public void SimdVectorAvx2() + public void SimdVectorAvx() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index b26ac0622..6c0583b62 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using BenchmarkDotNet.Attributes; @@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKBasic(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKScalar(8).ConvertToRgbInplace(values); } [Benchmark] @@ -30,12 +30,14 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg new JpegColorConverterBase.FromYccKVector8(8).ConvertToRgbInplace(values); } +#if SUPPORTS_RUNTIME_INTRINSICS [Benchmark] public void SimdVectorAvx2() { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKAvx2(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKAvx(8).ConvertToRgbInplace(values); } +#endif } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index fc3529513..41c9dd6a3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -70,6 +70,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } +#if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(CommonConversionData))] public void FromYCbCrAvx2(int inputBufferLength, int resultBufferLength, int seed) @@ -87,6 +88,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg resultBufferLength, seed); } +#endif [Theory] [MemberData(nameof(CommonConversionData))] @@ -130,6 +132,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } +#if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(CommonConversionData))] public void FromCmykAvx2(int inputBufferLength, int resultBufferLength, int seed) @@ -147,6 +150,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg resultBufferLength, seed); } +#endif [Theory] [MemberData(nameof(CommonConversionData))] @@ -172,6 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } +#if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(CommonConversionData))] public void FromGrayscaleAvx2(int inputBufferLength, int resultBufferLength, int seed) @@ -189,6 +194,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg resultBufferLength, seed); } +#endif [Theory] [MemberData(nameof(CommonConversionData))] @@ -232,6 +238,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } +#if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(CommonConversionData))] public void FromRgbAvx2(int inputBufferLength, int resultBufferLength, int seed) @@ -249,6 +256,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg resultBufferLength, seed); } +#endif [Theory] [MemberData(nameof(CommonConversionData))] @@ -292,6 +300,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } +#if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(CommonConversionData))] public void FromYccKAvx2(int inputBufferLength, int resultBufferLength, int seed) @@ -309,6 +318,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg resultBufferLength, seed); } +#endif [Theory] [MemberData(nameof(CommonConversionData))] From 231932f952f8084644e07174adc2d2c52e6e09a2 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 1 Dec 2021 08:27:08 +0300 Subject: [PATCH 127/228] Fixed docs --- .../Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 7137c5203..a3e98125e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -14,11 +14,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// /// Color decoding scheme: - /// + /// /// - /// 1. Decode spectral data to Jpeg color space - /// 2. Convert from Jpeg color space to RGB - /// 3. Convert from RGB to target pixel space + /// Decode spectral data to Jpeg color space + /// Convert from Jpeg color space to RGB + /// Convert from RGB to target pixel space /// /// /// From be057f5a339d9b1e47be4c86558d98bc3abd5dcd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 1 Dec 2021 08:37:40 +0300 Subject: [PATCH 128/228] Use Vector256.Count instead of magic 8 --- .../Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs | 2 +- .../ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs | 2 +- .../Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs | 2 +- .../Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs | 2 +- .../Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 2671dec70..4c89fc6fa 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // Used for the color conversion var scale = Vector256.Create(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { ref Vector256 c = ref Unsafe.Add(ref c0Base, i); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index 38b159bba..fcfcaa2a9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // Used for the color conversion var scale = Vector256.Create(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { ref Vector256 c0 = ref Unsafe.Add(ref c0Base, i); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs index 31c573903..83fbc369b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters // Used for the color conversion var scale = Vector256.Create(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { ref Vector256 r = ref Unsafe.Add(ref rBase, i); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index 1bf1c4461..adf6e8e0f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs index 53912ee07..86528c74d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var bCbMult = Vector256.Create(FromYCbCrScalar.BCbMult); // Walking 8 elements at one step: - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; From add85f899986218d57723d80b85bfba18dbacad6 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 1 Dec 2021 08:46:00 +0300 Subject: [PATCH 129/228] Removed HasAvx2 flag, reorganized test skipping --- src/ImageSharp/Common/Helpers/SimdUtils.cs | 12 ----- .../ColorConverters/JpegColorConverterAvx.cs | 2 +- .../ColorConverters/JpegColorConverterBase.cs | 2 +- .../JpegColorConverterScalar.cs | 2 +- .../JpegColorConverterVector.cs | 2 +- .../Formats/Jpg/JpegColorConverterTests.cs | 45 ++++++++++++------- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/SimdUtils.cs b/src/ImageSharp/Common/Helpers/SimdUtils.cs index 6d82cfad0..29068a82c 100644 --- a/src/ImageSharp/Common/Helpers/SimdUtils.cs +++ b/src/ImageSharp/Common/Helpers/SimdUtils.cs @@ -33,18 +33,6 @@ namespace SixLabors.ImageSharp public static bool HasVector4 { get; } = Vector.IsHardwareAccelerated && Vector.Count == 4; - public static bool HasAvx2 - { - get - { -#if SUPPORTS_RUNTIME_INTRINSICS - return Avx2.IsSupported; -#else - return false; -#endif - } - } - /// /// Transform all scalars in 'v' in a way that converting them to would have rounding semantics. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs index ff82b36dc..559422273 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected override bool IsAvailable => Avx.IsSupported; + public override bool IsAvailable => Avx.IsSupported; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 8767f5efc..b8fd169e9 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// Gets a value indicating whether this is available /// on the current runtime and CPU architecture. /// - protected abstract bool IsAvailable { get; } + public abstract bool IsAvailable { get; } /// /// Gets the of this converter. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs index 89d6c4544..76134d490 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected override bool IsAvailable => true; + public override bool IsAvailable => true; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index 3a40fad0c..3cd295f0b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - protected sealed override bool IsAvailable => SimdUtils.HasVector8; + public sealed override bool IsAvailable => SimdUtils.HasVector8; public override void ConvertToRgbInplace(in ComponentValues values) { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 41c9dd6a3..7b91e1a8a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -75,14 +75,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromYCbCrAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromYCbCrAvx(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromYCbCrAvx(8), + converter, 3, inputBufferLength, resultBufferLength, @@ -137,14 +140,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromCmykAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromCmykAvx(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromCmykAvx(8), + converter, 4, inputBufferLength, resultBufferLength, @@ -181,14 +187,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromGrayscaleAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromGrayscaleAvx(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromGrayscaleAvx(8), + converter, 1, inputBufferLength, resultBufferLength, @@ -243,14 +252,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromRgbAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromRgbAvx(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromRgbAvx(8), + converter, 3, inputBufferLength, resultBufferLength, @@ -305,14 +317,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [MemberData(nameof(CommonConversionData))] public void FromYccKAvx2(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasAvx2) + var converter = new JpegColorConverterBase.FromYccKAvx(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromYccKAvx(8), + converter, 4, inputBufferLength, resultBufferLength, From 7cf715b401a0cf25358a1967aae7aed69c921073 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Dec 2021 11:11:30 +0100 Subject: [PATCH 130/228] Add SSE2 version of SimpleHFilter16 --- .../Formats/Webp/Lossy/LossyUtils.cs | 131 +++++++++++++++++- 1 file changed, 126 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 04632ded8..9642d2afb 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -967,13 +967,27 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void SimpleHFilter16(Span p, int offset, int stride, int thresh) { - int thresh2 = (2 * thresh) + 1; - int end = offset + (16 * stride); - for (int i = offset; i < end; i += stride) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // beginning of p1 + p = p.Slice(offset - 2); + + Load16x4Sse2(p, p.Slice(8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); + DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); + Store16x4Sse2(p1, p0, q0, q1, p, p.Slice(8 * stride), stride); + } + else +#endif { - if (NeedsFilter(p, i, 1, thresh2)) + int thresh2 = (2 * thresh) + 1; + int end = offset + (16 * stride); + for (int i = offset; i < end; i += stride) { - DoFilter2(p, i, 1); + if (NeedsFilter(p, i, 1, thresh2)) + { + DoFilter2(p, i, 1); + } } } } @@ -1357,6 +1371,113 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Sse2.CompareEqual(t7, Vector128.Zero); } + private static void Load16x4Sse2(Span r0, Span r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) + { + // Assume the pixels around the edge (|) are numbered as follows + // 00 01 | 02 03 + // 10 11 | 12 13 + // ... | ... + // e0 e1 | e2 e3 + // f0 f1 | f2 f3 + // + // r0 is pointing to the 0th row (00) + // r8 is pointing to the 8th row (80) + + // Load + // p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 + // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 + Load8x4Sse2(r0, stride, out Vector128 t1, out Vector128 t2); + Load8x4Sse2(r8, stride, out p0, out q1); + + // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 + // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 + // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 + // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 + p1 = Sse2.UnpackLow(t1.AsInt64(), p0.AsInt64()).AsByte(); + p0 = Sse2.UnpackHigh(t1.AsInt64(), p0.AsInt64()).AsByte(); + q0 = Sse2.UnpackLow(t2.AsInt64(), q1.AsInt64()).AsByte(); + q1 = Sse2.UnpackHigh(t2.AsInt64(), q1.AsInt64()).AsByte(); + } + + // Reads 8 rows across a vertical edge. + private static void Load8x4Sse2(Span b, int stride, out Vector128 p, out Vector128 q) + { + // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 + // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 + ref byte bRef = ref MemoryMarshal.GetReference(b); + uint a00 = Unsafe.As(ref Unsafe.Add(ref bRef, 6 * stride)); + uint a01 = Unsafe.As(ref Unsafe.Add(ref bRef, 2 * stride)); + uint a02 = Unsafe.As(ref Unsafe.Add(ref bRef, 4 * stride)); + uint a03 = Unsafe.As(ref Unsafe.Add(ref bRef, 0 * stride)); + Vector128 a0 = Vector128.Create(a03, a02, a01, a00).AsByte(); + uint a10 = Unsafe.As(ref Unsafe.Add(ref bRef, 7 * stride)); + uint a11 = Unsafe.As(ref Unsafe.Add(ref bRef, 3 * stride)); + uint a12 = Unsafe.As(ref Unsafe.Add(ref bRef, 5 * stride)); + uint a13 = Unsafe.As(ref Unsafe.Add(ref bRef, 1 * stride)); + Vector128 a1 = Vector128.Create(a13, a12, a11, a10).AsByte(); + + // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 + // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 + Vector128 b0 = Sse2.UnpackLow(a0, a1); + Vector128 b1 = Sse2.UnpackHigh(a0, a1); + + // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 + // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 + Vector128 c0 = Sse2.UnpackLow(b0.AsInt16(), b1.AsInt16()); + Vector128 c1 = Sse2.UnpackHigh(b0.AsInt16(), b1.AsInt16()); + + // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + p = Sse2.UnpackLow(c0.AsInt32(), c1.AsInt32()).AsByte(); + q = Sse2.UnpackHigh(c0.AsInt32(), c1.AsInt32()).AsByte(); + } + + // Transpose back and store + private static void Store16x4Sse2(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, Span r0, Span r8, int stride) + { + // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 + // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 + Vector128 p0s = Sse2.UnpackLow(p1, p0); + Vector128 p1s = Sse2.UnpackHigh(p1, p0); + + // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 + // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 + Vector128 q0s = Sse2.UnpackLow(q0, q1); + Vector128 q1s = Sse2.UnpackHigh(q0, q1); + + // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 + // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 + Vector128 t1 = p0s; + p0s = Sse2.UnpackLow(t1.AsInt16(), q0s.AsInt16()).AsByte(); + q0s = Sse2.UnpackHigh(t1.AsInt16(), q0s.AsInt16()).AsByte(); + + // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 + // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 + t1 = p1s; + p1s = Sse2.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte(); + q1s = Sse2.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte(); + + Store4x4Sse2(p0s, r0, stride); + Store4x4Sse2(q0s, r0.Slice(4 * stride), stride); + + Store4x4Sse2(p1s, r8, stride); + Store4x4Sse2(q1s, r8.Slice(4 * stride), stride); + } + + private static void Store4x4Sse2(Vector128 x, Span dst, int stride) + { + int offset = 0; + ref byte dstRef = ref MemoryMarshal.GetReference(dst); + for (int i = 0; i < 4; i++) + { + Unsafe.As(ref Unsafe.Add(ref dstRef, offset)) = Sse2.ConvertToInt32(x.AsInt32()); + x = Sse2.ShiftRightLogical128BitLane(x, 4); + offset += stride; + } + } + [MethodImpl(InliningOptions.ShortMethod)] private static Vector128 GetBaseDeltaSse2(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) { From f415e379ed9a343096cbae8928887f85b62c676f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Dec 2021 22:01:55 +1100 Subject: [PATCH 131/228] Update JpegDecoderTests.cs --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 2cbc29027..dcdfc3e42 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -93,6 +93,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.IsType>(image); } + [Fact] + public async Task DecodeAsync_NonGeneric_CreatesRgb24Image() + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Jpeg.Baseline.Jpeg420Small); + using Image image = await Image.LoadAsync(file); + Assert.IsType>(image); + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.Calliphora, CommonNonDefaultPixelTypes)] public void JpegDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider) From 52d570af32d1d67c467bd2649c3d6959c4ae87a3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Dec 2021 12:35:58 +0100 Subject: [PATCH 132/228] Add SSE2 version of DoFilter4 --- .../Formats/Webp/Lossy/LossyUtils.cs | 83 ++++++++++++++++--- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 9642d2afb..9462db630 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -17,6 +17,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { #if SUPPORTS_RUNTIME_INTRINSICS private static readonly Vector128 Mean16x4Mask = Vector128.Create((short)0x00ff).AsByte(); + + private static readonly Vector128 SignBit = Vector128.Create((byte)0x80); + + private static readonly Vector128 Three = Vector128.Create((byte)3).AsSByte(); + + private static readonly Vector128 Four = Vector128.Create((byte)4).AsSByte(); + + private static readonly Vector128 SixtyFour = Vector128.Create((byte)64).AsSByte(); #endif // Note: method name in libwebp reference implementation is called VP8SSE16x16. @@ -1240,16 +1248,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Applies filter on 2 pixels (p0 and q0) private static void DoFilter2Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int thresh) { - var signBit = Vector128.Create((byte)0x80); - // Convert p1/q1 to byte (for GetBaseDeltaSse2). - Vector128 p1s = Sse2.Xor(p1, signBit); - Vector128 q1s = Sse2.Xor(q1, signBit); + Vector128 p1s = Sse2.Xor(p1, SignBit); + Vector128 q1s = Sse2.Xor(q1, SignBit); Vector128 mask = NeedsFilterSse2(p1, p0, q0, q1, thresh); // Flip sign. - p0 = Sse2.Xor(p0, signBit); - q0 = Sse2.Xor(q0, signBit); + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); Vector128 a = GetBaseDeltaSse2(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); @@ -1259,22 +1265,75 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy DoSimpleFilterSse2(ref p0, ref q0, a); // Flip sign. - p0 = Sse2.Xor(p0, signBit); - q0 = Sse2.Xor(q0, signBit); + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + } + + // Applies filter on 4 pixels (p1, p0, q0 and q1) + private static void DoFilter4Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, Vector128 mask, int tresh) + { + // Compute hev mask. + Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + + // Convert to signed values. + p1 = Sse2.Xor(p1, SignBit); + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + q1 = Sse2.Xor(q1, SignBit); + + Vector128 t1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1 + Vector128 t2 = Sse2.AndNot(notHev, t1); // hev(p1 - q1) + Vector128 t3 = Sse2.SubtractSaturate(q0, p0); // q0 - p0 + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) + t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) + t1 = Sse2.Add(t1, mask); // mask filter values we don't care about. + + t2 = Sse2.AddSaturate(t1.AsSByte(), Three).AsByte(); // 3 * (q0 - p0) + hev(p1 - q1) + 3 + t3 = Sse2.AddSaturate(t1.AsSByte(), Four).AsByte(); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + Vector128 t2SignedShift = SignedShift8bSse2(t2); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + Vector128 t3SignedShift = SignedShift8bSse2(t3); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 + p0 = Sse2.AddSaturate(p0.AsSByte(), t2SignedShift).AsByte(); // p0 += t2 + q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3SignedShift).AsByte(); // q0 -= t3 + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + + // This is equivalent to signed (a + 1) >> 1 calculation. + t2 = Sse2.Add(t3.AsByte(), SignBit); + t3 = Sse2.Average(t2, Vector128.Zero); + t3 = Sse2.Subtract(t3.AsSByte(), SixtyFour).AsByte(); + + t3 = Sse2.And(notHev, t3); // if !hev + q1 = Sse2.SubtractSaturate(q1, t3); // q1 -= t3 + p1 = Sse2.AddSaturate(p1, t3); // p1 += t3 + p1 = Sse2.Xor(p1, SignBit); + q1 = Sse2.Xor(q1, SignBit); } private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) { - Vector128 three = Vector128.Create((byte)3).AsSByte(); - Vector128 four = Vector128.Create((byte)4).AsSByte(); - Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), three); - Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), four); + Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), Three); + Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), Four); v4 = SignedShift8bSse2(v4.AsByte()).AsSByte(); // v4 >> 3 v3 = SignedShift8bSse2(v3.AsByte()).AsSByte(); // v3 >> 3 q0 = Sse2.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4 p0 = Sse2.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3 } + + private static Vector128 GetNotHev(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int hevThresh) + { + Vector128 t1 = Abs(p1, q0); + Vector128 t2 = Abs(q1, q0); + + var h = Vector128.Create((byte)hevThresh); + Vector128 tMax = Sse2.Max(t1, t2); + + Vector128 tMaxH = Sse2.SubtractSaturate(tMax, h); + + // not_hev <= t1 && not_hev <= t2 + return Sse2.CompareEqual(tMaxH, Vector128.Zero); + } #endif private static void DoFilter4(Span p, int offset, int step) From d7032fe6ee7d43a4a6415a7a4d17f84afdb55e4f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Dec 2021 23:05:21 +1100 Subject: [PATCH 133/228] Update src/ImageSharp/Formats/Png/PngDecoderCore.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e741db72c..333b00f23 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -374,7 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Png // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. // For example, a gamma of 1/2.2 would be stored as 45455. - => pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F; + => pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) * 1e-5F; /// /// Initializes the image and various buffers needed for processing From 8fd9b7224cff177789f30ba14c2235f91ca64f8d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Dec 2021 00:37:05 +1100 Subject: [PATCH 134/228] Fix transparency chunk identification --- src/ImageSharp/Formats/Png/PngDecoder.cs | 120 +++++++++++++------ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 6 + 2 files changed, 88 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index 5637d5c7b..04e70c51d 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -34,26 +34,48 @@ namespace SixLabors.ImageSharp.Formats.Png PngMetadata meta = info.Metadata.GetPngMetadata(); PngColorType color = meta.ColorType.GetValueOrDefault(); PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); - return color switch + switch (color) { - PngColorType.Grayscale => (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream), - - PngColorType.Rgb => this.Decode(configuration, stream), - - PngColorType.Palette => this.Decode(configuration, stream), - - PngColorType.GrayscaleWithAlpha => (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream), - - PngColorType.RgbWithAlpha => (bits == PngBitDepth.Bit16) - ? this.Decode(configuration, stream) - : this.Decode(configuration, stream), - - _ => this.Decode(configuration, stream), - }; + case PngColorType.Grayscale: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + } + + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + case PngColorType.Rgb: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + } + + return !meta.HasTransparency + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + case PngColorType.Palette: + return this.Decode(configuration, stream); + + case PngColorType.GrayscaleWithAlpha: + return (bits == PngBitDepth.Bit16) + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + case PngColorType.RgbWithAlpha: + return (bits == PngBitDepth.Bit16) + ? this.Decode(configuration, stream) + : this.Decode(configuration, stream); + + default: + return this.Decode(configuration, stream); + } } /// @@ -74,26 +96,48 @@ namespace SixLabors.ImageSharp.Formats.Png PngMetadata meta = info.Metadata.GetPngMetadata(); PngColorType color = meta.ColorType.GetValueOrDefault(); PngBitDepth bits = meta.BitDepth.GetValueOrDefault(); - return color switch + switch (color) { - PngColorType.Grayscale => (bits == PngBitDepth.Bit16) - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), - - PngColorType.Rgb => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), - - PngColorType.Palette => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), - - PngColorType.GrayscaleWithAlpha => (bits == PngBitDepth.Bit16) - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), - - PngColorType.RgbWithAlpha => (bits == PngBitDepth.Bit16) - ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) - : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), - - _ => await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false), - }; + case PngColorType.Grayscale: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + } + + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.Rgb: + if (bits == PngBitDepth.Bit16) + { + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + } + + return !meta.HasTransparency + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.Palette: + return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.GrayscaleWithAlpha: + return (bits == PngBitDepth.Bit16) + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + case PngColorType.RgbWithAlpha: + return (bits == PngBitDepth.Bit16) + ? await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false) + : await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + + default: + return await this.DecodeAsync(configuration, stream, cancellationToken).ConfigureAwait(false); + } } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 333b00f23..ba737cb42 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -258,6 +258,12 @@ namespace SixLabors.ImageSharp.Formats.Png case PngChunkType.Data: this.SkipChunkDataAndCrc(chunk); break; + case PngChunkType.Transparency: + byte[] alpha = new byte[chunk.Length]; + chunk.Data.GetSpan().CopyTo(alpha); + this.paletteAlpha = alpha; + this.AssignTransparentMarkers(alpha, pngMetadata); + break; case PngChunkType.Text: this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; From eb68bb2d17732db0053804da46412f4b94839d11 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Dec 2021 00:37:15 +1100 Subject: [PATCH 135/228] Update PngDecoderTests.cs --- .../Formats/Png/PngDecoderTests.cs | 227 +++++++++--------- 1 file changed, 111 insertions(+), 116 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 9fc4d03dd..82b5f718b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -1,7 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; +using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; using SixLabors.ImageSharp.Formats.Png; @@ -20,9 +22,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png [Trait("Format", "Png")] public partial class PngDecoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - private static PngDecoder PngDecoder => new PngDecoder(); + private static PngDecoder PngDecoder => new(); public static readonly string[] CommonTestImages = { @@ -63,16 +65,51 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png TestImages.Png.Bad.ZlibZtxtBadHeader, }; + public static readonly TheoryData PixelFormatRange = new() + { + { TestImages.Png.Gray4Bpp, typeof(Image) }, + { TestImages.Png.L16Bit, typeof(Image) }, + { TestImages.Png.Gray1BitTrans, typeof(Image) }, + { TestImages.Png.Gray2BitTrans, typeof(Image) }, + { TestImages.Png.Gray4BitTrans, typeof(Image) }, + { TestImages.Png.GrayA8Bit, typeof(Image) }, + { TestImages.Png.GrayAlpha16Bit, typeof(Image) }, + { TestImages.Png.Palette8Bpp, typeof(Image) }, + { TestImages.Png.PalettedTwoColor, typeof(Image) }, + { TestImages.Png.Rainbow, typeof(Image) }, + { TestImages.Png.Rgb24BppTrans, typeof(Image) }, + { TestImages.Png.Kaboom, typeof(Image) }, + { TestImages.Png.Rgb48Bpp, typeof(Image) }, + { TestImages.Png.Rgb48BppTrans, typeof(Image) }, + { TestImages.Png.Rgba64Bpp, typeof(Image) }, + }; + + [Theory] + [MemberData(nameof(PixelFormatRange))] + public void Decode_NonGeneric_CreatesCorrectImageType(string path, Type type) + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + using var image = Image.Load(file); + Assert.IsType(type, image); + } + + [Theory] + [MemberData(nameof(PixelFormatRange))] + public async Task DecodeAsync_NonGeneric_CreatesCorrectImageType(string path, Type type) + { + string file = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, path); + using Image image = await Image.LoadAsync(file); + Assert.IsType(type, image); + } + [Theory] [WithFileCollection(nameof(CommonTestImages), PixelTypes.Rgba32)] public void Decode(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -81,11 +118,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_GrayWithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -95,11 +130,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_Interlaced(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -112,11 +145,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_Indexed(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -125,11 +156,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_48Bpp(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -138,11 +167,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_64Bpp(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -153,11 +180,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_L8bitInterlaced(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -165,11 +190,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_L16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -178,23 +201,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_GrayAlpha16Bit(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] - [WithFile(TestImages.Png.GrayA8BitInterlaced, PixelTypes)] + [WithFile(TestImages.Png.GrayA8BitInterlaced, TestPixelTypes)] public void Decoder_CanDecode_Grey8bitInterlaced_WithAlpha(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -202,23 +221,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decoder_CanDecode_CorruptedImages(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] - [WithFile(TestImages.Png.Splash, PixelTypes)] + [WithFile(TestImages.Png.Splash, TestPixelTypes)] public void Decoder_IsNotBoundToSinglePixelType(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); } [Theory] @@ -232,10 +247,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Identify(string imagePath, int expectedPixelSize) { var testFile = TestFile.Create(imagePath); - using (var stream = new MemoryStream(testFile.Bytes, false)) - { - Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); - } + using var stream = new MemoryStream(testFile.Bytes, false); + Assert.Equal(expectedPixelSize, Image.Identify(stream)?.PixelType?.BitsPerPixel); } [Theory] @@ -246,10 +259,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); }); Assert.NotNull(ex); Assert.Contains("PNG Image does not contain a data chunk", ex.Message); @@ -264,10 +275,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); }); Assert.NotNull(ex); Assert.Contains("Invalid or unsupported bit depth", ex.Message); @@ -282,10 +291,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); }); Assert.NotNull(ex); Assert.Contains("Invalid or unsupported color type", ex.Message); @@ -300,11 +307,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -318,11 +323,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -336,11 +339,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -354,15 +355,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); - } + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); } }); Assert.Null(ex); @@ -377,11 +376,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); - image.CompareToOriginal(provider, ImageComparer.Exact); - } + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + image.CompareToOriginal(provider, ImageComparer.Exact); }); Assert.Null(ex); } @@ -395,15 +392,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png System.Exception ex = Record.Exception( () => { - using (Image image = provider.GetImage(PngDecoder)) - { - image.DebugSave(provider); + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); - // We don't have another x-plat reference decoder that can be compared for this image. - if (TestEnvironment.IsWindows) - { - image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); - } + // We don't have another x-plat reference decoder that can be compared for this image. + if (TestEnvironment.IsWindows) + { + image.CompareToOriginal(provider, ImageComparer.Exact, SystemDrawingReferenceDecoder.Instance); } }); Assert.NotNull(ex); From a77fe46f3f1dcc0452c6f547318fb1e8fcf2446c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Dec 2021 18:36:56 +0100 Subject: [PATCH 136/228] Add SSE2 version of HFilter16i --- .../Formats/Webp/Lossy/LossyUtils.cs | 143 ++++++++++++------ 1 file changed, 96 insertions(+), 47 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 9462db630..e28c8a4e8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -978,12 +978,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - // beginning of p1 + // Beginning of p1 p = p.Slice(offset - 2); - Load16x4Sse2(p, p.Slice(8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); + Load16x4(p, p.Slice(8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); - Store16x4Sse2(p1, p0, q0, q1, p, p.Slice(8 * stride), stride); + Store16x4(p1, p0, q0, q1, p, p.Slice(8 * stride), stride); } else #endif @@ -1002,7 +1002,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh) { - for (int k = 3; k > 0; --k) + for (int k = 3; k > 0; k--) { offset += 4 * stride; SimpleVFilter16(p, offset, stride, thresh); @@ -1011,7 +1011,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh) { - for (int k = 3; k > 0; --k) + for (int k = 3; k > 0; k--) { offset += 4; SimpleHFilter16(p, offset, stride, thresh); @@ -1028,7 +1028,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { - for (int k = 3; k > 0; --k) + for (int k = 3; k > 0; k--) { offset += 4 * stride; FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); @@ -1037,10 +1037,47 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void HFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { - for (int k = 3; k > 0; --k) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4; - FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + Load16x4(p.Slice(offset), p.Slice(offset + (8 * stride)), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + + Vector128 mask; + for (int k = 3; k > 0; k--) + { + // Beginning of p1. + Span b = p.Slice(offset + 2); + + // Beginning of q0 (and next span). + offset += 4; + + // Compute partial mask. + mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Load16x4(p.Slice(offset), p.Slice(offset + (8 * stride)), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); + mask = Sse2.Max(mask, Abs(tmp1, tmp2)); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, tmp1)); + + ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); + Store16x4(p1, p0, p3, p2, b, b.Slice(8 * stride), stride); + + // Rotate samples. + p1 = tmp1; + p0 = tmp2; + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4; + FilterLoop24(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + } } } @@ -1248,16 +1285,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Applies filter on 2 pixels (p0 and q0) private static void DoFilter2Sse2(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int thresh) { - // Convert p1/q1 to byte (for GetBaseDeltaSse2). + // Convert p1/q1 to byte (for GetBaseDelta). Vector128 p1s = Sse2.Xor(p1, SignBit); Vector128 q1s = Sse2.Xor(q1, SignBit); - Vector128 mask = NeedsFilterSse2(p1, p0, q0, q1, thresh); + Vector128 mask = NeedsFilter(p1, p0, q0, q1, thresh); // Flip sign. p0 = Sse2.Xor(p0, SignBit); q0 = Sse2.Xor(q0, SignBit); - Vector128 a = GetBaseDeltaSse2(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); + Vector128 a = GetBaseDelta(p1s.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1s.AsSByte()).AsByte(); // Mask filter values we don't care about. a = Sse2.And(a, mask); @@ -1281,33 +1318,33 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy q0 = Sse2.Xor(q0, SignBit); q1 = Sse2.Xor(q1, SignBit); - Vector128 t1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1 - Vector128 t2 = Sse2.AndNot(notHev, t1); // hev(p1 - q1) - Vector128 t3 = Sse2.SubtractSaturate(q0, p0); // q0 - p0 + Vector128 t1 = Sse2.SubtractSaturate(p1.AsSByte(), q1.AsSByte()); // p1 - q1 + t1 = Sse2.AndNot(notHev, t1.AsByte()).AsSByte(); // hev(p1 - q1) + Vector128 t2 = Sse2.SubtractSaturate(q0.AsSByte(), p0.AsSByte()); // q0 - p0 t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) t1 = Sse2.AddSaturate(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) - t1 = Sse2.Add(t1, mask); // mask filter values we don't care about. - - t2 = Sse2.AddSaturate(t1.AsSByte(), Three).AsByte(); // 3 * (q0 - p0) + hev(p1 - q1) + 3 - t3 = Sse2.AddSaturate(t1.AsSByte(), Four).AsByte(); // 3 * (q0 - p0) + hev(p1 - q1) + 4 - Vector128 t2SignedShift = SignedShift8bSse2(t2); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 - Vector128 t3SignedShift = SignedShift8bSse2(t3); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 - p0 = Sse2.AddSaturate(p0.AsSByte(), t2SignedShift).AsByte(); // p0 += t2 - q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3SignedShift).AsByte(); // q0 -= t3 + t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about. + + t2 = Sse2.AddSaturate(t1, Three); // 3 * (q0 - p0) + hev(p1 - q1) + 3 + Vector128 t3 = Sse2.AddSaturate(t1, Four); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 + p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2 + q0 = Sse2.SubtractSaturate(q0.AsSByte(), t3).AsByte(); // q0 -= t3 p0 = Sse2.Xor(p0, SignBit); q0 = Sse2.Xor(q0, SignBit); // This is equivalent to signed (a + 1) >> 1 calculation. - t2 = Sse2.Add(t3.AsByte(), SignBit); - t3 = Sse2.Average(t2, Vector128.Zero); - t3 = Sse2.Subtract(t3.AsSByte(), SixtyFour).AsByte(); + t2 = Sse2.Add(t3, SignBit.AsSByte()); + t3 = Sse2.Average(t2.AsByte(), Vector128.Zero).AsSByte(); + t3 = Sse2.Subtract(t3, SixtyFour); - t3 = Sse2.And(notHev, t3); // if !hev - q1 = Sse2.SubtractSaturate(q1, t3); // q1 -= t3 - p1 = Sse2.AddSaturate(p1, t3); // p1 += t3 - p1 = Sse2.Xor(p1, SignBit); - q1 = Sse2.Xor(q1, SignBit); + t3 = Sse2.And(notHev, t3.AsByte()).AsSByte(); // if !hev + q1 = Sse2.SubtractSaturate(q1.AsSByte(), t3).AsByte(); // q1 -= t3 + p1 = Sse2.AddSaturate(p1.AsSByte(), t3).AsByte(); // p1 += t3 + p1 = Sse2.Xor(p1.AsByte(), SignBit); + q1 = Sse2.Xor(q1.AsByte(), SignBit); } private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) @@ -1315,8 +1352,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), Three); Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), Four); - v4 = SignedShift8bSse2(v4.AsByte()).AsSByte(); // v4 >> 3 - v3 = SignedShift8bSse2(v3.AsByte()).AsSByte(); // v3 >> 3 + v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3 + v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 3 q0 = Sse2.SubtractSaturate(q0.AsSByte(), v4).AsByte(); // q0 -= v4 p0 = Sse2.AddSaturate(p0.AsSByte(), v3).AsByte(); // p0 += v3 } @@ -1413,7 +1450,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } #if SUPPORTS_RUNTIME_INTRINSICS - private static Vector128 NeedsFilterSse2(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh) + private static Vector128 NeedsFilter(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh) { var mthresh = Vector128.Create((byte)thresh); Vector128 t1 = Abs(p1, q1); // abs(p1 - q1) @@ -1430,7 +1467,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Sse2.CompareEqual(t7, Vector128.Zero); } - private static void Load16x4Sse2(Span r0, Span r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) + private static void Load16x4(Span r0, Span r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) { // Assume the pixels around the edge (|) are numbered as follows // 00 01 | 02 03 @@ -1447,8 +1484,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 - Load8x4Sse2(r0, stride, out Vector128 t1, out Vector128 t2); - Load8x4Sse2(r8, stride, out p0, out q1); + Load8x4(r0, stride, out Vector128 t1, out Vector128 t2); + Load8x4(r8, stride, out p0, out q1); // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 @@ -1461,7 +1498,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Reads 8 rows across a vertical edge. - private static void Load8x4Sse2(Span b, int stride, out Vector128 p, out Vector128 q) + private static void Load8x4(Span b, int stride, out Vector128 p, out Vector128 q) { // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 @@ -1494,7 +1531,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Transpose back and store - private static void Store16x4Sse2(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, Span r0, Span r8, int stride) + private static void Store16x4(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, Span r0, Span r8, int stride) { // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 @@ -1518,14 +1555,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy p1s = Sse2.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte(); q1s = Sse2.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte(); - Store4x4Sse2(p0s, r0, stride); - Store4x4Sse2(q0s, r0.Slice(4 * stride), stride); + Store4x4(p0s, r0, stride); + Store4x4(q0s, r0.Slice(4 * stride), stride); - Store4x4Sse2(p1s, r8, stride); - Store4x4Sse2(q1s, r8.Slice(4 * stride), stride); + Store4x4(p1s, r8, stride); + Store4x4(q1s, r8.Slice(4 * stride), stride); } - private static void Store4x4Sse2(Vector128 x, Span dst, int stride) + private static void Store4x4(Vector128 x, Span dst, int stride) { int offset = 0; ref byte dstRef = ref MemoryMarshal.GetReference(dst); @@ -1538,7 +1575,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 GetBaseDeltaSse2(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) + private static Vector128 GetBaseDelta(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1) { // Beware of addition order, for saturation! Vector128 p1q1 = Sse2.SubtractSaturate(p1, q1); // p1 - q1 @@ -1550,8 +1587,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return s3; } + // Shift each byte of "x" by 3 bits while preserving by the sign bit. [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 SignedShift8bSse2(Vector128 x) + private static Vector128 SignedShift8b(Vector128 x) { Vector128 low0 = Sse2.UnpackLow(Vector128.Zero, x); Vector128 high0 = Sse2.UnpackHigh(Vector128.Zero, x); @@ -1561,9 +1599,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Sse2.PackSignedSaturate(low1, high1); } + private static void ComplexMask(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh, int ithresh, ref Vector128 mask) + { + var it = Vector128.Create((byte)ithresh); + Vector128 diff = Sse2.SubtractSaturate(mask, it); + Vector128 threshMask = Sse2.CompareEqual(diff, Vector128.Zero); + Vector128 filterMask = NeedsFilter(p1, p0, q0, q1, thresh); + + mask = Sse2.And(threshMask, filterMask); + } + // Compute abs(p - q) = subs(p - q) OR subs(q - p) [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 Abs(Vector128 p, Vector128 q) => Sse2.Or(Sse2.SubtractSaturate(q, p), Sse2.SubtractSaturate(p, q)); + private static Vector128 Abs(Vector128 p, Vector128 q) + => Sse2.Or(Sse2.SubtractSaturate(q, p), Sse2.SubtractSaturate(p, q)); #endif [MethodImpl(InliningOptions.ShortMethod)] From 32db2d43088e9b99bc704f084d5fecb93cde3c28 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Dec 2021 19:55:46 +0100 Subject: [PATCH 137/228] Add SSE2 version of VFilter16i --- .../Formats/Webp/Lossy/LossyUtils.cs | 55 ++++++++++++++++++- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index e28c8a4e8..7daec2d7e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1028,10 +1028,59 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { - for (int k = 3; k > 0; k--) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4 * stride; - FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + ref byte pRef = ref MemoryMarshal.GetReference(p); + Vector128 p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + for (int k = 3; k > 0; k--) + { + // Beginning of p1. + Span b = p.Slice(offset + (2 * stride)); + offset += 4 * stride; + + Vector128 mask = Abs(p0, p1); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + p3 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); + Vector128 tmp1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + Vector128 tmp2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + mask = Sse2.Max(mask, Abs(tmp1, tmp2)); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, tmp1)); + + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); + + // Store. + ref byte outputRef = ref MemoryMarshal.GetReference(b); + Unsafe.As>(ref outputRef) = p1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 2)) = p3.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, stride * 3)) = p2.AsInt32(); + + // Rotate samples. + p1 = tmp1; + p0 = tmp2; + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4 * stride; + FilterLoop24(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + } } } From 56a7ebe2ae4da5e3426d52dc2746efe2d99c9c7f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Dec 2021 21:27:15 +0100 Subject: [PATCH 138/228] Add SSE2 version of VFilter8i --- .../Formats/Webp/Lossy/LossyUtils.cs | 60 ++++++++++++++++++- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 7daec2d7e..2bc1983d4 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1148,9 +1148,47 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - int offset4mulstride = offset + (4 * stride); - FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // load uv h-edges. + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Vector128 t2 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); + Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, p1)); + + offset += 4 * stride; + + Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + t1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); + t2 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); + + // Store. + StoreUv(p1, ref uRef, ref vRef, offset + (-2 * stride)); + StoreUv(p0, ref uRef, ref vRef, offset + (-1 * stride)); + StoreUv(q1, ref uRef, ref vRef, offset); + StoreUv(q1, ref uRef, ref vRef, offset + stride); + } + else +#endif + { + int offset4mulstride = offset + (4 * stride); + FilterLoop24(u, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offset4mulstride, stride, 1, 8, thresh, ithresh, hevThresh); + } } [MethodImpl(InliningOptions.ShortMethod)] @@ -1648,6 +1686,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Sse2.PackSignedSaturate(low1, high1); } + [MethodImpl(InliningOptions.ShortMethod)] private static void ComplexMask(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, int thresh, int ithresh, ref Vector128 mask) { var it = Vector128.Create((byte)ithresh); @@ -1658,6 +1697,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy mask = Sse2.And(threshMask, filterMask); } + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 LoadUvEdge(ref byte uRef, ref byte vRef, int offset) + { + var uVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref uRef, offset)), 0); + var vVec = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref vRef, offset)), 0); + return Sse2.UnpackLow(uVec, vVec).AsByte(); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static void StoreUv(Vector128 x, ref byte uRef, ref byte vRef, int offset) + { + Unsafe.As>(ref Unsafe.Add(ref uRef, offset)) = x.GetLower(); + Unsafe.As>(ref Unsafe.Add(ref vRef, offset)) = x.GetUpper(); + } + // Compute abs(p - q) = subs(p - q) OR subs(q - p) [MethodImpl(InliningOptions.ShortMethod)] private static Vector128 Abs(Vector128 p, Vector128 q) From 87ce4e53e249b280a13ea06af49041a2309d7658 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 1 Dec 2021 22:11:28 +0100 Subject: [PATCH 139/228] Various review comments --- src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs | 1 + src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 10 +-- .../Formats/Pbm/PbmDecoderTests.cs | 1 + ...RoundTripTests.cs => PbmRoundTripTests.cs} | 24 ++++++- .../Formats/Pbm/PbmTestUtils.cs | 65 ------------------- 5 files changed, 29 insertions(+), 72 deletions(-) rename tests/ImageSharp.Tests/Formats/Pbm/{RoundTripTests.cs => PbmRoundTripTests.cs} (62%) delete mode 100644 tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs index eb1ba8140..158786e3c 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs @@ -88,6 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm if (this.colorType != PbmColorType.BlackAndWhite) { this.maxPixelValue = this.options.MaxPixelValue ?? metadata.MaxPixelValue; + this.maxPixelValue = Math.Max(this.maxPixelValue, PbmConstants.MaxLength); } } diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index b67f0a077..2e7c60e5e 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -76,7 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscale); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscale); Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) @@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscaleWide); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelGrayscaleWide); Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) @@ -140,7 +140,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgb); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgb); Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) @@ -178,7 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgbWide); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelRgbWide); Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelBlackAndWhite); + using IMemoryOwner plainMemory = allocator.Allocate(width * MaxCharsPerPixelBlackAndWhite); Span plainSpan = plainMemory.GetSpan(); for (int y = 0; y < height; y++) diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 6c84fba9e..479db2ca5 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -84,6 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); + image.DebugSave(provider); image.CompareToReferenceOutput(provider, grayscale: isGrayscale); } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/RoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs similarity index 62% rename from tests/ImageSharp.Tests/Formats/Pbm/RoundTripTests.cs rename to tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs index 391e8c054..715a1e07e 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/RoundTripTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -11,8 +11,28 @@ using static SixLabors.ImageSharp.Tests.TestImages.Pbm; namespace SixLabors.ImageSharp.Tests.Formats.Pbm { [Trait("Format", "Pbm")] - public class RoundTripTests + public class PbmRoundTripTests { + [Theory] + [InlineData(BlackAndWhiteBinary)] + [InlineData(GrayscalePlain)] + [InlineData(GrayscaleBinary)] + public void PbmGrayscaleImageCanRoundTrip(string imagePath) + { + // Arrange + var testFile = TestFile.Create(imagePath); + using var stream = new MemoryStream(testFile.Bytes, false); + + // Act + using var originalImage = Image.Load(stream); + Image colorImage = originalImage.CloneAs(); + using Image encodedImage = this.RoundTrip(colorImage); + + // Assert + Assert.NotNull(encodedImage); + ImageComparer.Exact.VerifySimilarity(colorImage, encodedImage); + } + [Theory] [InlineData(RgbPlain)] [InlineData(RgbBinary)] @@ -37,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm using var decodedStream = new MemoryStream(); originalImage.SaveAsPbm(decodedStream); decodedStream.Seek(0, SeekOrigin.Begin); - var encodedImage = (Image)Image.Load(decodedStream); + var encodedImage = Image.Load(decodedStream); return encodedImage; } } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs deleted file mode 100644 index 7b701fe3d..000000000 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmTestUtils.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.IO; -using ImageMagick; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Pbm -{ - public static class PbmTestUtils - { - public static void CompareWithReferenceDecoder( - TestImageProvider provider, - Image image, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - string path = TestImageProvider.GetFilePathOrNull(provider); - if (path == null) - { - throw new InvalidOperationException("CompareToOriginal() works only with file providers!"); - } - - var testFile = TestFile.Create(path); - Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); - if (useExactComparer) - { - ImageComparer.Exact.VerifySimilarity(magickImage, image); - } - else - { - ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); - } - } - - public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - using (var magickImage = new MagickImage(fileInfo)) - { - magickImage.AutoOrient(); - var result = new Image(configuration, magickImage.Width, magickImage.Height); - - Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); - - using (IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe()) - { - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels, - resultPixels.Length); - } - - return result; - } - } - } -} From a5e1723be913544e4e7c7f52682a096f6e787431 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 1 Dec 2021 22:44:36 +0100 Subject: [PATCH 140/228] Dispose of images after use, in Pbm Round Trip Test --- tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs index 715a1e07e..1735efdce 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm // Act using var originalImage = Image.Load(stream); - Image colorImage = originalImage.CloneAs(); + using Image colorImage = originalImage.CloneAs(); using Image encodedImage = this.RoundTrip(colorImage); // Assert From 6b723baefc9390b7545409ec1e0ae3d87038d19f Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 1 Dec 2021 21:42:17 +0100 Subject: [PATCH 141/228] Add SSE2 version of HFilter8i --- .../Formats/Webp/Lossy/LossyUtils.cs | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 2bc1983d4..37cea73ca 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1194,9 +1194,37 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter8i(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - int offsetPlus4 = offset + 4; - FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, p1)); + + // Beginning of q0. + offset += 4; + + Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t2, t1)); + mask = Sse2.Max(mask, Abs(t1, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter4Sse2(ref p1, ref p0, ref q0, ref q1, mask, hevThresh); + + // Beginning of p1. + offset -= 2; + Store16x4(p1, p0, q0, q1, u.Slice(offset), v.Slice(offset), stride); + } + else +#endif + { + int offsetPlus4 = offset + 4; + FilterLoop24(u, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop24(v, offsetPlus4, 1, stride, 8, thresh, ithresh, hevThresh); + } } public static void Mean16x4(Span input, Span dc) @@ -1447,7 +1475,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static Vector128 GetNotHev(ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, int hevThresh) { - Vector128 t1 = Abs(p1, q0); + Vector128 t1 = Abs(p1, p0); Vector128 t2 = Abs(q1, q0); var h = Vector128.Create((byte)hevThresh); @@ -1460,6 +1488,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } #endif + // Applies filter on 4 pixels (p1, p0, q0 and q1) private static void DoFilter4(Span p, int offset, int step) { // 4 pixels in, 4 pixels out. @@ -1478,6 +1507,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy p[offset + step] = WebpLookupTables.Clip1(q1 - a3); } + // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) private static void DoFilter6(Span p, int offset, int step) { // 6 pixels in, 6 pixels out. From 45e4768b91416a07ab9ada37c389697d89e7bd7b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 2 Dec 2021 09:13:06 +0300 Subject: [PATCH 142/228] Vector color converters can work with any available vector size instead of 8 --- ...s => JpegColorConverter.FromCmykVector.cs} | 6 ++-- ...JpegColorConverter.FromGrayScaleVector.cs} | 6 ++-- ...cs => JpegColorConverter.FromRgbVector.cs} | 6 ++-- ... => JpegColorConverter.FromYCbCrVector.cs} | 6 ++-- ...s => JpegColorConverter.FromYccKVector.cs} | 6 ++-- .../ColorConverters/JpegColorConverterBase.cs | 10 +++---- .../JpegColorConverterVector.cs | 30 +++++++++---------- .../ColorConversion/CmykColorConversion.cs | 2 +- .../ColorConversion/RgbColorConversion.cs | 2 +- .../ColorConversion/YCbCrColorConversion.cs | 2 +- .../ColorConversion/YccKColorConverter.cs | 2 +- .../Formats/Jpg/JpegColorConverterTests.cs | 8 ++--- 12 files changed, 43 insertions(+), 43 deletions(-) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromCmykVector8.cs => JpegColorConverter.FromCmykVector.cs} (90%) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromGrayScaleVector8.cs => JpegColorConverter.FromGrayScaleVector.cs} (84%) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromRgbVector8.cs => JpegColorConverter.FromRgbVector.cs} (89%) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromYCbCrVector8.cs => JpegColorConverter.FromYCbCrVector.cs} (93%) rename src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/{JpegColorConverter.FromYccKVector8.cs => JpegColorConverter.FromYccKVector.cs} (94%) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs similarity index 90% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs index 685e25aad..9b123547b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -9,9 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykVector8 : VectorizedJpegColorConverter + internal sealed class FromCmykVector : VectorizedJpegColorConverter { - public FromCmykVector8(int precision) + public FromCmykVector(int precision) : base(JpegColorSpace.Cmyk, precision) { } @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var scale = new Vector(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { ref Vector c = ref Unsafe.Add(ref cBase, i); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs similarity index 84% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs index 6aa0b59a9..1ca329a12 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -9,9 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayScaleVector8 : VectorizedJpegColorConverter + internal sealed class FromGrayScaleVector : VectorizedJpegColorConverter { - public FromGrayScaleVector8(int precision) + public FromGrayScaleVector(int precision) : base(JpegColorSpace.Grayscale, precision) { } @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var scale = new Vector(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { ref Vector c0 = ref Unsafe.Add(ref cBase, i); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs similarity index 89% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs index 0dc440b7d..b2b059cdc 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -9,9 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbVector8 : VectorizedJpegColorConverter + internal sealed class FromRgbVector : VectorizedJpegColorConverter { - public FromRgbVector8(int precision) + public FromRgbVector(int precision) : base(JpegColorSpace.RGB, precision) { } @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var scale = new Vector(1 / this.MaximumValue); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { ref Vector r = ref Unsafe.Add(ref rBase, i); diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs similarity index 93% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index da71e2466..f34014332 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -10,9 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrVector8 : VectorizedJpegColorConverter + internal sealed class FromYCbCrVector : VectorizedJpegColorConverter { - public FromYCbCrVector8(int precision) + public FromYCbCrVector(int precision) : base(JpegColorSpace.YCbCr, precision) { } @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); var bCbMult = new Vector(FromYCbCrScalar.BCbMult); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs similarity index 94% rename from src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs rename to src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs index 152a0793c..1e826c2c0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -9,9 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKVector8 : VectorizedJpegColorConverter + internal sealed class FromYccKVector : VectorizedJpegColorConverter { - public FromYccKVector8(int precision) + public FromYccKVector(int precision) : base(JpegColorSpace.Ycck, precision) { } @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters var gCrMult = new Vector(-FromYCbCrScalar.GCrMult); var bCbMult = new Vector(FromYCbCrScalar.BCbMult); - nint n = values.Component0.Length / 8; + nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) { // y = yVals[i]; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index b8fd169e9..0ab7a108f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters #if SUPPORTS_RUNTIME_INTRINSICS yield return new FromYCbCrAvx(precision); #endif - yield return new FromYCbCrVector8(precision); + yield return new FromYCbCrVector(precision); yield return new FromYCbCrScalar(precision); } @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters #if SUPPORTS_RUNTIME_INTRINSICS yield return new FromYccKAvx(precision); #endif - yield return new FromYccKVector8(precision); + yield return new FromYccKVector(precision); yield return new FromYccKScalar(precision); } @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters #if SUPPORTS_RUNTIME_INTRINSICS yield return new FromCmykAvx(precision); #endif - yield return new FromCmykVector8(precision); + yield return new FromCmykVector(precision); yield return new FromCmykScalar(precision); } @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters #if SUPPORTS_RUNTIME_INTRINSICS yield return new FromGrayscaleAvx(precision); #endif - yield return new FromGrayScaleVector8(precision); + yield return new FromGrayScaleVector(precision); yield return new FromGrayscaleScalar(precision); } @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters #if SUPPORTS_RUNTIME_INTRINSICS yield return new FromRgbAvx(precision); #endif - yield return new FromRgbVector8(precision); + yield return new FromRgbVector(precision); yield return new FromRgbScalar(precision); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index 3cd295f0b..42f6cab5c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { @@ -9,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { /// /// abstract base for implementations - /// based on API. + /// based on API. /// /// /// Converters of this family can work with data of any size. @@ -25,27 +26,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public sealed override bool IsAvailable => SimdUtils.HasVector8; + public sealed override bool IsAvailable => Vector.Count % 4 == 0; public override void ConvertToRgbInplace(in ComponentValues values) { + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware"); + int length = values.Component0.Length; - int remainder = values.Component0.Length % 8; + int remainder = values.Component0.Length % Vector.Count; + + // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide + // Thus there's no need to check whether simdCount is greater than zero int simdCount = length - remainder; - if (simdCount > 0) + this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); + + // There's actually a lot of image/photo resolutions which won't have + // a remainder so it's better to check here than spend useless virtual call + if (remainder > 0) { - // This implementation is actually AVX specific. - // An AVX register is capable of storing 8 float-s. - if (!this.IsAvailable) - { - throw new InvalidOperationException( - "This converter can be used only on architecture having 256 byte floating point SIMD registers!"); - } - - this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); + this.ConvertCoreInplace(values.Slice(simdCount, remainder)); } - - this.ConvertCoreInplace(values.Slice(simdCount, remainder)); } protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException(); diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs index 36e666957..0f791ed8e 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromCmykVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromCmykVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs index 69c1a3974..987a93194 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromRgbVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromRgbVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs index 656cae104..8d6846033 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYCbCrVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYCbCrVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs index 6c0583b62..7e9edc918 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg { var values = new JpegColorConverterBase.ComponentValues(this.Input, 0); - new JpegColorConverterBase.FromYccKVector8(8).ConvertToRgbInplace(values); + new JpegColorConverterBase.FromYccKVector(8).ConvertToRgbInplace(values); } #if SUPPORTS_RUNTIME_INTRINSICS diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 7b91e1a8a..fbbb73b15 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverterBase.FromYCbCrVector8(8), + new JpegColorConverterBase.FromYCbCrVector(8), 3, inputBufferLength, resultBufferLength, @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverterBase.FromCmykVector8(8), + new JpegColorConverterBase.FromCmykVector(8), 4, inputBufferLength, resultBufferLength, @@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverterBase.FromRgbVector8(8), + new JpegColorConverterBase.FromRgbVector(8), 3, inputBufferLength, resultBufferLength, @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } ValidateConversion( - new JpegColorConverterBase.FromYccKVector8(8), + new JpegColorConverterBase.FromYccKVector(8), 4, inputBufferLength, resultBufferLength, From aa2422453126365b96f2c80e632e3c55c49e6d32 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 2 Dec 2021 10:54:46 +0300 Subject: [PATCH 143/228] CMYK to RGB converter performance tweaks --- .../ColorConverters/JpegColorConverter.FromCmykAvx.cs | 8 ++++---- .../JpegColorConverter.FromCmykScalar.cs | 11 ++++++----- .../JpegColorConverter.FromCmykVector.cs | 11 ++++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs index 4c89fc6fa..d627f7eaf 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); // Used for the color conversion - var scale = Vector256.Create(1 / this.MaximumValue); + var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue)); nint n = values.Component0.Length / Vector256.Count; for (nint i = 0; i < n; i++) @@ -41,9 +41,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters Vector256 k = Unsafe.Add(ref c3Base, i); k = Avx.Multiply(k, scale); - c = Avx.Multiply(Avx.Multiply(c, k), scale); - m = Avx.Multiply(Avx.Multiply(m, k), scale); - y = Avx.Multiply(Avx.Multiply(y, k), scale); + c = Avx.Multiply(c, k); + m = Avx.Multiply(m, k); + y = Avx.Multiply(y, k); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs index 057d7846a..e70aa7cb4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -24,17 +24,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters Span c2 = values.Component2; Span c3 = values.Component3; - float scale = 1 / maxValue; + float scale = 1 / (maxValue * maxValue); for (int i = 0; i < c0.Length; i++) { float c = c0[i]; float m = c1[i]; float y = c2[i]; - float k = c3[i] / maxValue; + float k = c3[i]; - c0[i] = c * k * scale; - c1[i] = m * k * scale; - c2[i] = y * k * scale; + k *= scale; + c0[i] = c * k; + c1[i] = m * k; + c2[i] = y * k; } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs index 9b123547b..8fd918140 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector kBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(values.Component3)); - var scale = new Vector(1 / this.MaximumValue); + var scale = new Vector(1 / (this.MaximumValue * this.MaximumValue)); nint n = values.Component0.Length / Vector.Count; for (nint i = 0; i < n; i++) @@ -35,11 +35,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters ref Vector c = ref Unsafe.Add(ref cBase, i); ref Vector m = ref Unsafe.Add(ref mBase, i); ref Vector y = ref Unsafe.Add(ref yBase, i); - Vector k = Unsafe.Add(ref kBase, i) * scale; + Vector k = Unsafe.Add(ref kBase, i); - c = c * k * scale; - m = m * k * scale; - y = y * k * scale; + k *= scale; + c *= k; + m *= k; + y *= k; } } From 5605de5b19c8462848e91b22c23b3ae04bb51d2b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 2 Dec 2021 11:13:00 +0300 Subject: [PATCH 144/228] Fixed existing tests, added grayscale vector test and fixed tests naming --- .../Formats/Jpg/JpegColorConverterTests.cs | 65 ++++++++++++++----- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index fbbb73b15..75d12710d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -54,16 +54,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [MemberData(nameof(CommonConversionData))] - public void FromYCbCrVector8(int inputBufferLength, int resultBufferLength, int seed) + public void FromYCbCrVector(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasVector8) + var converter = new JpegColorConverterBase.FromYCbCrVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromYCbCrVector(8), + converter, 3, inputBufferLength, resultBufferLength, @@ -119,16 +122,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [MemberData(nameof(CommonConversionData))] - public void FromCmykVector8(int inputBufferLength, int resultBufferLength, int seed) + public void FromCmykVector(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasVector8) + var converter = new JpegColorConverterBase.FromCmykVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromCmykVector(8), + converter, 4, inputBufferLength, resultBufferLength, @@ -182,6 +188,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed); } + [Theory] + [MemberData(nameof(CommonConversionData))] + public void FromGrayscaleVector(int inputBufferLength, int resultBufferLength, int seed) + { + var converter = new JpegColorConverterBase.FromGrayScaleVector(8); + + if (!converter.IsAvailable) + { + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); + return; + } + + ValidateConversion( + converter, + 1, + inputBufferLength, + resultBufferLength, + seed); + } + #if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(CommonConversionData))] @@ -231,16 +258,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [MemberData(nameof(CommonConversionData))] - public void FromRgbVector8(int inputBufferLength, int resultBufferLength, int seed) + public void FromRgbVector(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasVector8) + var converter = new JpegColorConverterBase.FromRgbVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromRgbVector(8), + converter, 3, inputBufferLength, resultBufferLength, @@ -296,16 +326,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [MemberData(nameof(CommonConversionData))] - public void FromYccKVector8(int inputBufferLength, int resultBufferLength, int seed) + public void FromYccKVector(int inputBufferLength, int resultBufferLength, int seed) { - if (!SimdUtils.HasVector8) + var converter = new JpegColorConverterBase.FromYccKVector(8); + + if (!converter.IsAvailable) { - this.Output.WriteLine("No AVX2 present, skipping test!"); + this.Output.WriteLine( + $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); return; } ValidateConversion( - new JpegColorConverterBase.FromYccKVector(8), + converter, 4, inputBufferLength, resultBufferLength, From f10fe55844f8fe9c6cabca11b412013e6a396118 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 2 Dec 2021 11:44:00 +0300 Subject: [PATCH 145/228] Added tests, removed duplicated tests --- .../Jpeg/Components/Decoder/JpegColorSpace.cs | 15 +++ .../Formats/Jpg/JpegColorConverterTests.cs | 109 ++++++++---------- 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs index 90162aba3..7ef280932 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegColorSpace.cs @@ -10,14 +10,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Undefined = 0, + /// + /// Color space with 1 component. + /// Grayscale, + /// + /// Color space with 4 components. + /// Ycck, + /// + /// Color space with 4 components. + /// Cmyk, + /// + /// Color space with 3 components. + /// RGB, + /// + /// Color space with 3 components. + /// YCbCr } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 75d12710d..5c2f45307 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -40,6 +40,55 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } + [Fact] + public void GetConverterThrowsExceptionOnInvalidColorSpace() + { + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.Undefined, 8)); + } + + [Fact] + public void GetConverterThrowsExceptionOnInvalidPrecision() + { + // Valid precisions: 8 & 12 bit + Assert.Throws(() => JpegColorConverterBase.GetConverter(JpegColorSpace.YCbCr, 9)); + } + + [Theory] + [InlineData(JpegColorSpace.Grayscale, 8)] + [InlineData(JpegColorSpace.Grayscale, 12)] + [InlineData(JpegColorSpace.Ycck, 8)] + [InlineData(JpegColorSpace.Ycck, 12)] + [InlineData(JpegColorSpace.Cmyk, 8)] + [InlineData(JpegColorSpace.Cmyk, 12)] + [InlineData(JpegColorSpace.RGB, 8)] + [InlineData(JpegColorSpace.RGB, 12)] + [InlineData(JpegColorSpace.YCbCr, 8)] + [InlineData(JpegColorSpace.YCbCr, 12)] + internal void GetConverterReturnsValidConverter(JpegColorSpace colorSpace, int precision) + { + var converter = JpegColorConverterBase.GetConverter(colorSpace, precision); + + Assert.NotNull(converter); + Assert.Equal(colorSpace, converter.ColorSpace); + Assert.Equal(precision, converter.Precision); + } + + [Theory] + [InlineData(JpegColorSpace.Grayscale, 1)] + [InlineData(JpegColorSpace.Ycck, 4)] + [InlineData(JpegColorSpace.Cmyk, 4)] + [InlineData(JpegColorSpace.RGB, 3)] + [InlineData(JpegColorSpace.YCbCr, 3)] + internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) + { + ValidateConversion( + colorSpace, + componentCount, + 40, + 40, + 1); + } + [Theory] [MemberData(nameof(CommonConversionData))] public void FromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) @@ -96,18 +145,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } #endif - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCr_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.YCbCr, - 3, - inputBufferLength, - resultBufferLength, - seed); - } - [Theory] [MemberData(nameof(CommonConversionData))] public void FromCmykBasic(int inputBufferLength, int resultBufferLength, int seed) @@ -164,18 +201,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } #endif - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmyk_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.Cmyk, - 4, - inputBufferLength, - resultBufferLength, - seed); - } - [Theory] [MemberData(nameof(CommonConversionData))] public void FromGrayscaleBasic(int inputBufferLength, int resultBufferLength, int seed) @@ -232,18 +257,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } #endif - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromGraysacle_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.Grayscale, - 1, - inputBufferLength, - resultBufferLength, - seed); - } - [Theory] [MemberData(nameof(CommonConversionData))] public void FromRgbBasic(int inputBufferLength, int resultBufferLength, int seed) @@ -300,18 +313,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } #endif - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgb_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.RGB, - 3, - inputBufferLength, - resultBufferLength, - seed); - } - [Theory] [MemberData(nameof(CommonConversionData))] public void FromYccKBasic(int inputBufferLength, int resultBufferLength, int seed) @@ -368,18 +369,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } #endif - [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYcck_WithDefaultConverter(int inputBufferLength, int resultBufferLength, int seed) - { - ValidateConversion( - JpegColorSpace.Ycck, - 4, - inputBufferLength, - resultBufferLength, - seed); - } - private static JpegColorConverterBase.ComponentValues CreateRandomValues( int componentCount, int inputBufferLength, From 854b22e8f1d6ce61f9b23de5606ee24bd881dc55 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 2 Dec 2021 12:04:00 +0300 Subject: [PATCH 146/228] Fixed tests --- .../Formats/Jpg/JpegColorConverterTests.cs | 130 ++++++------------ 1 file changed, 44 insertions(+), 86 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 5c2f45307..b34c8e997 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -20,18 +20,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { private const float Precision = 0.1F / 255; - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new ApproximateColorSpaceComparer(Precision); + private const int TestBufferLength = 40; - // int inputBufferLength, int resultBufferLength, int seed - public static readonly TheoryData CommonConversionData = - new TheoryData - { - { 40, 40, 1 }, - { 42, 40, 2 }, - { 42, 39, 3 } - }; + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(Precision); + + public static readonly TheoryData Seeds = new() { 1, 2, 3 }; - private static readonly ColorSpaceConverter ColorSpaceConverter = new ColorSpaceConverter(); + private static readonly ColorSpaceConverter ColorSpaceConverter = new(); public JpegColorConverterTests(ITestOutputHelper output) { @@ -84,26 +79,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( colorSpace, componentCount, - 40, - 40, 1); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrBasic(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYCbCrBasic(int seed) { ValidateConversion( new JpegColorConverterBase.FromYCbCrScalar(8), 3, - inputBufferLength, - resultBufferLength, seed); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrVector(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYCbCrVector(int seed) { var converter = new JpegColorConverterBase.FromYCbCrVector(8); @@ -117,15 +108,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 3, - inputBufferLength, - resultBufferLength, seed); } #if SUPPORTS_RUNTIME_INTRINSICS [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYCbCrAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYCbCrAvx2(int seed) { var converter = new JpegColorConverterBase.FromYCbCrAvx(8); @@ -139,27 +128,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 3, - inputBufferLength, - resultBufferLength, seed); } #endif [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmykBasic(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromCmykBasic(int seed) { ValidateConversion( new JpegColorConverterBase.FromCmykScalar(8), 4, - inputBufferLength, - resultBufferLength, seed); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmykVector(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromCmykVector(int seed) { var converter = new JpegColorConverterBase.FromCmykVector(8); @@ -173,15 +158,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 4, - inputBufferLength, - resultBufferLength, seed); } #if SUPPORTS_RUNTIME_INTRINSICS [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromCmykAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromCmykAvx2(int seed) { var converter = new JpegColorConverterBase.FromCmykAvx(8); @@ -195,27 +178,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 4, - inputBufferLength, - resultBufferLength, seed); } #endif [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromGrayscaleBasic(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromGrayscaleBasic(int seed) { ValidateConversion( new JpegColorConverterBase.FromGrayscaleScalar(8), 1, - inputBufferLength, - resultBufferLength, seed); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromGrayscaleVector(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromGrayscaleVector(int seed) { var converter = new JpegColorConverterBase.FromGrayScaleVector(8); @@ -229,15 +208,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 1, - inputBufferLength, - resultBufferLength, seed); } #if SUPPORTS_RUNTIME_INTRINSICS [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromGrayscaleAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromGrayscaleAvx2(int seed) { var converter = new JpegColorConverterBase.FromGrayscaleAvx(8); @@ -251,27 +228,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 1, - inputBufferLength, - resultBufferLength, seed); } #endif [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgbBasic(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromRgbBasic(int seed) { ValidateConversion( new JpegColorConverterBase.FromRgbScalar(8), 3, - inputBufferLength, - resultBufferLength, seed); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgbVector(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromRgbVector(int seed) { var converter = new JpegColorConverterBase.FromRgbVector(8); @@ -285,15 +258,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 3, - inputBufferLength, - resultBufferLength, seed); } #if SUPPORTS_RUNTIME_INTRINSICS [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromRgbAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromRgbAvx2(int seed) { var converter = new JpegColorConverterBase.FromRgbAvx(8); @@ -307,27 +278,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 3, - inputBufferLength, - resultBufferLength, seed); } #endif [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYccKBasic(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYccKBasic(int seed) { ValidateConversion( new JpegColorConverterBase.FromYccKScalar(8), 4, - inputBufferLength, - resultBufferLength, seed); } [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYccKVector(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYccKVector(int seed) { var converter = new JpegColorConverterBase.FromYccKVector(8); @@ -341,15 +308,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 4, - inputBufferLength, - resultBufferLength, seed); } #if SUPPORTS_RUNTIME_INTRINSICS [Theory] - [MemberData(nameof(CommonConversionData))] - public void FromYccKAvx2(int inputBufferLength, int resultBufferLength, int seed) + [MemberData(nameof(Seeds))] + public void FromYccKAvx2(int seed) { var converter = new JpegColorConverterBase.FromYccKAvx(8); @@ -363,27 +328,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, 4, - inputBufferLength, - resultBufferLength, seed); } #endif private static JpegColorConverterBase.ComponentValues CreateRandomValues( + int length, int componentCount, - int inputBufferLength, - int seed, - float minVal = 0f, - float maxVal = 255f) + int seed) { + const float minVal = 0f; + const float maxVal = Precision; + var rnd = new Random(seed); var buffers = new Buffer2D[componentCount]; for (int i = 0; i < componentCount; i++) { - var values = new float[inputBufferLength]; + float[] values = new float[length]; - for (int j = 0; j < inputBufferLength; j++) + for (int j = 0; j < values.Length; j++) { values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; } @@ -400,31 +364,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateConversion( JpegColorSpace colorSpace, int componentCount, - int inputBufferLength, - int resultBufferLength, int seed) { ValidateConversion( JpegColorConverterBase.GetConverter(colorSpace, 8), componentCount, - inputBufferLength, - resultBufferLength, seed); } private static void ValidateConversion( JpegColorConverterBase converter, int componentCount, - int inputBufferLength, - int resultBufferLength, int seed) { - JpegColorConverterBase.ComponentValues original = CreateRandomValues(componentCount, inputBufferLength, seed); + JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); JpegColorConverterBase.ComponentValues values = Copy(original); converter.ConvertToRgbInplace(values); - for (int i = 0; i < resultBufferLength; i++) + for (int i = 0; i < TestBufferLength; i++) { Validate(converter.ColorSpace, original, values, i); } From 9ec9b5a9876a3860286b48e0426c314014c988e5 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 2 Dec 2021 12:26:28 +0300 Subject: [PATCH 147/228] Added avx/no-avx runs to every Vector API based test --- .../Formats/Jpg/JpegColorConverterTests.cs | 87 ++++++++++++------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index b34c8e997..067f59aeb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -9,7 +9,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Tests.Colorspaces.Conversion; - +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; using Xunit.Abstractions; @@ -64,6 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var converter = JpegColorConverterBase.GetConverter(colorSpace, precision); Assert.NotNull(converter); + Assert.True(converter.IsAvailable); Assert.Equal(colorSpace, converter.ColorSpace); Assert.Equal(precision, converter.Precision); } @@ -76,8 +77,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [InlineData(JpegColorSpace.YCbCr, 3)] internal void ConvertWithSelectedConverter(JpegColorSpace colorSpace, int componentCount) { + var converter = JpegColorConverterBase.GetConverter(colorSpace, 8); ValidateConversion( - colorSpace, + converter, componentCount, 1); } @@ -105,10 +107,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - ValidateConversion( - converter, - 3, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromYCbCrVector(8), + 3, + FeatureTestRunner.Deserialize(arg)); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -155,10 +163,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - ValidateConversion( - converter, - 4, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromCmykVector(8), + 4, + FeatureTestRunner.Deserialize(arg)); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -205,10 +219,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - ValidateConversion( - converter, - 1, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromGrayScaleVector(8), + 1, + FeatureTestRunner.Deserialize(arg)); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -255,10 +275,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - ValidateConversion( - converter, - 3, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromRgbVector(8), + 3, + FeatureTestRunner.Deserialize(arg)); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -305,10 +331,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return; } - ValidateConversion( - converter, - 4, - seed); + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + seed, + HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + + static void RunTest(string arg) => + ValidateConversion( + new JpegColorConverterBase.FromYccKVector(8), + 4, + FeatureTestRunner.Deserialize(arg)); } #if SUPPORTS_RUNTIME_INTRINSICS @@ -361,17 +393,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg return new JpegColorConverterBase.ComponentValues(buffers, 0); } - private static void ValidateConversion( - JpegColorSpace colorSpace, - int componentCount, - int seed) - { - ValidateConversion( - JpegColorConverterBase.GetConverter(colorSpace, 8), - componentCount, - seed); - } - private static void ValidateConversion( JpegColorConverterBase converter, int componentCount, From ffd1ea8a3532c23ca63ec307ae0b4ef9f01dfaa2 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 2 Dec 2021 12:32:04 +0300 Subject: [PATCH 148/228] Fixed comments --- .../Decoder/ColorConverters/JpegColorConverterVector.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index 42f6cab5c..c04591a28 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -40,8 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters int simdCount = length - remainder; this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount)); - // There's actually a lot of image/photo resolutions which won't have - // a remainder so it's better to check here than spend useless virtual call + // Jpeg images width is always divisible by 8 without a remainder + // so it's safe to say SSE/AVX implementations would never have + // 'remainder' pixels + // But some exotic simd implementations e.g. AVX-512 can have + // remainder pixels if (remainder > 0) { this.ConvertCoreInplace(values.Slice(simdCount, remainder)); From d4c9c6ec005e3c767d89a186e25dceb79328c56b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Dec 2021 12:13:17 +0100 Subject: [PATCH 149/228] Add SSE2 version of DoFilter6 --- .../Formats/Webp/Lossy/LossyUtils.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 37cea73ca..e2ae35b3d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -24,6 +24,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static readonly Vector128 Four = Vector128.Create((byte)4).AsSByte(); + private static readonly Vector128 Nine = Vector128.Create((short)0x0900).AsSByte(); + + private static readonly Vector128 SixtyThree = Vector128.Create((byte)63).AsSByte(); + private static readonly Vector128 SixtyFour = Vector128.Create((byte)64).AsSByte(); #endif @@ -1462,6 +1466,50 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy q1 = Sse2.Xor(q1.AsByte(), SignBit); } + // Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) + private static void DoFilter6Sse2(ref Vector128 p2, ref Vector128 p1, ref Vector128 p0, ref Vector128 q0, ref Vector128 q1, ref Vector128 q2, Vector128 mask, int tresh) + { + // Compute hev mask. + Vector128 notHev = GetNotHev(ref p1, ref p0, ref q0, ref q1, tresh); + + // Convert to signed values. + p1 = Sse2.Xor(p1, SignBit); + p0 = Sse2.Xor(p0, SignBit); + q0 = Sse2.Xor(q0, SignBit); + q1 = Sse2.Xor(q1, SignBit); + q0 = Sse2.Xor(p2, SignBit); + q1 = Sse2.Xor(q2, SignBit); + + Vector128 a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte()); + + // Do simple filter on pixels with hev. + Vector128 m = Sse2.AndNot(notHev, mask); + Vector128 f = Sse2.And(a.AsByte(), m); + DoSimpleFilterSse2(ref p0, ref q0, f); + + // Do strong filter on pixels with not hev. + m = Sse2.And(notHev, mask); + f = Sse2.And(a.AsByte(), m); + Vector128 flow = Sse2.UnpackLow(Vector128.Zero, f); + Vector128 fhigh = Sse2.UnpackHigh(Vector128.Zero, f); + + Vector128 f9High = Sse2.MultiplyHigh(flow.AsInt16(), Nine.AsInt16()); // Filter (lo) * 9 + Vector128 f9Low = Sse2.MultiplyLow(fhigh.AsInt16(), Nine.AsInt16()); // Filter (hi) * 9 + + Vector128 a2Low = Sse2.Add(f9Low, SixtyThree.AsInt16()); // Filter * 9 + 63 + Vector128 a2High = Sse2.Add(f9High, SixtyThree.AsInt16()); // Filter * 9 + 63 + + Vector128 a1Low = Sse2.Add(a2Low, f9Low); // Filter * 18 + 63 + Vector128 a1High = Sse2.Add(a2High, f9High); // // Filter * 18 + 63 + + Vector128 a0Low = Sse2.Add(a1Low, f9Low); // Filter * 27 + 63 + Vector128 a0High = Sse2.Add(a1High, f9High); // Filter * 27 + 63 + + Update2Pixels(ref p2, ref q2, a2Low, a2High); + Update2Pixels(ref p1, ref q1, a1Low, a1High); + Update2Pixels(ref p0, ref q0, a0Low, a0High); + } + private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) { Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), Three); @@ -1727,6 +1775,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy mask = Sse2.And(threshMask, filterMask); } + // Updates values of 2 pixels at MB edge during complex filtering. + // Update operations: + // q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] + // Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). + private static void Update2Pixels(ref Vector128 pi, ref Vector128 qi, Vector128 a0Low, Vector128 a0High) + { + Vector128 a1Low = Sse2.ShiftRightArithmetic(a0Low, 7); + Vector128 a1High = Sse2.ShiftRightArithmetic(a0High, 7); + Vector128 delta = Sse2.PackSignedSaturate(a1Low, a1High); + pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte(); + qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte(); + pi = Sse2.Xor(pi, SixtyFour.AsByte()); + qi = Sse2.Xor(qi, SignBit.AsByte()); + } + [MethodImpl(InliningOptions.ShortMethod)] private static Vector128 LoadUvEdge(ref byte uRef, ref byte vRef, int offset) { From 15716c1d38136a9a4e382a536e174f956dac39cb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 2 Dec 2021 22:53:11 +1100 Subject: [PATCH 150/228] Fix Color conversion for differing types --- src/ImageSharp/Color/Color.cs | 9 +++- .../Color/ColorTests.CastTo.cs | 51 ++++++++++++++++--- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs index 7c21d62dd..cd0583361 100644 --- a/src/ImageSharp/Color/Color.cs +++ b/src/ImageSharp/Color/Color.cs @@ -270,8 +270,15 @@ namespace SixLabors.ImageSharp return pixel; } + if (this.boxedHighPrecisionPixel is null) + { + pixel = default; + pixel.FromRgba64(this.data); + return pixel; + } + pixel = default; - pixel.FromRgba64(this.data); + pixel.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); return pixel; } diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index 3003265ca..bfc290c2e 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -90,17 +91,30 @@ namespace SixLabors.ImageSharp.Tests } [Fact] - public void GenericPixel() + public void Vector4Constructor() { - AssertGenericPixel(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue)); - AssertGenericPixel(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1)); - AssertGenericPixel(new Rgb48(1, 2, ushort.MaxValue - 1)); - AssertGenericPixel(new La32(1, ushort.MaxValue - 1)); - AssertGenericPixel(new L16(ushort.MaxValue - 1)); - AssertGenericPixel(new Rgba32(1, 2, 255, 254)); + // Act: + Color color = new(Vector4.One); + + // Assert: + Assert.Equal(new RgbaVector(1, 1, 1, 1), color.ToPixel()); + Assert.Equal(new Rgba64(65535, 65535, 65535, 65535), color.ToPixel()); + Assert.Equal(new Rgba32(255, 255, 255, 255), color.ToPixel()); + Assert.Equal(new L8(255), color.ToPixel()); + } + + [Fact] + public void GenericPixelRoundTrip() + { + AssertGenericPixelRoundTrip(new RgbaVector(float.Epsilon, 2 * float.Epsilon, float.MaxValue, float.MinValue)); + AssertGenericPixelRoundTrip(new Rgba64(1, 2, ushort.MaxValue, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new Rgb48(1, 2, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new La32(1, ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new L16(ushort.MaxValue - 1)); + AssertGenericPixelRoundTrip(new Rgba32(1, 2, 255, 254)); } - private static void AssertGenericPixel(TPixel source) + private static void AssertGenericPixelRoundTrip(TPixel source) where TPixel : unmanaged, IPixel { // Act: @@ -110,6 +124,27 @@ namespace SixLabors.ImageSharp.Tests TPixel actual = color.ToPixel(); Assert.Equal(source, actual); } + + [Fact] + public void GenericPixelDifferentPrecision() + { + AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba64(65535, 65535, 65535, 65535)); + AssertGenericPixelDifferentPrecision(new RgbaVector(1, 1, 1, 1), new Rgba32(255, 255, 255, 255)); + AssertGenericPixelDifferentPrecision(new Rgba64(65535, 65535, 65535, 65535), new Rgba32(255, 255, 255, 255)); + AssertGenericPixelDifferentPrecision(new Rgba32(255, 255, 255, 255), new L8(255)); + } + + private static void AssertGenericPixelDifferentPrecision(TPixel source, TPixel2 expected) + where TPixel : unmanaged, IPixel + where TPixel2 : unmanaged, IPixel + { + // Act: + var color = Color.FromPixel(source); + + // Assert: + TPixel2 actual = color.ToPixel(); + Assert.Equal(expected, actual); + } } } } From c8329106778926bfd6ceb3680125796dd2e27caa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Dec 2021 14:35:04 +0100 Subject: [PATCH 151/228] Add SSE2 version of VFilter16 --- .../Formats/Webp/Lossy/LossyUtils.cs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index e2ae35b3d..d966adaad 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1022,9 +1022,50 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } + // On macroblock edges. [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - => FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ref byte pRef = ref MemoryMarshal.GetReference(p); + Vector128 t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (4 * stride))); + Vector128 p2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (3 * stride))); + Vector128 p1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - (2 * stride))); + Vector128 p0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset - stride)); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t1, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Vector128 q0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); + Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (1 * stride))); + Vector128 q2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); + t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t1, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + // Store. + ref byte outputRef = ref MemoryMarshal.GetReference(p); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (3 * stride))) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (2 * stride))) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - stride)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + stride)) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + (2 * stride))) = p0.AsInt32(); + } + else +#endif + { + FilterLoop26(p, offset, stride, 1, 16, thresh, ithresh, hevThresh); + } + } [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) From a2765c0764271a390257018f631f1ab83b751fb2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Dec 2021 14:52:16 +0100 Subject: [PATCH 152/228] Add SSE2 version of HFilter16 --- .../Formats/Webp/Lossy/LossyUtils.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index d966adaad..a2bbc5cc5 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1040,7 +1040,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy mask = Sse2.Max(mask, Abs(p2, p1)); Vector128 q0 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset)); - Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (1 * stride))); + Vector128 q1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + stride)); Vector128 q2 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (2 * stride))); t1 = Unsafe.As>(ref Unsafe.Add(ref pRef, offset + (3 * stride))); @@ -1069,7 +1069,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter16(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) - => FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + Span b = p.Slice(offset - 4); + Load16x4(b, b.Slice(8 * stride), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Load16x4(p.Slice(offset), p.Slice(offset + 8), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(q3, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + Store16x4(p3, p2, p1, p0, b, b.Slice(8 * stride), stride); + Store16x4(q3, q2, q1, q0, p, p.Slice(8 * stride), stride); + } + else +#endif + { + FilterLoop26(p, offset, 1, stride, 16, thresh, ithresh, hevThresh); + } + } public static void VFilter16i(Span p, int offset, int stride, int thresh, int ithresh, int hevThresh) { From 49cf2f2834df12067e7d8fdc6244567dba2cd83d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Dec 2021 15:23:56 +0100 Subject: [PATCH 153/228] Add SSE2 version of VFilter8 --- .../Formats/Webp/Lossy/LossyUtils.cs | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index a2bbc5cc5..095f320c9 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1179,6 +1179,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy mask = Sse2.Max(mask, Abs(p2, p1)); Load16x4(p.Slice(offset), p.Slice(offset + (8 * stride)), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); + mask = Sse2.Max(mask, Abs(tmp1, tmp2)); mask = Sse2.Max(mask, Abs(p3, p2)); mask = Sse2.Max(mask, Abs(p2, tmp1)); @@ -1207,8 +1208,47 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void VFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + // Load uv h-edges. + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Vector128 t1 = LoadUvEdge(ref uRef, ref vRef, offset - (4 * stride)); + Vector128 p2 = LoadUvEdge(ref uRef, ref vRef, offset - (3 * stride)); + Vector128 p1 = LoadUvEdge(ref uRef, ref vRef, offset - (2 * stride)); + Vector128 p0 = LoadUvEdge(ref uRef, ref vRef, offset - stride); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(t1, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Vector128 q0 = LoadUvEdge(ref uRef, ref vRef, offset); + Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); + Vector128 q2 = LoadUvEdge(ref uRef, ref vRef, offset + (2 * stride)); + t1 = LoadUvEdge(ref uRef, ref vRef, offset + (3 * stride)); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(t1, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + // Store. + StoreUv(p2, ref uRef, ref vRef, offset - (3 * stride)); + StoreUv(p1, ref uRef, ref vRef, offset - (2 * stride)); + StoreUv(p0, ref uRef, ref vRef, offset - stride); + StoreUv(q0, ref uRef, ref vRef, offset); + StoreUv(q1, ref uRef, ref vRef, offset + (1 * stride)); + StoreUv(p2, ref uRef, ref vRef, offset + (2 * stride)); + } + else +#endif + { + FilterLoop26(u, offset, stride, 1, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, stride, 1, 8, thresh, ithresh, hevThresh); + } } [MethodImpl(InliningOptions.ShortMethod)] @@ -1224,7 +1264,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - // load uv h-edges. + // Load uv h-edges. ref byte uRef = ref MemoryMarshal.GetReference(u); ref byte vRef = ref MemoryMarshal.GetReference(v); Vector128 t2 = LoadUvEdge(ref uRef, ref vRef, offset); From bd7e9981bbc21e9ebec71021f6364b3f127f9a39 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Dec 2021 15:45:56 +0100 Subject: [PATCH 154/228] Add SSE2 version of HFilter8 --- .../Formats/Webp/Lossy/LossyUtils.cs | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 095f320c9..9f10be3b8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1186,6 +1186,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); + Store16x4(p1, p0, p3, p2, b, b.Slice(8 * stride), stride); // Rotate samples. @@ -1254,8 +1255,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy [MethodImpl(InliningOptions.ShortMethod)] public static void HFilter8(Span u, Span v, int offset, int stride, int thresh, int ithresh, int hevThresh) { - FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); - FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + Span tu = u.Slice(offset - 4); + Span tv = v.Slice(offset - 4); + Load16x4(tu, tv, stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + + Vector128 mask = Abs(p1, p0); + mask = Sse2.Max(mask, Abs(p3, p2)); + mask = Sse2.Max(mask, Abs(p2, p1)); + + Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + + mask = Sse2.Max(mask, Abs(q1, q0)); + mask = Sse2.Max(mask, Abs(q3, q2)); + mask = Sse2.Max(mask, Abs(q2, q1)); + + ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); + DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); + + Store16x4(p3, p2, p1, p0, tu, tv, stride); + Store16x4(q0, p1, q2, q3, u.Slice(offset), v.Slice(offset), stride); + } + else +#endif + { + FilterLoop26(u, offset, 1, stride, 8, thresh, ithresh, hevThresh); + FilterLoop26(v, offset, 1, stride, 8, thresh, ithresh, hevThresh); + } } [MethodImpl(InliningOptions.ShortMethod)] From 5b57e98563bc44c4ea44acb1b6daba243b0d4962 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 2 Dec 2021 19:47:50 +0100 Subject: [PATCH 155/228] Fix some issues with the SSE versions of the filters --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 9f10be3b8..71522d66f 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static readonly Vector128 Nine = Vector128.Create((short)0x0900).AsSByte(); - private static readonly Vector128 SixtyThree = Vector128.Create((byte)63).AsSByte(); + private static readonly Vector128 SixtyThree = Vector128.Create((short)63).AsSByte(); private static readonly Vector128 SixtyFour = Vector128.Create((byte)64).AsSByte(); #endif @@ -1080,7 +1080,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy mask = Sse2.Max(mask, Abs(p3, p2)); mask = Sse2.Max(mask, Abs(p2, p1)); - Load16x4(p.Slice(offset), p.Slice(offset + 8), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + Load16x4(p.Slice(offset), p.Slice(offset + (8 * stride)), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); mask = Sse2.Max(mask, Abs(q1, q0)); mask = Sse2.Max(mask, Abs(q3, q2)); @@ -1614,8 +1614,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy p0 = Sse2.Xor(p0, SignBit); q0 = Sse2.Xor(q0, SignBit); q1 = Sse2.Xor(q1, SignBit); - q0 = Sse2.Xor(p2, SignBit); - q1 = Sse2.Xor(q2, SignBit); + p2 = Sse2.Xor(p2, SignBit); + q2 = Sse2.Xor(q2, SignBit); Vector128 a = GetBaseDelta(p1.AsSByte(), p0.AsSByte(), q0.AsSByte(), q1.AsSByte()); @@ -1818,8 +1818,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 - Vector128 b0 = Sse2.UnpackLow(a0, a1); - Vector128 b1 = Sse2.UnpackHigh(a0, a1); + Vector128 b0 = Sse2.UnpackLow(a0.AsSByte(), a1.AsSByte()); + Vector128 b1 = Sse2.UnpackHigh(a0.AsSByte(), a1.AsSByte()); // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 @@ -1923,7 +1923,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 delta = Sse2.PackSignedSaturate(a1Low, a1High); pi = Sse2.AddSaturate(pi.AsSByte(), delta).AsByte(); qi = Sse2.SubtractSaturate(qi.AsSByte(), delta).AsByte(); - pi = Sse2.Xor(pi, SixtyFour.AsByte()); + pi = Sse2.Xor(pi, SignBit.AsByte()); qi = Sse2.Xor(qi, SignBit.AsByte()); } From a26a758011713da068a24251111486808ba18050 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 3 Dec 2021 07:24:24 +0300 Subject: [PATCH 156/228] Removed redundant allocations, fixed assertion & switch-case --- .../Formats/Jpg/JpegColorConverterTests.cs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 067f59aeb..3ebc137fa 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -399,7 +399,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int seed) { JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); - JpegColorConverterBase.ComponentValues values = Copy(original); + JpegColorConverterBase.ComponentValues values = new( + original.ComponentCount, + original.Component0, + original.Component1, + original.Component2, + original.Component3); converter.ConvertToRgbInplace(values); @@ -407,15 +412,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { Validate(converter.ColorSpace, original, values, i); } - - static JpegColorConverterBase.ComponentValues Copy(JpegColorConverterBase.ComponentValues values) - { - Span c0 = values.Component0.ToArray(); - Span c1 = values.ComponentCount > 1 ? values.Component1.ToArray().AsSpan() : c0; - Span c2 = values.ComponentCount > 2 ? values.Component2.ToArray().AsSpan() : c0; - Span c3 = values.ComponentCount > 3 ? values.Component3.ToArray().AsSpan() : Span.Empty; - return new JpegColorConverterBase.ComponentValues(values.ComponentCount, c0, c1, c2, c3); - } } private static void Validate( @@ -441,8 +437,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg case JpegColorSpace.YCbCr: ValidateYCbCr(original, result, i); break; + case JpegColorSpace.Undefined: default: - Assert.True(false, $"Colorspace {colorSpace} not supported!"); + Assert.True(false, $"Invalid Colorspace enum value: {colorSpace}."); break; } } From 5b62e94b26450fe5ad08813c61904f63a7681830 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 3 Dec 2021 08:11:09 +0300 Subject: [PATCH 157/228] Minor fixes --- .../Decoder/ColorConverters/JpegColorConverterVector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index c04591a28..523ac8896 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -26,14 +26,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { } - public sealed override bool IsAvailable => Vector.Count % 4 == 0; + public sealed override bool IsAvailable => Vector.IsHardwareAccelerated && Vector.Count % 4 == 0; public override void ConvertToRgbInplace(in ComponentValues values) { - DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware"); + DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); int length = values.Component0.Length; - int remainder = values.Component0.Length % Vector.Count; + int remainder = length % Vector.Count; // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide // Thus there's no need to check whether simdCount is greater than zero From 09b2e54c7184124c37bc5a70657002b7e97f7f07 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 3 Dec 2021 08:34:54 +0300 Subject: [PATCH 158/228] More robust test failure output --- .../Formats/Jpg/JpegColorConverterTests.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 3ebc137fa..086ae30f3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -454,7 +454,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); var expected = ColorSpaceConverter.ToRgb(ycbcr); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) @@ -479,7 +480,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); var expected = new Rgb(v.X, v.Y, v.Z); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) @@ -491,7 +493,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); var expected = new Rgb(r / 255F, g / 255F, b / 255F); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) @@ -500,7 +503,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); var expected = new Rgb(y / 255F, y / 255F, y / 255F); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) @@ -523,7 +527,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); var expected = new Rgb(v.X, v.Y, v.Z); - Assert.Equal(expected, actual, ColorSpaceComparer); + bool equal = ColorSpaceComparer.Equals(expected, actual); + Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); } } } From 26b3e664db2eb20d5ddb848dd0c01ea1b31c1c37 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 3 Dec 2021 11:34:45 +0100 Subject: [PATCH 159/228] Fix bug in black and white plain decoding --- src/ImageSharp/Formats/Pbm/PlainDecoder.cs | 4 ++-- src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 2 +- tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs index bf2b10e1d..9fa8e513e 100644 --- a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -174,8 +174,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - var white = new L8(0); - var black = new L8(255); + var white = new L8(255); + var black = new L8(0); for (int y = 0; y < height; y++) { diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index 2e7c60e5e..8d534b7a9 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -230,7 +230,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm int written = 0; for (int x = 0; x < width; x++) { - byte value = (rowSpan[x].PackedValue > 127) ? Zero : One; + byte value = (rowSpan[x].PackedValue < 128) ? One : Zero; plainSpan[written++] = value; plainSpan[written++] = Space; } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs index 1735efdce..cba75b2a0 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm public class PbmRoundTripTests { [Theory] + [InlineData(BlackAndWhitePlain)] [InlineData(BlackAndWhiteBinary)] [InlineData(GrayscalePlain)] [InlineData(GrayscaleBinary)] From c4cf012ac4f3eaf315ed23ff4161acf4a0a8d981 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 3 Dec 2021 13:45:08 +0100 Subject: [PATCH 160/228] Scale pixel value up to full allocation range --- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 30 +++++++++++++++++++ .../Formats/Pbm/PbmDecoderTests.cs | 3 ++ .../Formats/Pbm/PbmRoundTripTests.cs | 2 ++ ...ecodeReferenceImage_L8_grayscale_plain.png | 3 ++ .../DecodeReferenceImage_Rgb24_rgb_plain.png | 3 ++ 5 files changed, 41 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png create mode 100644 tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index bd99a578a..427ea15e8 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -7,6 +7,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing; namespace SixLabors.ImageSharp.Formats.Pbm { @@ -52,6 +53,23 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// Size IImageDecoderInternals.Dimensions => this.PixelSize; + private bool NeedsUpscaling + { + get + { + bool needsUpscaling = false; + if (this.ColorType != PbmColorType.BlackAndWhite) + { + if (this.MaxPixelValue is not 255 and not 65535) + { + needsUpscaling = true; + } + } + + return needsUpscaling; + } + } + /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel @@ -63,6 +81,10 @@ namespace SixLabors.ImageSharp.Formats.Pbm Buffer2D pixels = image.GetRootFramePixelBuffer(); this.ProcessPixels(stream, pixels); + if (this.NeedsUpscaling) + { + this.ProcessUpscaling(image); + } return image; } @@ -165,5 +187,13 @@ namespace SixLabors.ImageSharp.Formats.Pbm PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.MaxPixelValue); } } + + private void ProcessUpscaling(Image image) + where TPixel : unmanaged, IPixel + { + int maxAllocationValue = (this.MaxPixelValue > 255) ? 65535 : 255; + float factor = maxAllocationValue / this.MaxPixelValue; + image.Mutate(x => x.Brightness(factor)); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 479db2ca5..8b8e1a08f 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -74,10 +74,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm } [Theory] + [WithFile(BlackAndWhitePlain, PixelTypes.L8, true)] [WithFile(BlackAndWhiteBinary, PixelTypes.L8, true)] + [WithFile(GrayscalePlain, PixelTypes.L8, true)] [WithFile(GrayscalePlainNormalized, PixelTypes.L8, true)] [WithFile(GrayscaleBinary, PixelTypes.L8, true)] [WithFile(GrayscaleBinaryWide, PixelTypes.L16, true)] + [WithFile(RgbPlain, PixelTypes.Rgb24, false)] [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, false)] [WithFile(RgbBinary, PixelTypes.Rgb24, false)] public void DecodeReferenceImage(TestImageProvider provider, bool isGrayscale) diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs index cba75b2a0..9521ee7e9 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -17,6 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [InlineData(BlackAndWhitePlain)] [InlineData(BlackAndWhiteBinary)] [InlineData(GrayscalePlain)] + [InlineData(GrayscalePlainNormalized)] [InlineData(GrayscaleBinary)] public void PbmGrayscaleImageCanRoundTrip(string imagePath) { @@ -36,6 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [Theory] [InlineData(RgbPlain)] + [InlineData(RgbPlainNormalized)] [InlineData(RgbBinary)] public void PbmColorImageCanRoundTrip(string imagePath) { diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png new file mode 100644 index 000000000..9c86c2fc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_L8_grayscale_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f8e8b8a1a05e76b1eeb577373c3a6f492e356f0dd58489afded248415cec4a07 +size 145 diff --git a/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png new file mode 100644 index 000000000..421a59849 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/PbmDecoderTests/DecodeReferenceImage_Rgb24_rgb_plain.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c44322c4bf461acea27053057f5241afb029d9a1e66e94dcf1be6f86f7f97727 +size 152 From 09d056e42b2b56d65a2a2bc80e4adb94d1d80341 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 3 Dec 2021 15:42:18 +0300 Subject: [PATCH 161/228] Fixed test bug introduced in one of previous commits, code cleanup --- .../Formats/Jpg/JpegColorConverterTests.cs | 42 ++++++++----------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 086ae30f3..2c6b29382 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -18,11 +18,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public class JpegColorConverterTests { + private static readonly float MaxColorChannelValue = 255f; + private const float Precision = 0.1F / 255; private const int TestBufferLength = 40; - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(Precision); + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); public static readonly TheoryData Seeds = new() { 1, 2, 3 }; @@ -369,9 +371,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg int componentCount, int seed) { - const float minVal = 0f; - const float maxVal = Precision; - var rnd = new Random(seed); var buffers = new Buffer2D[componentCount]; @@ -381,7 +380,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg for (int j = 0; j < values.Length; j++) { - values[j] = ((float)rnd.NextDouble() * (maxVal - minVal)) + minVal; + values[j] = (float)rnd.NextDouble() * MaxColorChannelValue; } // no need to dispose when buffer is not array owner @@ -401,10 +400,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg JpegColorConverterBase.ComponentValues original = CreateRandomValues(TestBufferLength, componentCount, seed); JpegColorConverterBase.ComponentValues values = new( original.ComponentCount, - original.Component0, - original.Component1, - original.Component2, - original.Component3); + original.Component0.ToArray(), + original.Component1.ToArray(), + original.Component2.ToArray(), + original.Component3.ToArray()); converter.ConvertToRgbInplace(values); @@ -449,10 +448,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float y = values.Component0[i]; float cb = values.Component1[i]; float cr = values.Component2[i]; - var ycbcr = new YCbCr(y, cb, cr); - + var expected = ColorSpaceConverter.ToRgb(new YCbCr(y, cb, cr)); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - var expected = ColorSpaceConverter.ToRgb(ycbcr); bool equal = ColorSpaceComparer.Equals(expected, actual); Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); @@ -460,8 +457,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + Vector4 v = Vector4.Zero; float y = values.Component0[i]; float cb = values.Component1[i] - 128F; @@ -475,10 +471,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; v.W = 1F; - v *= scale; - - var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + v /= new Vector4(MaxColorChannelValue, MaxColorChannelValue, MaxColorChannelValue, 1f); var expected = new Rgb(v.X, v.Y, v.Z); + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); bool equal = ColorSpaceComparer.Equals(expected, actual); Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); @@ -489,9 +484,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float r = values.Component0[i]; float g = values.Component1[i]; float b = values.Component2[i]; - + var expected = new Rgb(new Vector3(r, g, b) / new Vector3(MaxColorChannelValue)); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); - var expected = new Rgb(r / 255F, g / 255F, b / 255F); bool equal = ColorSpaceComparer.Equals(expected, actual); Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); @@ -500,8 +494,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { float y = values.Component0[i]; + var expected = new Rgb(new Vector3(y) / new Vector3(MaxColorChannelValue)); var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); - var expected = new Rgb(y / 255F, y / 255F, y / 255F); bool equal = ColorSpaceComparer.Equals(expected, actual); Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); @@ -509,8 +503,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - var v = new Vector4(0, 0, 0, 1F); - var scale = new Vector4(1 / 255F, 1 / 255F, 1 / 255F, 1F); + Vector4 v = Vector4.Zero; float c = values.Component0[i]; float m = values.Component1[i]; @@ -522,10 +515,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg v.Z = y * k; v.W = 1F; - v *= scale; - - var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); + v /= new Vector4(MaxColorChannelValue, MaxColorChannelValue, MaxColorChannelValue, 1f); var expected = new Rgb(v.X, v.Y, v.Z); + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); bool equal = ColorSpaceComparer.Equals(expected, actual); Assert.True(equal, $"Colors {expected} and {actual} are not equal at index {i}"); From f3b35e8acba740cec6687da4ba806507337d63e1 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 3 Dec 2021 16:42:21 +0300 Subject: [PATCH 162/228] Yet another 'fix' --- .../Formats/Jpg/JpegColorConverterTests.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 2c6b29382..22af74525 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -449,6 +449,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float cb = values.Component1[i]; float cr = values.Component2[i]; var expected = ColorSpaceConverter.ToRgb(new YCbCr(y, cb, cr)); + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); bool equal = ColorSpaceComparer.Equals(expected, actual); @@ -469,10 +470,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - v.W = 1F; - v /= new Vector4(MaxColorChannelValue, MaxColorChannelValue, MaxColorChannelValue, 1f); - var expected = new Rgb(v.X, v.Y, v.Z); + float r = v.X / MaxColorChannelValue; + float g = v.Y / MaxColorChannelValue; + float b = v.Z / MaxColorChannelValue; + var expected = new Rgb(r, g, b); + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); bool equal = ColorSpaceComparer.Equals(expected, actual); @@ -481,10 +484,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateRgb(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - float r = values.Component0[i]; - float g = values.Component1[i]; - float b = values.Component2[i]; - var expected = new Rgb(new Vector3(r, g, b) / new Vector3(MaxColorChannelValue)); + float r = values.Component0[i] / MaxColorChannelValue; + float g = values.Component1[i] / MaxColorChannelValue; + float b = values.Component2[i] / MaxColorChannelValue; + var expected = new Rgb(r, g, b); + var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); bool equal = ColorSpaceComparer.Equals(expected, actual); @@ -493,8 +497,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateGrayScale(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - float y = values.Component0[i]; - var expected = new Rgb(new Vector3(y) / new Vector3(MaxColorChannelValue)); + float y = values.Component0[i] / MaxColorChannelValue; + var expected = new Rgb(y, y, y); + var actual = new Rgb(result.Component0[i], result.Component0[i], result.Component0[i]); bool equal = ColorSpaceComparer.Equals(expected, actual); @@ -510,13 +515,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg float y = values.Component2[i]; float k = values.Component3[i] / 255F; - v.X = c * k; - v.Y = m * k; - v.Z = y * k; - v.W = 1F; + float r = c * k / MaxColorChannelValue; + float g = m * k / MaxColorChannelValue; + float b = y * k / MaxColorChannelValue; + var expected = new Rgb(r, g, b); - v /= new Vector4(MaxColorChannelValue, MaxColorChannelValue, MaxColorChannelValue, 1f); - var expected = new Rgb(v.X, v.Y, v.Z); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); bool equal = ColorSpaceComparer.Equals(expected, actual); From 86bf6c31d32de111a775f714829021d89d1cb236 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 3 Dec 2021 11:27:22 +0100 Subject: [PATCH 163/228] Fix issues with SSE filters --- .../Formats/Webp/Lossy/LossyUtils.cs | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 368b3614e..6ecff598f 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -22,7 +22,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static readonly Vector128 Three = Vector128.Create((byte)3).AsSByte(); - private static readonly Vector128 Four = Vector128.Create((byte)4).AsSByte(); + private static readonly Vector128 FourShort = Vector128.Create((short)4); + + private static readonly Vector128 FourSByte = Vector128.Create((byte)4).AsSByte(); private static readonly Vector128 Nine = Vector128.Create((short)0x0900).AsSByte(); @@ -918,7 +920,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Horizontal pass and subsequent transpose. // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 dc = Sse2.Add(t0.AsInt16(), Four.AsInt16()); + Vector128 dc = Sse2.Add(t0.AsInt16(), FourShort); a = Sse2.Add(dc, t2.AsInt16()); b = Sse2.Subtract(dc, t2.AsInt16()); @@ -1039,7 +1041,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Horizontal pass and subsequent transpose. // First pass, c and d calculations are longer because of the "trick" multiplications. - Vector128 dc = Sse2.Add(t0.AsInt16(), Four.AsInt16()); + Vector128 dc = Sse2.Add(t0.AsInt16(), FourShort); a = Sse2.Add(dc, t2.AsInt16()); b = Sse2.Subtract(dc, t2.AsInt16()); @@ -1326,12 +1328,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Store. ref byte outputRef = ref MemoryMarshal.GetReference(p); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (3 * stride))) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (2 * stride))) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (3 * stride))) = p2.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - (2 * stride))) = p1.AsInt32(); Unsafe.As>(ref Unsafe.Add(ref outputRef, offset - stride)) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset)) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + stride)) = p0.AsInt32(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + (2 * stride))) = p0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset)) = q0.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + stride)) = q1.AsInt32(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, offset + (2 * stride))) = q2.AsInt32(); } else #endif @@ -1363,7 +1365,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); Store16x4(p3, p2, p1, p0, b, b.Slice(8 * stride), stride); - Store16x4(q3, q2, q1, q0, p, p.Slice(8 * stride), stride); + Store16x4(q0, q1, q2, q3, p.Slice(offset), p.Slice(offset + (8 * stride)), stride); } else #endif @@ -1515,7 +1517,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy StoreUv(p0, ref uRef, ref vRef, offset - stride); StoreUv(q0, ref uRef, ref vRef, offset); StoreUv(q1, ref uRef, ref vRef, offset + (1 * stride)); - StoreUv(p2, ref uRef, ref vRef, offset + (2 * stride)); + StoreUv(q2, ref uRef, ref vRef, offset + (2 * stride)); } else #endif @@ -1549,7 +1551,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); Store16x4(p3, p2, p1, p0, tu, tv, stride); - Store16x4(q0, p1, q2, q3, u.Slice(offset), v.Slice(offset), stride); + Store16x4(q0, q1, q2, q3, u.Slice(offset), v.Slice(offset), stride); } else #endif @@ -1583,6 +1585,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 q1 = LoadUvEdge(ref uRef, ref vRef, offset + stride); t1 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 2)); t2 = LoadUvEdge(ref uRef, ref vRef, offset + (stride * 3)); + mask = Sse2.Max(mask, Abs(q1, q0)); mask = Sse2.Max(mask, Abs(t2, t1)); mask = Sse2.Max(mask, Abs(t1, q1)); @@ -1593,7 +1596,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Store. StoreUv(p1, ref uRef, ref vRef, offset + (-2 * stride)); StoreUv(p0, ref uRef, ref vRef, offset + (-1 * stride)); - StoreUv(q1, ref uRef, ref vRef, offset); + StoreUv(q0, ref uRef, ref vRef, offset); StoreUv(q1, ref uRef, ref vRef, offset + stride); } else @@ -1611,7 +1614,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); + Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); Vector128 mask = Abs(p1, p0); mask = Sse2.Max(mask, Abs(t2, t1)); @@ -1621,6 +1624,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy offset += 4; Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); + mask = Sse2.Max(mask, Abs(q1, q0)); mask = Sse2.Max(mask, Abs(t2, t1)); mask = Sse2.Max(mask, Abs(t1, q1)); @@ -1856,7 +1860,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy t1 = Sse2.And(t1.AsByte(), mask).AsSByte(); // mask filter values we don't care about. t2 = Sse2.AddSaturate(t1, Three); // 3 * (q0 - p0) + hev(p1 - q1) + 3 - Vector128 t3 = Sse2.AddSaturate(t1, Four); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + Vector128 t3 = Sse2.AddSaturate(t1, FourSByte); // 3 * (q0 - p0) + hev(p1 - q1) + 4 t2 = SignedShift8b(t2.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 t3 = SignedShift8b(t3.AsByte()); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 p0 = Sse2.AddSaturate(p0.AsSByte(), t2).AsByte(); // p0 += t2 @@ -1903,8 +1907,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 flow = Sse2.UnpackLow(Vector128.Zero, f); Vector128 fhigh = Sse2.UnpackHigh(Vector128.Zero, f); - Vector128 f9High = Sse2.MultiplyHigh(flow.AsInt16(), Nine.AsInt16()); // Filter (lo) * 9 - Vector128 f9Low = Sse2.MultiplyLow(fhigh.AsInt16(), Nine.AsInt16()); // Filter (hi) * 9 + Vector128 f9Low = Sse2.MultiplyHigh(flow.AsInt16(), Nine.AsInt16()); // Filter (lo) * 9 + Vector128 f9High = Sse2.MultiplyHigh(fhigh.AsInt16(), Nine.AsInt16()); // Filter (hi) * 9 Vector128 a2Low = Sse2.Add(f9Low, SixtyThree.AsInt16()); // Filter * 9 + 63 Vector128 a2High = Sse2.Add(f9High, SixtyThree.AsInt16()); // Filter * 9 + 63 @@ -1923,7 +1927,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static void DoSimpleFilterSse2(ref Vector128 p0, ref Vector128 q0, Vector128 fl) { Vector128 v3 = Sse2.AddSaturate(fl.AsSByte(), Three); - Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), Four); + Vector128 v4 = Sse2.AddSaturate(fl.AsSByte(), FourSByte); v4 = SignedShift8b(v4.AsByte()).AsSByte(); // v4 >> 3 v3 = SignedShift8b(v3.AsByte()).AsSByte(); // v3 >> 3 From fabf00ab8f5a338f7ddba557074b1778aa5dea92 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 3 Dec 2021 15:44:47 +0100 Subject: [PATCH 164/228] Add SSE2 version of SimpleVFilter16i --- .../Formats/Webp/Lossy/LossyUtils.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 6ecff598f..62e5a2807 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1281,10 +1281,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void SimpleVFilter16i(Span p, int offset, int stride, int thresh) { - for (int k = 3; k > 0; k--) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4 * stride; - SimpleVFilter16(p, offset, stride, thresh); + for (int k = 3; k > 0; k--) + { + offset += 4 * stride; + SimpleVFilter16(p, offset, stride, thresh); + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4 * stride; + SimpleVFilter16(p, offset, stride, thresh); + } } } From c8d182bad62622039f706c1ca788d96ada5f091a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 3 Dec 2021 17:14:49 +0100 Subject: [PATCH 165/228] Add SSE2 version of SimpleHFilter16i --- .../Formats/Webp/Lossy/LossyUtils.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 62e5a2807..5fa75d815 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1303,10 +1303,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static void SimpleHFilter16i(Span p, int offset, int stride, int thresh) { - for (int k = 3; k > 0; k--) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - offset += 4; - SimpleHFilter16(p, offset, stride, thresh); + for (int k = 3; k > 0; k--) + { + offset += 4; + SimpleHFilter16(p, offset, stride, thresh); + } + } + else +#endif + { + for (int k = 3; k > 0; k--) + { + offset += 4; + SimpleHFilter16(p, offset, stride, thresh); + } } } From 67c3de2240cbae15ffe3f315db935de48646bf77 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 3 Dec 2021 17:53:15 +0100 Subject: [PATCH 166/228] Add tests for decoding lossy without hardware intrinsics --- .../Formats/WebP/WebpDecoderTests.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 34fa72c63..2cbeff2ce 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Webp; @@ -19,6 +20,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp private static MagickReferenceDecoder ReferenceDecoder => new(); + private static string TestImageLossySimpleFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.SimpleFilter02); + + private static string TestImageLossyComplexFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.BikeComplexFilter); + [Theory] [InlineData(Lossless.GreenTransform1, 1000, 307, 32)] [InlineData(Lossless.BikeThreeTransforms, 250, 195, 32)] @@ -359,5 +364,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp { } }); + +#if SUPPORTS_RUNTIME_INTRINSICS + private static void RunDecodeLossyWithSimpleFilterTest() + { + var provider = TestImageProvider.File(TestImageLossySimpleFilterPath); + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + private static void RunDecodeLossyWithComplexFilterTest() + { + var provider = TestImageProvider.File(TestImageLossyComplexFilterPath); + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Fact] + public void DecodeLossyWithSimpleFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithSimpleFilterTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void DecodeLossyWithComplexFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithComplexFilterTest, HwIntrinsics.DisableHWIntrinsic); +#endif } } From 15c28e1495d9a493a70ca50d9451491e95553af7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 3 Dec 2021 20:22:31 +0100 Subject: [PATCH 167/228] Simplify Memset: Use Span.Fill --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 5fa75d815..966fb561f 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -2295,14 +2295,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } [MethodImpl(InliningOptions.ShortMethod)] - private static void Memset(Span dst, byte value, int startIdx, int count) - { - int end = startIdx + count; - for (int i = startIdx; i < end; i++) - { - dst[i] = value; - } - } + private static void Memset(Span dst, byte value, int startIdx, int count) => dst.Slice(startIdx, count).Fill(value); [MethodImpl(InliningOptions.ShortMethod)] private static int Clamp255(int x) => x < 0 ? 0 : x > 255 ? 255 : x; From ca9ad91c020009a70ec4659c363a22f254f72b79 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 4 Dec 2021 16:04:16 +0300 Subject: [PATCH 168/228] Removed duplicated code, removed unused variables --- .../Formats/Jpg/JpegColorConverterTests.cs | 149 ++++-------------- 1 file changed, 34 insertions(+), 115 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index 22af74525..f1e3684de 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -88,13 +88,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Theory] [MemberData(nameof(Seeds))] - public void FromYCbCrBasic(int seed) - { - ValidateConversion( - new JpegColorConverterBase.FromYCbCrScalar(8), - 3, - seed); - } + public void FromYCbCrBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYCbCrScalar(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] @@ -124,33 +119,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg #if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(Seeds))] - public void FromYCbCrAvx2(int seed) - { - var converter = new JpegColorConverterBase.FromYCbCrAvx(8); - - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } - - ValidateConversion( - converter, - 3, - seed); - } + public void FromYCbCrAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed); #endif [Theory] [MemberData(nameof(Seeds))] - public void FromCmykBasic(int seed) - { - ValidateConversion( - new JpegColorConverterBase.FromCmykScalar(8), - 4, - seed); - } + public void FromCmykBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromCmykScalar(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] @@ -180,33 +156,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg #if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(Seeds))] - public void FromCmykAvx2(int seed) - { - var converter = new JpegColorConverterBase.FromCmykAvx(8); - - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } - - ValidateConversion( - converter, - 4, - seed); - } + public void FromCmykAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed); #endif [Theory] [MemberData(nameof(Seeds))] - public void FromGrayscaleBasic(int seed) - { - ValidateConversion( - new JpegColorConverterBase.FromGrayscaleScalar(8), - 1, - seed); - } + public void FromGrayscaleBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromGrayscaleScalar(8), 1, seed); [Theory] [MemberData(nameof(Seeds))] @@ -236,33 +193,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg #if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(Seeds))] - public void FromGrayscaleAvx2(int seed) - { - var converter = new JpegColorConverterBase.FromGrayscaleAvx(8); - - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } - - ValidateConversion( - converter, - 1, - seed); - } + public void FromGrayscaleAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed); #endif [Theory] [MemberData(nameof(Seeds))] - public void FromRgbBasic(int seed) - { - ValidateConversion( - new JpegColorConverterBase.FromRgbScalar(8), - 3, - seed); - } + public void FromRgbBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromRgbScalar(8), 3, seed); [Theory] [MemberData(nameof(Seeds))] @@ -292,33 +230,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg #if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(Seeds))] - public void FromRgbAvx2(int seed) - { - var converter = new JpegColorConverterBase.FromRgbAvx(8); - - if (!converter.IsAvailable) - { - this.Output.WriteLine( - $"Skipping test - {converter.GetType().Name} is not supported on current hardware."); - return; - } - - ValidateConversion( - converter, - 3, - seed); - } + public void FromRgbAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed); #endif [Theory] [MemberData(nameof(Seeds))] - public void FromYccKBasic(int seed) - { - ValidateConversion( - new JpegColorConverterBase.FromYccKScalar(8), - 4, - seed); - } + public void FromYccKBasic(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYccKScalar(8), 4, seed); [Theory] [MemberData(nameof(Seeds))] @@ -348,10 +267,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg #if SUPPORTS_RUNTIME_INTRINSICS [Theory] [MemberData(nameof(Seeds))] - public void FromYccKAvx2(int seed) - { - var converter = new JpegColorConverterBase.FromYccKAvx(8); + public void FromYccKAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYccKAvx(8), 4, seed); +#endif + private void TestConverter( + JpegColorConverterBase converter, + int componentCount, + int seed) + { if (!converter.IsAvailable) { this.Output.WriteLine( @@ -361,10 +285,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ValidateConversion( converter, - 4, + componentCount, seed); } -#endif private static JpegColorConverterBase.ComponentValues CreateRandomValues( int length, @@ -458,22 +381,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateCyyK(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - Vector4 v = Vector4.Zero; - float y = values.Component0[i]; float cb = values.Component1[i] - 128F; float cr = values.Component2[i] - 128F; float k = values.Component3[i] / 255F; - v.X = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; - v.Y = (255F - (float)Math.Round( + float r = (255F - (float)Math.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; + float g = (255F - (float)Math.Round( y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; - v.Z = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; + float b = (255F - (float)Math.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; - float r = v.X / MaxColorChannelValue; - float g = v.Y / MaxColorChannelValue; - float b = v.Z / MaxColorChannelValue; + r /= MaxColorChannelValue; + g /= MaxColorChannelValue; + b /= MaxColorChannelValue; var expected = new Rgb(r, g, b); var actual = new Rgb(result.Component0[i], result.Component1[i], result.Component2[i]); @@ -508,12 +429,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private static void ValidateCmyk(in JpegColorConverterBase.ComponentValues values, in JpegColorConverterBase.ComponentValues result, int i) { - Vector4 v = Vector4.Zero; - float c = values.Component0[i]; float m = values.Component1[i]; float y = values.Component2[i]; - float k = values.Component3[i] / 255F; + float k = values.Component3[i] / MaxColorChannelValue; float r = c * k / MaxColorChannelValue; float g = m * k / MaxColorChannelValue; From ea2f5894eeae7afaf10a4c41ff734ac8dde7d587 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 4 Dec 2021 16:13:32 +0300 Subject: [PATCH 169/228] Renamings --- .../ColorConverters/JpegColorConverter.FromCmykAvx.cs | 2 +- .../ColorConverters/JpegColorConverter.FromCmykScalar.cs | 2 +- .../ColorConverters/JpegColorConverter.FromCmykVector.cs | 2 +- .../ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs | 2 +- .../JpegColorConverter.FromGrayScaleScalar.cs | 2 +- .../JpegColorConverter.FromGrayScaleVector.cs | 2 +- .../ColorConverters/JpegColorConverter.FromRgbAvx.cs | 2 +- .../ColorConverters/JpegColorConverter.FromRgbScalar.cs | 2 +- .../ColorConverters/JpegColorConverter.FromRgbVector.cs | 2 +- .../ColorConverters/JpegColorConverter.FromYCbCrAvx.cs | 2 +- .../ColorConverters/JpegColorConverter.FromYCbCrScalar.cs | 2 +- .../ColorConverters/JpegColorConverter.FromYCbCrVector.cs | 2 +- .../ColorConverters/JpegColorConverter.FromYccKAvx.cs | 2 +- .../ColorConverters/JpegColorConverter.FromYccKScalar.cs | 2 +- .../ColorConverters/JpegColorConverter.FromYccKVector.cs | 2 +- .../Decoder/ColorConverters/JpegColorConverterAvx.cs | 4 ++-- .../Decoder/ColorConverters/JpegColorConverterScalar.cs | 4 ++-- .../Decoder/ColorConverters/JpegColorConverterVector.cs | 6 +++--- 18 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs index d627f7eaf..7366ee30a 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykAvx : AvxColorConverter + internal sealed class FromCmykAvx : JpegColorConverterAvx { public FromCmykAvx(int precision) : base(JpegColorSpace.Cmyk, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs index e70aa7cb4..68dfa9bfb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykScalar.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykScalar : ScalarJpegColorConverter + internal sealed class FromCmykScalar : JpegColorConverterScalar { public FromCmykScalar(int precision) : base(JpegColorSpace.Cmyk, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs index 8fd918140..6b7ed169e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromCmykVector : VectorizedJpegColorConverter + internal sealed class FromCmykVector : JpegColorConverterVector { public FromCmykVector(int precision) : base(JpegColorSpace.Cmyk, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs index fcfcaa2a9..963543ad4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayscaleAvx : AvxColorConverter + internal sealed class FromGrayscaleAvx : JpegColorConverterAvx { public FromGrayscaleAvx(int precision) : base(JpegColorSpace.Grayscale, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs index 18ac5ff99..3f6a6caa4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleScalar.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayscaleScalar : ScalarJpegColorConverter + internal sealed class FromGrayscaleScalar : JpegColorConverterScalar { public FromGrayscaleScalar(int precision) : base(JpegColorSpace.Grayscale, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs index 1ca329a12..c484aac28 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleVector.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromGrayScaleVector : VectorizedJpegColorConverter + internal sealed class FromGrayScaleVector : JpegColorConverterVector { public FromGrayScaleVector(int precision) : base(JpegColorSpace.Grayscale, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs index 83fbc369b..f017716e3 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx.cs @@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbAvx : AvxColorConverter + internal sealed class FromRgbAvx : JpegColorConverterAvx { public FromRgbAvx(int precision) : base(JpegColorSpace.RGB, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs index 83861d1e2..24c59206d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbScalar.cs @@ -5,7 +5,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbScalar : ScalarJpegColorConverter + internal sealed class FromRgbScalar : JpegColorConverterScalar { public FromRgbScalar(int precision) : base(JpegColorSpace.RGB, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs index b2b059cdc..ff3a2bee1 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromRgbVector : VectorizedJpegColorConverter + internal sealed class FromRgbVector : JpegColorConverterVector { public FromRgbVector(int precision) : base(JpegColorSpace.RGB, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs index adf6e8e0f..892bcc79e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrAvx : AvxColorConverter + internal sealed class FromYCbCrAvx : JpegColorConverterAvx { public FromYCbCrAvx(int precision) : base(JpegColorSpace.YCbCr, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs index 73c73970d..4b6d88f72 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrScalar.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrScalar : ScalarJpegColorConverter + internal sealed class FromYCbCrScalar : JpegColorConverterScalar { // TODO: comments, derived from ITU-T Rec. T.871 internal const float RCrMult = 1.402f; diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs index f34014332..48e311d99 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector.cs @@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYCbCrVector : VectorizedJpegColorConverter + internal sealed class FromYCbCrVector : JpegColorConverterVector { public FromYCbCrVector(int precision) : base(JpegColorSpace.YCbCr, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs index 86528c74d..1f18d5324 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx.cs @@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKAvx : AvxColorConverter + internal sealed class FromYccKAvx : JpegColorConverterAvx { public FromYccKAvx(int precision) : base(JpegColorSpace.Ycck, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs index 4657e50cf..d6387ae71 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKScalar.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKScalar : ScalarJpegColorConverter + internal sealed class FromYccKScalar : JpegColorConverterScalar { public FromYccKScalar(int precision) : base(JpegColorSpace.Ycck, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs index 1e826c2c0..66c79ae7c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { internal abstract partial class JpegColorConverterBase { - internal sealed class FromYccKVector : VectorizedJpegColorConverter + internal sealed class FromYccKVector : JpegColorConverterVector { public FromYccKVector(int precision) : base(JpegColorSpace.Ycck, precision) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs index 559422273..81c7c0764 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterAvx.cs @@ -18,9 +18,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// DO NOT pass test data of invalid size to these converters as they /// potentially won't do a bound check and return a false positive result. /// - internal abstract class AvxColorConverter : JpegColorConverterBase + internal abstract class JpegColorConverterAvx : JpegColorConverterBase { - protected AvxColorConverter(JpegColorSpace colorSpace, int precision) + protected JpegColorConverterAvx(JpegColorSpace colorSpace, int precision) : base(colorSpace, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs index 76134d490..ff88ab969 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterScalar.cs @@ -9,9 +9,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// abstract base for implementations /// based on scalar instructions. /// - internal abstract class ScalarJpegColorConverter : JpegColorConverterBase + internal abstract class JpegColorConverterScalar : JpegColorConverterBase { - protected ScalarJpegColorConverter(JpegColorSpace colorSpace, int precision) + protected JpegColorConverterScalar(JpegColorSpace colorSpace, int precision) : base(colorSpace, precision) { } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs index 523ac8896..ca482d78d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterVector.cs @@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters /// such data out of the box. These converters have fallback code /// for 'remainder' data. /// - internal abstract class VectorizedJpegColorConverter : JpegColorConverterBase + internal abstract class JpegColorConverterVector : JpegColorConverterBase { - protected VectorizedJpegColorConverter(JpegColorSpace colorSpace, int precision) + protected JpegColorConverterVector(JpegColorSpace colorSpace, int precision) : base(colorSpace, precision) { } @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters DebugGuard.IsTrue(this.IsAvailable, $"{this.GetType().Name} converter is not supported on current hardware."); int length = values.Component0.Length; - int remainder = length % Vector.Count; + int remainder = (int)((uint)length % (uint)Vector.Count); // Jpeg images are guaranteed to have pixel strides at least 8 pixels wide // Thus there's no need to check whether simdCount is greater than zero From 72c6f529098b8069d03a4b2db9f1c34a25bca79c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 4 Dec 2021 18:23:58 +0100 Subject: [PATCH 170/228] Allocate clean buffer in AnalyzeEntropy --- src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index adabd0ac3..7769a0a6c 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public bool UseCrossColorTransform { get; set; } /// - /// Gets or sets a value indicating whether to use the substract green transform. + /// Gets or sets a value indicating whether to use the subtract green transform. /// public bool UseSubtractGreenTransform { get; set; } @@ -1049,7 +1049,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return EntropyIx.Palette; } - using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256); + using IMemoryOwner histoBuffer = this.memoryAllocator.Allocate((int)HistoIx.HistoTotal * 256, AllocationOptions.Clean); Span histo = histoBuffer.Memory.Span; uint pixPrev = bgra[0]; // Skip the first pixel. ReadOnlySpan prevRow = null; From f10da948307ebc78b852023007322f504a60bf06 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 4 Dec 2021 19:36:31 +0100 Subject: [PATCH 171/228] MemoryAllocatorSettings -> MemoryAllocatorOptions --- src/ImageSharp/Configuration.cs | 2 +- src/ImageSharp/Memory/Allocators/MemoryAllocator.cs | 6 +++--- ...MemoryAllocatorSettings.cs => MemoryAllocatorOptions.cs} | 2 +- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/ImageSharp/Memory/Allocators/{MemoryAllocatorSettings.cs => MemoryAllocatorOptions.cs} (95%) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 31c67dd68..94584ff20 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -134,7 +134,7 @@ namespace SixLabors.ImageSharp /// /// /// It's possible to reduce allocator footprint by assigning a custom instance created with - /// , but note that since the default pooling + /// , but note that since the default pooling /// allocators are expensive, it is strictly recommended to use a single process-wide allocator. /// You can ensure this by altering the allocator of , or by implementing custom application logic that /// manages allocator lifetime. diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs index 6649468da..4df78d9d9 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocator.cs @@ -37,10 +37,10 @@ namespace SixLabors.ImageSharp.Memory /// /// Creates the default using the provided options. /// - /// The . + /// The . /// The . - public static MemoryAllocator Create(MemoryAllocatorSettings settings) => - new UniformUnmanagedMemoryPoolMemoryAllocator(settings.MaximumPoolSizeMegabytes); + public static MemoryAllocator Create(MemoryAllocatorOptions options) => + new UniformUnmanagedMemoryPoolMemoryAllocator(options.MaximumPoolSizeMegabytes); /// /// Allocates an , holding a of length . diff --git a/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs similarity index 95% rename from src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs rename to src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs index 01ab46c2a..22a041075 100644 --- a/src/ImageSharp/Memory/Allocators/MemoryAllocatorSettings.cs +++ b/src/ImageSharp/Memory/Allocators/MemoryAllocatorOptions.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Memory /// /// Defines options for creating the default . /// - public struct MemoryAllocatorSettings + public struct MemoryAllocatorOptions { private int? maximumPoolSizeMegabytes; diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index e6c50ae34..26a90591e 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators static void RunTest() { - var allocator = MemoryAllocator.Create(new MemoryAllocatorSettings() + var allocator = MemoryAllocator.Create(new MemoryAllocatorOptions() { MaximumPoolSizeMegabytes = 8 }); From 751fc061a5196c68ff2424281f7a9f3cb73d0cbd Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 4 Dec 2021 19:37:28 +0100 Subject: [PATCH 172/228] update shared-infrastructure --- shared-infrastructure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-infrastructure b/shared-infrastructure index a042aba17..59ce17f5a 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 +Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 From d35b239c5e0939602043f6a30a7a8bd6eb49caee Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 4 Dec 2021 19:45:13 +0100 Subject: [PATCH 173/228] automatic consequence of shared-infrastructure update? --- .gitattributes | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 70ced6903..355b64dce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -87,7 +87,6 @@ *.eot binary *.exe binary *.otf binary -*.pbm binary *.pdf binary *.ppt binary *.pptx binary @@ -95,7 +94,6 @@ *.snk binary *.ttc binary *.ttf binary -*.wbmp binary *.woff binary *.woff2 binary *.xls binary @@ -126,3 +124,9 @@ *.dds filter=lfs diff=lfs merge=lfs -text *.ktx filter=lfs diff=lfs merge=lfs -text *.ktx2 filter=lfs diff=lfs merge=lfs -text +*.pam filter=lfs diff=lfs merge=lfs -text +*.pbm filter=lfs diff=lfs merge=lfs -text +*.pgm filter=lfs diff=lfs merge=lfs -text +*.ppm filter=lfs diff=lfs merge=lfs -text +*.pnm filter=lfs diff=lfs merge=lfs -text +*.wbmp filter=lfs diff=lfs merge=lfs -text From 3780ca78e0f6567e94ccecd8ab2856fca362ec21 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 4 Dec 2021 19:54:49 +0100 Subject: [PATCH 174/228] Add lossless encode test which checks the expected bytes count of the encoded file --- .../Formats/WebP/WebpEncoderTests.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 70cc487bf..ec0040a46 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -101,6 +101,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", testOutputDetails, encoder); } + [Theory] + [WithFile(Lossy.NoFilter06, PixelTypes.Rgba32, 15114)] + public void Encode_Lossless_WithBestQuality_HasExpectedSize(TestImageProvider provider, int expectedBytes) + where TPixel : unmanaged, IPixel + { + var encoder = new WebpEncoder() + { + FileFormat = WebpFileFormatType.Lossless, + Method = WebpEncodingMethod.BestQuality + }; + + using Image image = provider.GetImage(); + using var memoryStream = new MemoryStream(); + image.Save(memoryStream, encoder); + + Assert.Equal(memoryStream.Length, expectedBytes); + } + [Theory] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 85)] [WithFile(RgbTestPattern100x100, PixelTypes.Rgba32, 60)] From 90316a1507a69fa93d74cd6179ed9c2133e56893 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 4 Dec 2021 19:56:43 +0100 Subject: [PATCH 175/228] serialize MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed --- .../UniformUnmanagedMemoryPoolTests.Trim.cs | 80 +++++++++++-------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 75e57c62b..2ab3c973a 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -47,46 +47,58 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; + // Complicated Xunit ceremony to disable parallel execution of an individual test, + // MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed, + // which is strongly timing-sensitive, and might be flaky under high load. + [CollectionDefinition(nameof(NonParallelCollection), DisableParallelization = true)] + public class NonParallelCollection + { + } - // TODO: Investigate failures on MacOS. All handles are released after GC. - // (It seems to happen more consistently on .NET 6.) - [ConditionalFact(nameof(IsNotMacOs))] - public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() + [Collection(nameof(NonParallelCollection))] + public class NonParallel { - RemoteExecutor.Invoke(RunTest).Dispose(); + public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; - static void RunTest() + // TODO: Investigate failures on MacOS. All handles are released after GC. + // (It seems to happen more consistently on .NET 6.) + [ConditionalFact(nameof(IsNotMacOs))] + public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { - var trimSettings1 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 6_000 }; - var pool1 = new UniformUnmanagedMemoryPool(128, 256, trimSettings1); - Thread.Sleep(8_000); // Let some callbacks fire already - var trimSettings2 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 3_000 }; - var pool2 = new UniformUnmanagedMemoryPool(128, 256, trimSettings2); - - pool1.Return(pool1.Rent(64)); - pool2.Return(pool2.Rent(64)); - Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); - - // This exercises pool weak reference list trimming: - LeakPoolInstance(); - GC.Collect(); - GC.WaitForPendingFinalizers(); - Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + RemoteExecutor.Invoke(RunTest).Dispose(); - Thread.Sleep(15_000); - Assert.True( - UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, - $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); - GC.KeepAlive(pool1); - GC.KeepAlive(pool2); - } + static void RunTest() + { + var trimSettings1 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 6_000 }; + var pool1 = new UniformUnmanagedMemoryPool(128, 256, trimSettings1); + Thread.Sleep(8_000); // Let some callbacks fire already + var trimSettings2 = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 3_000 }; + var pool2 = new UniformUnmanagedMemoryPool(128, 256, trimSettings2); + + pool1.Return(pool1.Rent(64)); + pool2.Return(pool2.Rent(64)); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + + // This exercises pool weak reference list trimming: + LeakPoolInstance(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Assert.Equal(128, UnmanagedMemoryHandle.TotalOutstandingHandles); + + Thread.Sleep(15_000); + Assert.True( + UnmanagedMemoryHandle.TotalOutstandingHandles <= 64, + $"UnmanagedMemoryHandle.TotalOutstandingHandles={UnmanagedMemoryHandle.TotalOutstandingHandles} > 64"); + GC.KeepAlive(pool1); + GC.KeepAlive(pool2); + } - [MethodImpl(MethodImplOptions.NoInlining)] - static void LeakPoolInstance() - { - var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 4_000 }; - _ = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + [MethodImpl(MethodImplOptions.NoInlining)] + static void LeakPoolInstance() + { + var trimSettings = new UniformUnmanagedMemoryPool.TrimSettings { TrimPeriodMilliseconds = 4_000 }; + _ = new UniformUnmanagedMemoryPool(128, 256, trimSettings); + } } } From 7eaa5ee1a2c3536041ff960050e15053b479511b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 4 Dec 2021 19:57:21 +0100 Subject: [PATCH 176/228] attempt to re-enable MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed on Mac --- .../Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 2ab3c973a..08c5fe75e 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -58,11 +58,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [Collection(nameof(NonParallelCollection))] public class NonParallel { - public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; - - // TODO: Investigate failures on MacOS. All handles are released after GC. - // (It seems to happen more consistently on .NET 6.) - [ConditionalFact(nameof(IsNotMacOs))] + [Fact] public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { RemoteExecutor.Invoke(RunTest).Dispose(); From 284772c11cdd54a864a8a729282eef6404390204 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 4 Dec 2021 21:06:20 +0100 Subject: [PATCH 177/228] Push input images to LFS again --- tests/Images/Input/Pbm/00000_00000.ppm | 7 +++---- .../Pbm/Gene-UP WebSocket RunImageMask.pgm | Bin 614417 -> 131 bytes .../Images/Input/Pbm/blackandwhite_binary.pbm | 6 +++--- .../Images/Input/Pbm/blackandwhite_plain.pbm | 13 +++---------- tests/Images/Input/Pbm/grayscale_plain.pgm | 13 +++---------- .../Input/Pbm/grayscale_plain_normalized.pgm | 13 +++---------- tests/Images/Input/Pbm/rgb_plain.ppm | 11 +++-------- .../Images/Input/Pbm/rgb_plain_normalized.ppm | 11 +++-------- tests/Images/Input/Pbm/rings.pgm | Bin 40038 -> 130 bytes 9 files changed, 21 insertions(+), 53 deletions(-) diff --git a/tests/Images/Input/Pbm/00000_00000.ppm b/tests/Images/Input/Pbm/00000_00000.ppm index 321188762..f6e085787 100644 --- a/tests/Images/Input/Pbm/00000_00000.ppm +++ b/tests/Images/Input/Pbm/00000_00000.ppm @@ -1,4 +1,3 @@ -P6 -29 30 -255 -KNPJLNVWTl^UtrnryysɻܫmpCARVbmY[aKOPDKKAEDBCBSTVPPRZYTk_{YQUZϧ~ѰɊʇR?NQ[ug\kTQVIMNLNKPPNNNPVUV]Z[xzkfD6ڹŕyݩbkbhberZ[^SSHJHIJENNJJKM[SSn\duQP[G㾵ؐFOL9oc}`ZKHBIJCIOJGJG`QHb`swIHrkϸدңΘˎy^mJH6"RHnnULJIKHP]\EJBxfTuaXA@Y_Ӂtujk``XVPNLJHED@A=?;>:y=9m=8x>7?1S=LDfkSPRJJJiknempmlB6A=@DAECGEJHOHNHNKNOOUW[_clky]`rkrlsorjqf}KHw_cHJJLIFh__{lmgrtw{}ɉ؎䐟쟵Ս|lalVPcQOZML_KJeJKTGKEEDJHBPLJo|xeq_kxbo}tttt``LLMPOTdkyiqgo```YYPWWRXVTRNNIFHIGGHGDJJHȻіoq\fJ\|f_ryXt]S^J;A9HGONIHCwvޮڳًaCBC}Pqe_nTSQOSQSURLLHNMGZWRţ~|kMWWJ[|T^xla~LVldŎ˱խýzy}сMQPZyhl]WW\a`Y[YPOLVSO[VQǎwWJJ:z`deuPOMRԝmwPmǟ}hڝYaG:qkvabkpp^a_QRPSSQ\[VaVUGvmslt@>gk٣ƺvWp^kf©zo|?9UTkmbhhY]\RUTRTT`b`r^daTwut@<ʁȾǕy˶nvճ۠@AWYmq]abX[ZUXXSUVZ\\|sqvpm}~|~x~}@<̔vq~~wǭƘ}AAZ[orX[[WZVUXTTWS[^Zlrthjndciffnhjfw}sD@pv󚗳lurxѵϑ?>^_jmVVTYZSVYQZ]T^aXlgHHX[él[[}Ĉuk۽ʺkl>4RNfg[YWZZUZ]W]`Y^aZĭëcgAAtq֨t~wjµļEOS?yor]\_\Z[[XWYUZ[V]]X­nvkxqyYOF8ɉt̰⡰_gckhplfjYTU^Z[a`bUWWUURXUQlvSk]n^q][BE*ʆhcK]cyX|YThTRV^^cWX_SUXOQPSUR|Y}}db|gI`Oj{Jm^OS?fOy_\K@7RTdrkhӇ裷Ähk]hkSeTOYPPSZ^bNQWLORHMNIQOU?OduOlKp`J_Fsw]d`lNRMI}MFQGVNoisjzcVWOKLRRT[_cMOUSTXJNQNUToĮX^:DO4mNveFlNNrUYx]b|l|jdhaztz~lkz[Y`NKPOORTXZLNOOOOMPPNTRLyWgnUW]M\|je^c}]h~bngmuy}}}{|wljy\[iMLUJJOPRRQRNRRNRSOQTPo|qkshjkdz}y}yziprnnhuoqup{okvjiuRS[FFJJJJOOKMNJNNI[[Wzpy|suvvruoothmrdkq`inbhmcgfekcKTKdla}[cinmasmugcm]Z`JHKKIIUTP^^Yab[cgc[_c[bgJUYDHKMJLTLM^QU]W^Z[`__bXVXJMNIRQLUQXaipmfumphah]V[PKOLHH]ZWge_nqh`hbUTW_ek=JOAGLEDESOMc[^ZW`RSXZZ\cbhQR]LPVNTVT_jfkhnnmb^ce^cWRWIGHQPMff_cfZZaY \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:3b291c0d3a0747c7425a3445bea1de1fa7c112a183d2f78bb9fc96ec5ae9804e +size 2623 diff --git a/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm b/tests/Images/Input/Pbm/Gene-UP WebSocket RunImageMask.pgm index 8265eaa50621a9a5455776fbe2c5faeb5933b862..efd46a2c89c2106981842f9fca60d12947f070c7 100644 GIT binary patch literal 131 zcmWN^%MHUI3;@tOQ?Nio_(Qe<+bKwGiE5HVr*BSA@1igF@sVxLgEys~eLh}0FSqTi z3yinogFszcjE<69F+OZIK_~@l(b2nvm4eFw$w1Ozp+=q2Si8EM@9q(uv5{gzf;A`d NR&f834B9yz#UCXGC)@x4 literal 614417 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 UfB_6(00S7n00uCCfxLnL0M1c2-v9sr diff --git a/tests/Images/Input/Pbm/blackandwhite_binary.pbm b/tests/Images/Input/Pbm/blackandwhite_binary.pbm index a25b1d350..d07976894 100644 --- a/tests/Images/Input/Pbm/blackandwhite_binary.pbm +++ b/tests/Images/Input/Pbm/blackandwhite_binary.pbm @@ -1,3 +1,3 @@ -P4 -# CREATOR: bitmap2pbm Version 1.0.0 -8 4 @0@0 +version https://git-lfs.github.com/spec/v1 +oid sha256:0313a99c2acdd34d6ba67815d1daa25f2452bfada71a1828dbcbb3cc48a20b20 +size 48 diff --git a/tests/Images/Input/Pbm/blackandwhite_plain.pbm b/tests/Images/Input/Pbm/blackandwhite_plain.pbm index fea8cafd0..9c92a99cc 100644 --- a/tests/Images/Input/Pbm/blackandwhite_plain.pbm +++ b/tests/Images/Input/Pbm/blackandwhite_plain.pbm @@ -1,10 +1,3 @@ -P1 -# PBM example -24 7 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 1 1 1 0 -0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 0 -0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 1 0 -0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 -0 1 0 0 0 0 0 1 1 1 1 0 0 1 1 1 1 0 0 1 0 0 0 0 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:12ccfacadea1c97c15b6d192ee3ae3b6a1d79bdca30fddbe597390f71e86d59c +size 367 diff --git a/tests/Images/Input/Pbm/grayscale_plain.pgm b/tests/Images/Input/Pbm/grayscale_plain.pgm index ba4757248..fa521b5da 100644 --- a/tests/Images/Input/Pbm/grayscale_plain.pgm +++ b/tests/Images/Input/Pbm/grayscale_plain.pgm @@ -1,10 +1,3 @@ -P2 -24 7 -15 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 -0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 -0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 -0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 -0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:08068b4d30f19024e716176033f13f7203a45513e6ae73e79dc824509c92621a +size 507 diff --git a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm index fe0329629..96497d605 100644 --- a/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm +++ b/tests/Images/Input/Pbm/grayscale_plain_normalized.pgm @@ -1,10 +1,3 @@ -P2 -24 7 -255 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0 51 51 51 51 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 255 255 255 0 -0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 255 0 -0 51 51 51 0 0 0 119 119 119 0 0 0 187 187 187 0 0 0 255 255 255 255 0 -0 51 0 0 0 0 0 119 0 0 0 0 0 187 0 0 0 0 0 255 0 0 0 0 -0 51 0 0 0 0 0 119 119 119 119 0 0 187 187 187 187 0 0 255 0 0 0 0 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:e39342751c2a57a060a029213fd7d83cb9a72881b8b01dd6d5b0e897df5077de +size 599 diff --git a/tests/Images/Input/Pbm/rgb_plain.ppm b/tests/Images/Input/Pbm/rgb_plain.ppm index ecd1b915c..32472d0ce 100644 --- a/tests/Images/Input/Pbm/rgb_plain.ppm +++ b/tests/Images/Input/Pbm/rgb_plain.ppm @@ -1,8 +1,3 @@ -P3 -# example from the man page -4 4 -15 - 0 0 0 0 0 0 0 0 0 15 0 15 - 0 0 0 0 15 7 0 0 0 0 0 0 - 0 0 0 0 0 0 0 15 7 0 0 0 -15 0 15 0 0 0 0 0 0 0 0 0 \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:895cd889f6723a5357936e852308cff25b74ead01618bf8efa0f876a86dc18c1 +size 205 diff --git a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm index 628931579..3d7fbe241 100644 --- a/tests/Images/Input/Pbm/rgb_plain_normalized.ppm +++ b/tests/Images/Input/Pbm/rgb_plain_normalized.ppm @@ -1,8 +1,3 @@ -P3 -# example from the man page -4 4 -255 - 0 0 0 0 0 0 0 0 0 255 0 255 - 0 0 0 0 255 119 0 0 0 0 0 0 - 0 0 0 0 0 0 0 255 119 0 0 0 -255 0 255 0 0 0 0 0 0 0 0 0 \ No newline at end of file +version https://git-lfs.github.com/spec/v1 +oid sha256:59be6295e3983708ffba811a408acd83df8e9736b487a94d30132dee0edd6cb6 +size 234 diff --git a/tests/Images/Input/Pbm/rings.pgm b/tests/Images/Input/Pbm/rings.pgm index e0d4b4ed4d4cfc4da44b131a68f60824957428b6..4f2c8d2c74faa33c342dad41e4850bfa8d527cf6 100644 GIT binary patch literal 130 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 Wa*AACLyF_*_)AQ-%Z&HTYW)|`Dr-*w From 20b0349455d4918f304e63c6863673c07fcb8a1f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 4 Dec 2021 21:08:29 +0100 Subject: [PATCH 178/228] NeedsUpscaling code improvements --- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 427ea15e8..1aeecc16f 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -53,22 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// Size IImageDecoderInternals.Dimensions => this.PixelSize; - private bool NeedsUpscaling - { - get - { - bool needsUpscaling = false; - if (this.ColorType != PbmColorType.BlackAndWhite) - { - if (this.MaxPixelValue is not 255 and not 65535) - { - needsUpscaling = true; - } - } - - return needsUpscaling; - } - } + private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.MaxPixelValue is not 255 and not 65535; /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) From 3a0a044fe2c6d964ff589aebb1215f34c76d5bda Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 4 Dec 2021 21:14:25 +0100 Subject: [PATCH 179/228] Improve exception messages --- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 1aeecc16f..b2be74ea1 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -94,8 +94,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm int bytesRead = stream.Read(buffer); if (bytesRead != 2 || buffer[0] != 'P') { - // Empty or not an PPM image. - throw new InvalidImageContentException("TODO"); + throw new InvalidImageContentException("Empty or not an PPM image."); } switch ((char)buffer[1]) @@ -134,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm // PAM image: sequence of images. // Not implemented yet default: - throw new NotImplementedException("TODO"); + throw new InvalidImageContentException("Unknown of not implemented image type encountered."); } stream.SkipWhitespaceAndComments(); From 61cfa66bdb1f60760729a004f302163dbe187ada Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 4 Dec 2021 21:43:36 +0100 Subject: [PATCH 180/228] Make variables as const if possible --- src/ImageSharp/Formats/Pbm/BinaryDecoder.cs | 13 +++++++------ src/ImageSharp/Formats/Pbm/BinaryEncoder.cs | 6 +++--- src/ImageSharp/Formats/Pbm/PlainDecoder.cs | 5 +++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs index 8b6df295b..2a171456a 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -14,6 +14,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// internal class BinaryDecoder { + private static L8 white = new L8(255); + private static L8 black = new L8(0); + /// /// Decode the specified pixels. /// @@ -60,9 +63,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void ProcessGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) where TPixel : unmanaged, IPixel { + const int bytesPerPixel = 1; int width = pixels.Width; int height = pixels.Height; - int bytesPerPixel = 1; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); @@ -82,9 +85,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void ProcessWideGrayscale(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) where TPixel : unmanaged, IPixel { + const int bytesPerPixel = 2; int width = pixels.Width; int height = pixels.Height; - int bytesPerPixel = 2; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); @@ -104,9 +107,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void ProcessRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) where TPixel : unmanaged, IPixel { + const int bytesPerPixel = 3; int width = pixels.Width; int height = pixels.Height; - int bytesPerPixel = 3; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); @@ -126,9 +129,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void ProcessWideRgb(Configuration configuration, Buffer2D pixels, BufferedReadStream stream) where TPixel : unmanaged, IPixel { + const int bytesPerPixel = 6; int width = pixels.Width; int height = pixels.Height; - int bytesPerPixel = 6; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); @@ -154,8 +157,6 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - var white = new L8(255); - var black = new L8(0); for (int y = 0; y < height; y++) { diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs index 2bcbaeef7..626026726 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs @@ -83,9 +83,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteWideGrayscale(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { + const int bytesPerPixel = 2; int width = image.Width; int height = image.Height; - int bytesPerPixel = 2; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); @@ -107,9 +107,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteRgb(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { + const int bytesPerPixel = 3; int width = image.Width; int height = image.Height; - int bytesPerPixel = 3; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); @@ -131,9 +131,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm private static void WriteWideRgb(Configuration configuration, Stream stream, ImageFrame image) where TPixel : unmanaged, IPixel { + const int bytesPerPixel = 6; int width = image.Width; int height = image.Height; - int bytesPerPixel = 6; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs index 9fa8e513e..dc5350bdd 100644 --- a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -14,6 +14,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// internal class PlainDecoder { + private static L8 white = new L8(255); + private static L8 black = new L8(0); + /// /// Decode the specified pixels. /// @@ -174,8 +177,6 @@ namespace SixLabors.ImageSharp.Formats.Pbm MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); - var white = new L8(255); - var black = new L8(0); for (int y = 0; y < height; y++) { From e9b6b8aaa2e1e218c28d9c0c31837296d2123ab8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 5 Dec 2021 09:07:10 +0300 Subject: [PATCH 181/228] Fixed error --- .../Jpeg/Components/Encoder/HuffmanScanEncoder.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index d71a62ec9..6acc6b6db 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -650,6 +650,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } this.target.Write(this.streamWriteBuffer, 0, writeIdx); + this.emitWriteIndex = this.emitBuffer.Length; } /// @@ -660,11 +661,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder /// This must be called only if is true /// only during the macroblocks encoding routine. /// - private void FlushToStream() - { + private void FlushToStream() => this.FlushToStream(this.emitWriteIndex * 4); - this.emitWriteIndex = this.emitBuffer.Length; - } /// /// Flushes final cached bits to the stream padding 1's to @@ -681,10 +679,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // And writing only valuable count of bytes count we want to write to the output stream int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8); uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount); - this.emitBuffer[--this.emitWriteIndex] = packedBytes; + this.emitBuffer[this.emitWriteIndex - 1] = packedBytes; // Flush cached bytes to the output stream with padding bits - this.FlushToStream((this.emitWriteIndex * 4) - 4 + valuableBytesCount); + int lastByteIndex = (this.emitWriteIndex * 4) - valuableBytesCount; + this.FlushToStream(lastByteIndex); } } } From dd49f9a811aabf0a93b73b4a3a1a30016e91367e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 5 Dec 2021 16:24:39 +0100 Subject: [PATCH 182/228] Single image compare utils for multiple Formats --- .../Formats/Pbm/PbmEncoderTests.cs | 4 +- .../Formats/Tga/TgaDecoderTests.cs | 96 +++++++++---------- .../Formats/Tga/TgaEncoderTests.cs | 4 +- .../Formats/Tiff/TiffMetadataTests.cs | 8 ++ .../Formats/Tiff/TiffTestUtils.cs | 65 ------------- .../Formats/WebP/LosslessUtilsTests.cs | 2 +- .../ImageComparison/ImageComparingUtils.cs} | 5 +- 7 files changed, 63 insertions(+), 121 deletions(-) delete mode 100644 tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs rename tests/ImageSharp.Tests/{Formats/Tga/TgaTestUtils.cs => TestUtilities/ImageComparison/ImageComparingUtils.cs} (93%) diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs index 339cc4a5c..dcc63618c 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -5,7 +5,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Formats.Tga; - +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm memStream.Position = 0; using (var encodedImage = (Image)Image.Load(memStream)) { - TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); + ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 1c53ff6a1..bd7a6e07a 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -89,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -113,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -245,7 +245,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -269,7 +269,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -281,7 +281,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -293,7 +293,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -317,7 +317,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -329,7 +329,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -353,7 +353,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -365,7 +365,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -377,7 +377,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -389,7 +389,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -401,7 +401,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -413,7 +413,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -425,7 +425,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -437,7 +437,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -449,7 +449,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -461,7 +461,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -473,7 +473,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -485,7 +485,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -497,7 +497,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -509,7 +509,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -521,7 +521,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -533,7 +533,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -545,7 +545,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -557,7 +557,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -569,7 +569,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -581,7 +581,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -593,7 +593,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -605,7 +605,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -617,7 +617,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -629,7 +629,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -641,7 +641,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -653,7 +653,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -665,7 +665,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -677,7 +677,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -689,7 +689,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -701,7 +701,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } @@ -714,7 +714,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga using (Image image = provider.GetImage(TgaDecoder)) { image.DebugSave(provider); - TgaTestUtils.CompareWithReferenceDecoder(provider, image); + ImageComparingUtils.CompareWithReferenceDecoder(provider, image); } } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 4c768a1a5..da8ff8018 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -4,7 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tga; @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga memStream.Position = 0; using (var encodedImage = (Image)Image.Load(memStream)) { - TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); + ImageComparingUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index cdd9616a7..c912ae26b 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Collections.Generic; using System.IO; using System.Linq; using SixLabors.ImageSharp.Common.Helpers; @@ -22,6 +23,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { private static TiffDecoder TiffDecoder => new TiffDecoder(); + private class NumberComparer : IEqualityComparer + { + public bool Equals(Number x, Number y) => x.Equals(y); + + public int GetHashCode(Number obj) => obj.GetHashCode(); + } + [Fact] public void TiffMetadata_CloneIsDeep() { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs deleted file mode 100644 index eacadae2b..000000000 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffTestUtils.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using System.Collections.Generic; -using System.IO; -using ImageMagick; - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; - -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Formats.Tiff -{ - public static class TiffTestUtils - { - public static void CompareWithReferenceDecoder( - string encodedImagePath, - Image image, - bool useExactComparer = true, - float compareTolerance = 0.01f) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - var testFile = TestFile.Create(encodedImagePath); - Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); - if (useExactComparer) - { - ImageComparer.Exact.VerifySimilarity(magickImage, image); - } - else - { - ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image); - } - } - - public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo) - where TPixel : unmanaged, ImageSharp.PixelFormats.IPixel - { - using var magickImage = new MagickImage(fileInfo); - magickImage.AutoOrient(); - var result = new Image(configuration, magickImage.Width, magickImage.Height); - - Assert.True(result.TryGetSinglePixelSpan(out Span resultPixels)); - - using IUnsafePixelCollection pixels = magickImage.GetPixelsUnsafe(); - byte[] data = pixels.ToByteArray(PixelMapping.RGBA); - - PixelOperations.Instance.FromRgba32Bytes( - configuration, - data, - resultPixels, - resultPixels.Length); - - return result; - } - } - - internal class NumberComparer : IEqualityComparer - { - public bool Equals(Number x, Number y) => x.Equals(y); - - public int GetHashCode(Number obj) => obj.GetHashCode(); - } -} diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 684d7791b..bd8a48a44 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -229,7 +229,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void TransformColorInverse_Works() => RunTransformColorInverseTest(); #if SUPPORTS_RUNTIME_INTRINSICS - + [Fact] public void CombinedShannonEntropy_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.AllowAll); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs similarity index 93% rename from tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs rename to tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs index c96777031..7eae5938f 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs @@ -5,12 +5,11 @@ using System; using System.IO; using ImageMagick; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tga +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public static class TgaTestUtils + public static class ImageComparingUtils { public static void CompareWithReferenceDecoder( TestImageProvider provider, From 1aa27bd3efdb43d0daac07527212fe4a1a879b2a Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 5 Dec 2021 16:57:24 +0100 Subject: [PATCH 183/228] Add Magick compatible input image --- tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs | 5 ++++- tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs | 8 ++------ tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs | 2 ++ tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Pbm/grayscale_plain_magick.pgm | 3 +++ tests/Images/Input/Pbm/rgb_plain_magick.ppm | 3 +++ 6 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 tests/Images/Input/Pbm/grayscale_plain_magick.pgm create mode 100644 tests/Images/Input/Pbm/rgb_plain_magick.ppm diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 8b8e1a08f..51bf61d23 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -4,7 +4,6 @@ using System.IO; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; @@ -18,9 +17,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] [InlineData(GrayscalePlain, PbmColorType.Grayscale)] + [InlineData(GrayscalePlainMagick, PbmColorType.Grayscale)] [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] [InlineData(RgbPlain, PbmColorType.Rgb)] + [InlineData(RgbPlainMagick, PbmColorType.Rgb)] [InlineData(RgbBinary, PbmColorType.Rgb)] public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType) { @@ -42,6 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [InlineData(BlackAndWhitePlain)] [InlineData(BlackAndWhiteBinary)] [InlineData(GrayscalePlain)] + [InlineData(GrayscalePlainMagick)] [InlineData(GrayscaleBinary)] [InlineData(GrayscaleBinaryWide)] public void ImageLoadL8CanDecode(string imagePath) @@ -59,6 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [Theory] [InlineData(RgbPlain)] + [InlineData(RgbPlainMagick)] [InlineData(RgbBinary)] public void ImageLoadRgb24CanDecode(string imagePath) { diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs index dcc63618c..44fab1617 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -93,24 +93,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm public void PbmEncoder_P4_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.BlackAndWhite, PbmEncoding.Binary); - /* Disabled as Magick throws an error reading the input image [Theory] - [WithFile(GrayscalePlain, PixelTypes.Rgb24)] + [WithFile(GrayscalePlainMagick, PixelTypes.Rgb24)] public void PbmEncoder_P2_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Plain); - */ [Theory] [WithFile(GrayscaleBinary, PixelTypes.Rgb24)] public void PbmEncoder_P5_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Grayscale, PbmEncoding.Binary); - /* Disabled as Magick throws an error reading the input image [Theory] - [WithFile(RgbPlain, PixelTypes.Rgb24)] + [WithFile(RgbPlainMagick, PixelTypes.Rgb24)] public void PbmEncoder_P3_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestPbmEncoderCore(provider, PbmColorType.Rgb, PbmEncoding.Plain); - */ [Theory] [WithFile(RgbBinary, PixelTypes.Rgb24)] diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs index 9521ee7e9..190972535 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmRoundTripTests.cs @@ -18,6 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [InlineData(BlackAndWhiteBinary)] [InlineData(GrayscalePlain)] [InlineData(GrayscalePlainNormalized)] + [InlineData(GrayscalePlainMagick)] [InlineData(GrayscaleBinary)] public void PbmGrayscaleImageCanRoundTrip(string imagePath) { @@ -38,6 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm [Theory] [InlineData(RgbPlain)] [InlineData(RgbPlainNormalized)] + [InlineData(RgbPlainMagick)] [InlineData(RgbBinary)] public void PbmColorImageCanRoundTrip(string imagePath) { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 444be63a2..930b550a2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -875,9 +875,11 @@ namespace SixLabors.ImageSharp.Tests public const string GrayscaleBinaryWide = "Pbm/Gene-UP WebSocket RunImageMask.pgm"; public const string GrayscalePlain = "Pbm/grayscale_plain.pgm"; public const string GrayscalePlainNormalized = "Pbm/grayscale_plain_normalized.pgm"; + public const string GrayscalePlainMagick = "Pbm/grayscale_plain_magick.pgm"; public const string RgbBinary = "Pbm/00000_00000.ppm"; public const string RgbPlain = "Pbm/rgb_plain.ppm"; public const string RgbPlainNormalized = "Pbm/rgb_plain_normalized.ppm"; + public const string RgbPlainMagick = "Pbm/rgb_plain_magick.ppm"; } } } diff --git a/tests/Images/Input/Pbm/grayscale_plain_magick.pgm b/tests/Images/Input/Pbm/grayscale_plain_magick.pgm new file mode 100644 index 000000000..fe1bb28b3 --- /dev/null +++ b/tests/Images/Input/Pbm/grayscale_plain_magick.pgm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec652ee7ea1a82d8ea2fd344670ab9aee2c2f52af86458d9991754204e1fc2bb +size 464 diff --git a/tests/Images/Input/Pbm/rgb_plain_magick.ppm b/tests/Images/Input/Pbm/rgb_plain_magick.ppm new file mode 100644 index 000000000..ee88eb7f3 --- /dev/null +++ b/tests/Images/Input/Pbm/rgb_plain_magick.ppm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f38a31162f31e77f5ad80da968a386b2cbccc6998a88a4c6b311b48919119a1 +size 149 From 093e1ae946a6df6138b381ca97fb3ad65dd58568 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 5 Dec 2021 17:42:00 +0100 Subject: [PATCH 184/228] Add missing using --- .../TestUtilities/ImageComparison/ImageComparingUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs index 7eae5938f..cbf38f308 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison } var testFile = TestFile.Create(path); - Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); + using Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath)); if (useExactComparer) { ImageComparer.Exact.VerifySimilarity(magickImage, image); From f09641c06b1f77de8234883aeb3e6ca4bc84962b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 5 Dec 2021 17:46:10 +0100 Subject: [PATCH 185/228] Cleanup --- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 2 +- src/ImageSharp/Formats/Pbm/PbmEncoder.cs | 4 ++-- src/ImageSharp/Formats/Pbm/PbmFormat.cs | 2 +- src/ImageSharp/Formats/Pbm/PbmMetadata.cs | 5 +---- src/ImageSharp/Formats/Pbm/PlainDecoder.cs | 6 +++--- tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs | 7 +++---- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index b2be74ea1..8bac0bfd1 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -115,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm this.Encoding = PbmEncoding.Plain; break; case '4': - // Binary PBM format: 1 component per pixel, 8 picels per byte. + // Binary PBM format: 1 component per pixel, 8 pixels per byte. this.ColorType = PbmColorType.BlackAndWhite; this.Encoding = PbmEncoding.Binary; break; diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs index fe0f7f9f1..7984dddac 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs @@ -13,9 +13,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// Image encoder for writing an image to a stream as PGM, PBM or PPM bitmap. These images are from /// the family of PNM images. /// - /// The PNM formats are a faily simple image format. They share a plain text header, consisting of: + /// The PNM formats are a fairly simple image format. They share a plain text header, consisting of: /// signature, width, height and max_pixel_value only. The pixels follow thereafter and can be in - /// plain text decimals seperated by spaces, or binary encoded. + /// plain text decimals separated by spaces, or binary encoded. /// /// /// PBM diff --git a/src/ImageSharp/Formats/Pbm/PbmFormat.cs b/src/ImageSharp/Formats/Pbm/PbmFormat.cs index 35aa9cf8c..5ffb49652 100644 --- a/src/ImageSharp/Formats/Pbm/PbmFormat.cs +++ b/src/ImageSharp/Formats/Pbm/PbmFormat.cs @@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// Gets the current instance. /// - public static PbmFormat Instance { get; } = new PbmFormat(); + public static PbmFormat Instance { get; } = new(); /// public string Name => "PBM"; diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs index b29cd27c2..869b1b06d 100644 --- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -11,10 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// Initializes a new instance of the class. /// - public PbmMetadata() - { - this.MaxPixelValue = this.ColorType == PbmColorType.BlackAndWhite ? 1 : 255; - } + public PbmMetadata() => this.MaxPixelValue = this.ColorType == PbmColorType.BlackAndWhite ? 1 : 255; /// /// Initializes a new instance of the class. diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs index dc5350bdd..4521f9b64 100644 --- a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -14,8 +14,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// internal class PlainDecoder { - private static L8 white = new L8(255); - private static L8 black = new L8(0); + private static readonly L8 White = new(255); + private static readonly L8 Black = new(0); /// /// Decode the specified pixels. @@ -184,7 +184,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int value = stream.ReadDecimal(); stream.SkipWhitespaceAndComments(); - rowSpan[x] = value == 0 ? white : black; + rowSpan[x] = value == 0 ? White : Black; } Span pixelSpan = pixels.GetRowSpan(y); diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs index 44fab1617..2ca49a39d 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -4,7 +4,6 @@ using System.IO; using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Tests.Formats.Tga; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; @@ -17,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm public class PbmEncoderTests { public static readonly TheoryData ColorType = - new TheoryData + new() { PbmColorType.BlackAndWhite, PbmColorType.Grayscale, @@ -25,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm }; public static readonly TheoryData PbmColorTypeFiles = - new TheoryData + new() { { BlackAndWhiteBinary, PbmColorType.BlackAndWhite }, { BlackAndWhitePlain, PbmColorType.BlackAndWhite }, @@ -67,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm Encoding = PbmEncoding.Plain }; - TestFile testFile = TestFile.Create(imagePath); + var testFile = TestFile.Create(imagePath); using (Image input = testFile.CreateRgba32Image()) { using (var memStream = new MemoryStream()) From c214af56eb40e3740117dfc429f7b600a0559f9c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Dec 2021 19:35:51 +1100 Subject: [PATCH 186/228] Optimize chunk reading to maximize performance --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 56 +++++++++++++++++--- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index ba737cb42..71b322df4 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -37,6 +37,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private readonly bool ignoreMetadata; + /// + /// Gets or sets a value indicating whether to read the header and trns chunks only. + /// + private readonly bool colorMetadataOnly; + /// /// Used the manage memory allocations. /// @@ -124,11 +129,12 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } - internal PngDecoderCore(Configuration configuration, bool ignoreMetadata) + internal PngDecoderCore(Configuration configuration, bool colorMetadataOnly) { this.Configuration = configuration ?? Configuration.Default; this.memoryAllocator = this.Configuration.MemoryAllocator; - this.ignoreMetadata = ignoreMetadata; + this.colorMetadataOnly = colorMetadataOnly; + this.ignoreMetadata = true; } /// @@ -137,7 +143,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Gets the dimensions of the image. /// - public Size Dimensions => new Size(this.header.Width, this.header.Height); + public Size Dimensions => new(this.header.Width, this.header.Height); /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -250,9 +256,21 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Physical: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Gamma: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Data: @@ -265,15 +283,39 @@ namespace SixLabors.ImageSharp.Formats.Png this.AssignTransparentMarkers(alpha, pngMetadata); break; case PngChunkType.Text: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: + if (this.colorMetadataOnly) + { + this.SkipChunkDataAndCrc(chunk); + break; + } + if (!this.ignoreMetadata) { byte[] exifData = new byte[chunk.Length]; @@ -928,7 +970,7 @@ namespace SixLabors.ImageSharp.Formats.Png int zeroIndex = data.IndexOf((byte)0); // Keywords are restricted to 1 to 79 bytes in length. - if (zeroIndex < PngConstants.MinTextKeywordLength || zeroIndex > PngConstants.MaxTextKeywordLength) + if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength) { return; } @@ -1155,8 +1197,10 @@ namespace SixLabors.ImageSharp.Formats.Png PngChunkType type = this.ReadChunkType(); - // NOTE: Reading the chunk data is the responsible of the caller - if (type == PngChunkType.Data) + // NOTE: Reading the Data chunk is the responsible of the caller + // If we're reading color metadata only we're only interested in the Header and Transparancy chunks. + // We can skip all other chunk data in the stream for better performance. + if (type == PngChunkType.Data || (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency)) { chunk = new PngChunk(length, type); From 7429547d4cdf743c6a270292b085adb113d6d20d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Dec 2021 19:46:04 +1100 Subject: [PATCH 187/228] Faster exit --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 71b322df4..7ffe0cad2 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -82,6 +82,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private byte[] paletteAlpha; + /// + /// A value indicating whether the header chunk has been reached. + /// + private bool isHeaderChunkReached; + /// /// A value indicating whether the end chunk has been reached. /// @@ -254,6 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Png { case PngChunkType.Header: this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); + this.isHeaderChunkReached = true; break; case PngChunkType.Physical: if (this.colorMetadataOnly) @@ -281,6 +287,13 @@ namespace SixLabors.ImageSharp.Formats.Png chunk.Data.GetSpan().CopyTo(alpha); this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); + + if (this.colorMetadataOnly && this.isHeaderChunkReached) + { + // Quick exit + this.isEndChunkReached = true; + } + break; case PngChunkType.Text: if (this.colorMetadataOnly) From 6107845451f1e5823930ebbd6a7c99730a291609 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 7 Dec 2021 16:15:15 +0100 Subject: [PATCH 188/228] Use ref parameters in Load8x4 and Load16x4 --- .../Formats/Webp/Lossy/LossyUtils.cs | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 966fb561f..af5f136fa 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -1258,11 +1258,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (Sse2.IsSupported) { // Beginning of p1 - p = p.Slice(offset - 2); + ref byte pRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(p), offset - 2); - Load16x4(p, p.Slice(8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); + Load16x4(ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1); DoFilter2Sse2(ref p1, ref p0, ref q0, ref q1, thresh); - Store16x4(p1, p0, q0, q1, p, p.Slice(8 * stride), stride); + Store16x4(p1, p0, q0, q1, ref pRef, ref Unsafe.Add(ref pRef, 8 * stride), stride); } else #endif @@ -1374,14 +1374,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - Span b = p.Slice(offset - 4); - Load16x4(b, b.Slice(8 * stride), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + ref byte pRef = ref MemoryMarshal.GetReference(p); + ref byte bRef = ref Unsafe.Add(ref pRef, offset - 4); + Load16x4(ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); Vector128 mask = Abs(p1, p0); mask = Sse2.Max(mask, Abs(p3, p2)); mask = Sse2.Max(mask, Abs(p2, p1)); - Load16x4(p.Slice(offset), p.Slice(offset + (8 * stride)), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); mask = Sse2.Max(mask, Abs(q1, q0)); mask = Sse2.Max(mask, Abs(q3, q2)); @@ -1390,8 +1391,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - Store16x4(p3, p2, p1, p0, b, b.Slice(8 * stride), stride); - Store16x4(q0, q1, q2, q3, p.Slice(offset), p.Slice(offset + (8 * stride)), stride); + Store16x4(p3, p2, p1, p0, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); + Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride); } else #endif @@ -1463,13 +1464,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - Load16x4(p.Slice(offset), p.Slice(offset + (8 * stride)), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + ref byte pRef = ref MemoryMarshal.GetReference(p); + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); Vector128 mask; for (int k = 3; k > 0; k--) { // Beginning of p1. - Span b = p.Slice(offset + 2); + ref byte bRef = ref Unsafe.Add(ref pRef, offset + 2); // Beginning of q0 (and next span). offset += 4; @@ -1479,7 +1481,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy mask = Sse2.Max(mask, Abs(p3, p2)); mask = Sse2.Max(mask, Abs(p2, p1)); - Load16x4(p.Slice(offset), p.Slice(offset + (8 * stride)), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); + Load16x4(ref Unsafe.Add(ref pRef, offset), ref Unsafe.Add(ref pRef, offset + (8 * stride)), stride, out p3, out p2, out Vector128 tmp1, out Vector128 tmp2); mask = Sse2.Max(mask, Abs(tmp1, tmp2)); mask = Sse2.Max(mask, Abs(p3, p2)); @@ -1488,7 +1490,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ComplexMask(p1, p0, p3, p2, thresh, ithresh, ref mask); DoFilter4Sse2(ref p1, ref p0, ref p3, ref p2, mask, hevThresh); - Store16x4(p1, p0, p3, p2, b, b.Slice(8 * stride), stride); + Store16x4(p1, p0, p3, p2, ref bRef, ref Unsafe.Add(ref bRef, 8 * stride), stride); // Rotate samples. p1 = tmp1; @@ -1559,15 +1561,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - Span tu = u.Slice(offset - 4); - Span tv = v.Slice(offset - 4); - Load16x4(tu, tv, stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Load16x4(ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride, out Vector128 p3, out Vector128 p2, out Vector128 p1, out Vector128 p0); Vector128 mask = Abs(p1, p0); mask = Sse2.Max(mask, Abs(p3, p2)); mask = Sse2.Max(mask, Abs(p2, p1)); - Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out Vector128 q2, out Vector128 q3); mask = Sse2.Max(mask, Abs(q1, q0)); mask = Sse2.Max(mask, Abs(q3, q2)); @@ -1576,8 +1578,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ComplexMask(p1, p0, q0, q1, thresh, ithresh, ref mask); DoFilter6Sse2(ref p2, ref p1, ref p0, ref q0, ref q1, ref q2, mask, hevThresh); - Store16x4(p3, p2, p1, p0, tu, tv, stride); - Store16x4(q0, q1, q2, q3, u.Slice(offset), v.Slice(offset), stride); + Store16x4(p3, p2, p1, p0, ref Unsafe.Add(ref uRef, offset - 4), ref Unsafe.Add(ref vRef, offset - 4), stride); + Store16x4(q0, q1, q2, q3, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); } else #endif @@ -1640,7 +1642,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 t2, out Vector128 t1, out Vector128 p1, out Vector128 p0); Vector128 mask = Abs(p1, p0); mask = Sse2.Max(mask, Abs(t2, t1)); @@ -1649,7 +1653,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Beginning of q0. offset += 4; - Load16x4(u.Slice(offset), v.Slice(offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); + Load16x4(ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride, out Vector128 q0, out Vector128 q1, out t1, out t2); mask = Sse2.Max(mask, Abs(q1, q0)); mask = Sse2.Max(mask, Abs(t2, t1)); @@ -1660,7 +1664,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Beginning of p1. offset -= 2; - Store16x4(p1, p0, q0, q1, u.Slice(offset), v.Slice(offset), stride); + Store16x4(p1, p0, q0, q1, ref Unsafe.Add(ref uRef, offset), ref Unsafe.Add(ref vRef, offset), stride); } else #endif @@ -2072,7 +2076,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Sse2.CompareEqual(t7, Vector128.Zero); } - private static void Load16x4(Span r0, Span r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) + private static void Load16x4(ref byte r0, ref byte r8, int stride, out Vector128 p1, out Vector128 p0, out Vector128 q0, out Vector128 q1) { // Assume the pixels around the edge (|) are numbered as follows // 00 01 | 02 03 @@ -2089,8 +2093,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 - Load8x4(r0, stride, out Vector128 t1, out Vector128 t2); - Load8x4(r8, stride, out p0, out q1); + Load8x4(ref r0, stride, out Vector128 t1, out Vector128 t2); + Load8x4(ref r8, stride, out p0, out q1); // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 @@ -2103,11 +2107,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Reads 8 rows across a vertical edge. - private static void Load8x4(Span b, int stride, out Vector128 p, out Vector128 q) + private static void Load8x4(ref byte bRef, int stride, out Vector128 p, out Vector128 q) { // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 - ref byte bRef = ref MemoryMarshal.GetReference(b); uint a00 = Unsafe.As(ref Unsafe.Add(ref bRef, 6 * stride)); uint a01 = Unsafe.As(ref Unsafe.Add(ref bRef, 2 * stride)); uint a02 = Unsafe.As(ref Unsafe.Add(ref bRef, 4 * stride)); @@ -2136,7 +2139,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Transpose back and store - private static void Store16x4(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, Span r0, Span r8, int stride) + private static void Store16x4(Vector128 p1, Vector128 p0, Vector128 q0, Vector128 q1, ref byte r0Ref, ref byte r8Ref, int stride) { // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 @@ -2160,17 +2163,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy p1s = Sse2.UnpackLow(t1.AsInt16(), q1s.AsInt16()).AsByte(); q1s = Sse2.UnpackHigh(t1.AsInt16(), q1s.AsInt16()).AsByte(); - Store4x4(p0s, r0, stride); - Store4x4(q0s, r0.Slice(4 * stride), stride); + Store4x4(p0s, ref r0Ref, stride); + Store4x4(q0s, ref Unsafe.Add(ref r0Ref, 4 * stride), stride); - Store4x4(p1s, r8, stride); - Store4x4(q1s, r8.Slice(4 * stride), stride); + Store4x4(p1s, ref r8Ref, stride); + Store4x4(q1s, ref Unsafe.Add(ref r8Ref, 4 * stride), stride); } - private static void Store4x4(Vector128 x, Span dst, int stride) + private static void Store4x4(Vector128 x, ref byte dstRef, int stride) { int offset = 0; - ref byte dstRef = ref MemoryMarshal.GetReference(dst); for (int i = 0; i < 4; i++) { Unsafe.As(ref Unsafe.Add(ref dstRef, offset)) = Sse2.ConvertToInt32(x.AsInt32()); From c90993b32ff5e18d2b0afcf124346d41e5226ee9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Dec 2021 23:36:46 +0100 Subject: [PATCH 189/228] Comments on [Theory] data --- .../UniformUnmanagedPoolMemoryAllocatorTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 26a90591e..f7893ec64 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -254,9 +254,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [InlineData(300)] - [InlineData(600)] - [InlineData(1200)] + [InlineData(300)] // Group of single SharedArrayPoolBuffer + [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer + [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers public void MemoryGroupFinalizer_ReturnsToPool(int length) { // RunTest(length.ToString()); @@ -304,8 +304,8 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } [Theory] - [InlineData(300)] - [InlineData(600)] + [InlineData(300)] // Group of single SharedArrayPoolBuffer + [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer public void MemoryOwnerFinalizer_ReturnsToPool(int length) { // RunTest(length.ToString()); From 01f055a6a29a50627f6e6fa1162e81c07b1facef Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Dec 2021 23:37:23 +0100 Subject: [PATCH 190/228] Revert "attempt to re-enable MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed on Mac" This reverts commit 7eaa5ee1a2c3536041ff960050e15053b479511b. --- .../Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 08c5fe75e..2ab3c973a 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -58,7 +58,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [Collection(nameof(NonParallelCollection))] public class NonParallel { - [Fact] + public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; + + // TODO: Investigate failures on MacOS. All handles are released after GC. + // (It seems to happen more consistently on .NET 6.) + [ConditionalFact(nameof(IsNotMacOs))] public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { RemoteExecutor.Invoke(RunTest).Dispose(); From 4986c52b30dad3a2ea1d570bc648bb6d27fadc11 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 7 Dec 2021 23:43:50 +0100 Subject: [PATCH 191/228] Address local failures caused by high memory load --- .../UniformUnmanagedMemoryPoolTests.Trim.cs | 7 +++++++ .../UniformUnmanagedPoolMemoryAllocatorTests.cs | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs index 2ab3c973a..f748b7feb 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs @@ -65,6 +65,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [ConditionalFact(nameof(IsNotMacOs))] public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() { + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } + RemoteExecutor.Invoke(RunTest).Dispose(); static void RunTest() diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index f7893ec64..cc6632af1 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -259,6 +259,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers public void MemoryGroupFinalizer_ReturnsToPool(int length) { + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } + // RunTest(length.ToString()); RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); @@ -308,6 +315,13 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer public void MemoryOwnerFinalizer_ReturnsToPool(int length) { + if (!TestEnvironment.RunsOnCI) + { + // This may fail in local runs resulting in high memory load. + // Remove the condition for local debugging! + return; + } + // RunTest(length.ToString()); RemoteExecutor.Invoke(RunTest, length.ToString()).Dispose(); From 1f6a503b9b3e57480cb73ae9f1218143228802bd Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Dec 2021 13:05:47 +0100 Subject: [PATCH 192/228] Add SSE2 version of Vp8_Sse16X16 and Vp8_Sse16X8 --- .../Formats/Webp/Lossy/LossyUtils.cs | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index af5f136fa..69ce6c7f9 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -40,11 +40,33 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Note: method name in libwebp reference implementation is called VP8SSE16x16. [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16X16(Span a, Span b) => Vp8_SseNxN(a, b, 16, 16); + public static int Vp8_Sse16X16(Span a, Span b) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + return Vp8_Sse16xN_Sse2(a, b, 8); + } +#endif + { + return Vp8_SseNxN(a, b, 16, 16); + } + } // Note: method name in libwebp reference implementation is called VP8SSE16x8. [MethodImpl(InliningOptions.ShortMethod)] - public static int Vp8_Sse16X8(Span a, Span b) => Vp8_SseNxN(a, b, 16, 8); + public static int Vp8_Sse16X8(Span a, Span b) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + return Vp8_Sse16xN_Sse2(a, b, 4); + } +#endif + { + return Vp8_SseNxN(a, b, 16, 8); + } + } // Note: method name in libwebp reference implementation is called VP8SSE4x4. [MethodImpl(InliningOptions.ShortMethod)] @@ -146,6 +168,52 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return count; } +#if SUPPORTS_RUNTIME_INTRINSICS + [MethodImpl(InliningOptions.ShortMethod)] + private static int Vp8_Sse16xN_Sse2(Span a, Span b, int numPairs) + { + Vector128 sum = Vector128.Zero; + int offset = 0; + for (int i = 0; i < numPairs; i++) + { + // Load values. + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + Vector128 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset)); + Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset)); + Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps)); + Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps)); + + Vector128 sum1 = SubtractAndAccumulate(a0, b0); + Vector128 sum2 = SubtractAndAccumulate(a1, b1); + sum = Sse2.Add(sum, Sse2.Add(sum1, sum2)); + + offset += 2 * WebpConstants.Bps; + } + + return Numerics.ReduceSum(sum); + } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector128 SubtractAndAccumulate(Vector128 a, Vector128 b) + { + // Take abs(a-b) in 8b. + Vector128 ab = Sse2.SubtractSaturate(a, b); + Vector128 ba = Sse2.SubtractSaturate(b, a); + Vector128 absAb = Sse2.Or(ab, ba); + + // Zero-extend to 16b. + Vector128 c0 = Sse2.UnpackLow(absAb, Vector128.Zero); + Vector128 c1 = Sse2.UnpackHigh(absAb, Vector128.Zero); + + // Multiply with self. + Vector128 sum1 = Sse2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); + Vector128 sum2 = Sse2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); + + return Sse2.Add(sum1, sum2); + } +#endif + [MethodImpl(InliningOptions.ShortMethod)] public static void Vp8Copy4X4(Span src, Span dst) => Copy(src, dst, 4, 4); From 1e2278b13ce9da5bebda9b261aa5f50c1c2b5531 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Dec 2021 13:36:52 +0100 Subject: [PATCH 193/228] Add tests for Sse16X16 and Sse16X8 --- .../Formats/WebP/LossyUtilsTests.cs | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index aaffac443..9625008ca 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -76,6 +76,124 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.True(expected.SequenceEqual(dst)); } + private static void RunVp8Sse16X16Test() + { + // arrange + byte[] a = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, + 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, + 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, + 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, + 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, + 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, + 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, + 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, + 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, + 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, + 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, + 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, + 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + }; + + byte[] b = + { + 150, 150, 150, 150, 146, 149, 152, 154, 164, 166, 154, 132, 99, 92, 106, 112, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, + 161, 164, 151, 130, 93, 86, 100, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, 93, 86, 100, 106, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 150, 150, 150, 150, + 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 148, 148, 148, 148, 149, 158, 162, 159, 155, 155, 153, 129, + 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 154, 154, 154, 156, 161, 159, 152, + 155, 155, 153, 129, 94, 87, 101, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, 94, 87, 101, 106, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 153, 157, 162, + 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 152, 156, 158, 157, 140, 137, 145, 159, 155, 160, 150, 131, + 89, 88, 102, 101, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 153, 161, 160, 149, 118, 128, 147, 162, 155, 160, 150, 131, 86, 85, 99, 98, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 154, 165, 161, 144, 96, 128, 154, 159, 155, + 160, 150, 131, 83, 82, 97, 96, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 161, 160, 149, 105, 78, 127, 156, 170, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 160, 160, 133, 85, 81, 129, 155, + 167, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 156, 147, 109, 76, 85, 130, 153, 163, 156, 156, 154, 130, 81, 77, 95, 102, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 152, 128, 87, 83, + 88, 132, 152, 159, 156, 156, 154, 130, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204 + }; + + int expected = 2063; + + // act + int actual = LossyUtils.Vp8_Sse16X16(a, b); + + // assert + Assert.Equal(expected, actual); + } + + private static void RunVp8Sse16X8Test() + { + // arrange + byte[] a = + { + 107, 104, 104, 103, 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, + 147, 147, 146, 159, 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, + 172, 172, 172, 168, 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, + 93, 90, 102, 107, 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, + 150, 149, 152, 151, 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, + 102, 102, 121, 117, 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, + 154, 154, 151, 132, 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, + 171, 178, 172, 176, 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, + 102, 100, 107, 100, 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, + 160, 162, 161, 153, 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, + 171, 179, 178, 172, 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, + 86, 86, 102, 105, 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, + 154, 152, 158, 163, 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105 + }; + + byte[] b = + { + 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, + 146, 149, 152, 154, 161, 164, 151, 130, 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, + 171, 171, 171, 171, 171, 177, 169, 175, 150, 150, 150, 150, 146, 149, 152, 154, 158, 161, 148, 127, + 93, 86, 100, 106, 103, 103, 103, 103, 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, + 150, 150, 150, 150, 146, 149, 152, 154, 156, 159, 146, 125, 99, 92, 106, 112, 103, 103, 103, 103, + 101, 106, 122, 114, 171, 171, 171, 171, 171, 177, 169, 175, 148, 148, 148, 148, 149, 158, 162, 159, + 155, 155, 153, 129, 94, 87, 101, 106, 102, 100, 100, 102, 100, 101, 120, 122, 170, 176, 176, 170, + 174, 180, 171, 177, 151, 151, 151, 151, 152, 159, 161, 156, 155, 155, 153, 129, 94, 87, 101, 106, + 102, 105, 105, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, 154, 154, 154, 154, + 156, 161, 159, 152, 155, 155, 153, 129, 94, 87, 101, 106, 102, 112, 112, 102, 100, 101, 120, 122, + 170, 176, 176, 170, 174, 180, 171, 177, 156, 156, 156, 156, 159, 162, 158, 149, 155, 155, 153, 129, + 94, 87, 101, 106, 102, 117, 117, 102, 100, 101, 120, 122, 170, 176, 176, 170, 174, 180, 171, 177, + 152, 153, 157, 162, 150, 149, 149, 151, 155, 160, 150, 131, 91, 90, 104, 104 + }; + + int expected = 749; + + // act + int actual = LossyUtils.Vp8_Sse16X8(a, b); + + // assert + Assert.Equal(expected, actual); + } + private static void RunVp8Sse4X4Test() { // arrange @@ -168,6 +286,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void RunTransformOne_Works() => RunTransformOneTest(); + [Fact] + public void Vp8Sse16X16_Works() => RunVp8Sse16X16Test(); + + [Fact] + public void Vp8Sse16X8_Works() => RunVp8Sse16X8Test(); + [Fact] public void Vp8Sse4X4_Works() => RunVp8Sse4X4Test(); @@ -190,6 +314,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void TransformOne_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformOneTest, HwIntrinsics.DisableHWIntrinsic); + [Fact] + public void Vp8Sse16X16_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.AllowAll); + + [Fact] + public void Vp8Sse16X16_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableSSE2); + + [Fact] + public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll); + + [Fact] + public void Vp8Sse16X8_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableSSE2); + [Fact] public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); From 3d00c68a721383aca8155f3d73ad0bc7b0613044 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Dec 2021 14:03:48 +0100 Subject: [PATCH 194/228] Add AVX2 version of Vp8_Sse16X16 and Vp8_Sse16X8 --- .../Formats/Webp/Lossy/LossyUtils.cs | 62 +++++++++++++++++++ .../Formats/WebP/LossyUtilsTests.cs | 6 ++ 2 files changed, 68 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 69ce6c7f9..abaf72089 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -43,6 +43,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static int Vp8_Sse16X16(Span a, Span b) { #if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + return Vp8_Sse16xN_Avx2(a, b, 4); + } + if (Sse2.IsSupported) { return Vp8_Sse16xN_Sse2(a, b, 8); @@ -58,6 +63,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static int Vp8_Sse16X8(Span a, Span b) { #if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + return Vp8_Sse16xN_Avx2(a, b, 2); + } + if (Sse2.IsSupported) { return Vp8_Sse16xN_Sse2(a, b, 4); @@ -194,6 +204,39 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Numerics.ReduceSum(sum); } + [MethodImpl(InliningOptions.ShortMethod)] + private static int Vp8_Sse16xN_Avx2(Span a, Span b, int numPairs) + { + Vector256 sum = Vector256.Zero; + int offset = 0; + for (int i = 0; i < numPairs; i++) + { + // Load values. + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); + var a0 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, offset)), + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps))); + var b0 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, offset)), + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + WebpConstants.Bps))); + var a1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (2 * WebpConstants.Bps))), + Unsafe.As>(ref Unsafe.Add(ref aRef, offset + (3 * WebpConstants.Bps)))); + var b1 = Vector256.Create( + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (2 * WebpConstants.Bps))), + Unsafe.As>(ref Unsafe.Add(ref bRef, offset + (3 * WebpConstants.Bps)))); + + Vector256 sum1 = SubtractAndAccumulate(a0, b0); + Vector256 sum2 = SubtractAndAccumulate(a1, b1); + sum = Avx2.Add(sum, Avx2.Add(sum1, sum2)); + + offset += 4 * WebpConstants.Bps; + } + + return Numerics.ReduceSum(sum); + } + [MethodImpl(InliningOptions.ShortMethod)] private static Vector128 SubtractAndAccumulate(Vector128 a, Vector128 b) { @@ -212,6 +255,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return Sse2.Add(sum1, sum2); } + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector256 SubtractAndAccumulate(Vector256 a, Vector256 b) + { + // Take abs(a-b) in 8b. + Vector256 ab = Avx2.SubtractSaturate(a, b); + Vector256 ba = Avx2.SubtractSaturate(b, a); + Vector256 absAb = Avx2.Or(ab, ba); + + // Zero-extend to 16b. + Vector256 c0 = Avx2.UnpackLow(absAb, Vector256.Zero); + Vector256 c1 = Avx2.UnpackHigh(absAb, Vector256.Zero); + + // Multiply with self. + Vector256 sum1 = Avx2.MultiplyAddAdjacent(c0.AsInt16(), c0.AsInt16()); + Vector256 sum2 = Avx2.MultiplyAddAdjacent(c1.AsInt16(), c1.AsInt16()); + + return Avx2.Add(sum1, sum2); + } #endif [MethodImpl(InliningOptions.ShortMethod)] diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 9625008ca..cc5f1b4c2 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -320,12 +320,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void Vp8Sse16X16_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Vp8Sse16X16_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X16Test, HwIntrinsics.DisableAVX2); + [Fact] public void Vp8Sse16X8_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.AllowAll); [Fact] public void Vp8Sse16X8_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableSSE2); + [Fact] + public void Vp8Sse16X8_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse16X8Test, HwIntrinsics.DisableAVX2); + [Fact] public void Vp8Sse4X4_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVp8Sse4X4Test, HwIntrinsics.AllowAll); From 2a48c941afb70b8d33f186171129e8fa4beaf625 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Dec 2021 20:01:50 +0100 Subject: [PATCH 195/228] Add AVX2 version of CollectHistogram --- .../Formats/Webp/Lossy/Vp8Histogram.cs | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs index d384302b9..f679fcb13 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs @@ -3,6 +3,11 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Formats.Webp.Lossy { @@ -19,6 +24,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// private const int MaxCoeffThresh = 31; +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector256 MaxCoeffThreshVec = Vector256.Create((short)MaxCoeffThresh); +#endif + private int maxValue; private int lastNonZero; @@ -52,11 +61,38 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vp8Encoding.FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), this.output, this.scratch); // Convert coefficients to bin. - for (int k = 0; k < 16; ++k) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) { - int v = Math.Abs(this.output[k]) >> 3; - int clippedValue = ClipMax(v, MaxCoeffThresh); - ++this.distribution[clippedValue]; + // Load. + ref short outputRef = ref MemoryMarshal.GetReference(this.output); + Vector256 out0 = Unsafe.As>(ref outputRef); + + // v = abs(out) >> 3 + Vector256 abs0 = Avx2.Abs(out0.AsInt16()); + Vector256 v0 = Avx2.ShiftRightArithmetic(abs0.AsInt16(), 3); + + // bin = min(v, MAX_COEFF_THRESH) + Vector256 min0 = Avx2.Min(v0, MaxCoeffThreshVec); + + // Store. + Unsafe.As>(ref outputRef) = min0; + + // Convert coefficients to bin. + for (int k = 0; k < 16; ++k) + { + ++this.distribution[this.output[k]]; + } + } + else +#endif + { + for (int k = 0; k < 16; ++k) + { + int v = Math.Abs(this.output[k]) >> 3; + int clippedValue = ClipMax(v, MaxCoeffThresh); + ++this.distribution[clippedValue]; + } } } From 5f2b207d15403d57066c338f30fb8973d8e76bd3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 8 Dec 2021 20:40:03 +0100 Subject: [PATCH 196/228] Add collect histogram tests --- .../Formats/WebP/Vp8HistogramTests.cs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs index 4ff42f4ee..693626755 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8HistogramTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using SixLabors.ImageSharp.Formats.Webp.Lossy; +using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Webp @@ -67,6 +68,108 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + private static void RunCollectHistogramTest() + { + // arrange + var histogram = new Vp8Histogram(); + + byte[] reference = + { + 154, 154, 151, 151, 149, 148, 151, 157, 163, 163, 154, 132, 102, 98, 104, 108, 107, 104, 104, 103, + 101, 106, 123, 119, 170, 171, 172, 171, 168, 175, 171, 173, 151, 151, 149, 150, 147, 147, 146, 159, + 164, 165, 154, 129, 92, 90, 101, 105, 104, 103, 104, 101, 100, 105, 123, 117, 172, 172, 172, 168, + 170, 177, 170, 175, 151, 149, 150, 150, 147, 147, 156, 161, 161, 161, 151, 126, 93, 90, 102, 107, + 104, 103, 104, 101, 104, 104, 122, 117, 172, 172, 170, 168, 170, 177, 172, 175, 150, 149, 152, 151, + 148, 151, 160, 159, 157, 157, 148, 133, 96, 90, 103, 107, 104, 104, 101, 100, 102, 102, 121, 117, + 170, 170, 169, 171, 171, 179, 173, 175, 149, 151, 152, 151, 148, 154, 162, 157, 154, 154, 151, 132, + 92, 89, 101, 108, 104, 102, 101, 101, 103, 103, 123, 118, 171, 168, 177, 173, 171, 178, 172, 176, + 152, 152, 152, 151, 154, 162, 161, 155, 149, 157, 156, 129, 92, 87, 101, 107, 102, 100, 107, 100, + 101, 102, 123, 118, 170, 175, 182, 172, 171, 179, 173, 175, 152, 151, 154, 155, 160, 162, 161, 153, + 150, 156, 153, 129, 92, 91, 102, 106, 100, 109, 115, 99, 101, 102, 124, 120, 171, 179, 178, 172, + 171, 181, 171, 173, 154, 154, 154, 162, 160, 158, 156, 152, 153, 157, 151, 128, 86, 86, 102, 105, + 102, 122, 114, 99, 101, 102, 125, 120, 178, 173, 177, 172, 171, 180, 172, 173, 154, 152, 158, 163, + 150, 148, 148, 156, 151, 158, 152, 129, 87, 87, 101, 105, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 154, 151, 165, 156, 141, 137, 146, 158, 152, 159, 152, 133, + 90, 88, 99, 106, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 154, 160, 164, 150, 126, 127, 149, 159, 155, 161, 153, 131, 84, 86, 97, 103, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 157, 167, 157, 137, 102, 128, 155, 161, + 157, 159, 154, 134, 84, 82, 97, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 163, 163, 150, 113, 78, 132, 156, 162, 159, 160, 154, 132, 83, 78, 91, 97, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 163, 157, 137, 80, 78, + 131, 154, 163, 157, 159, 149, 131, 82, 77, 94, 100, 204, 204, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 159, 151, 108, 72, 88, 132, 156, 162, 159, 157, 151, 130, 79, 78, + 95, 102, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 151, 130, + 82, 82, 89, 134, 154, 161, 161, 157, 152, 129, 81, 77, 95, 102, 204, 204, 204, 204, 204, 204, 204, + 204, 204, 204, 204, 204, 204, 204, 204, 204 + }; + byte[] pred = + { + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129 + }; + int expectedAlpha = 146; + + // act + histogram.CollectHistogram(reference, pred, 0, 10); + int actualAlpha = histogram.GetAlpha(); + + // assert + Assert.Equal(expectedAlpha, actualAlpha); + } + + [Fact] + public void RunCollectHistogramTest_Works() => RunCollectHistogramTest(); + [Fact] public void GetAlpha_WithEmptyHistogram_Works() { @@ -111,5 +214,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp // assert Assert.Equal(1054, alpha); } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void CollectHistogramTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.AllowAll); + + [Fact] + public void CollectHistogramTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectHistogramTest, HwIntrinsics.DisableHWIntrinsic); +#endif } } From f6301d4b679cc6fa7319858a2a7a2238f45cd6a5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 8 Dec 2021 21:45:04 +0100 Subject: [PATCH 197/228] Refactor Pbm option enums --- src/ImageSharp/Formats/Pbm/BinaryDecoder.cs | 12 ++--- src/ImageSharp/Formats/Pbm/BinaryEncoder.cs | 8 +-- .../Formats/Pbm/IPbmEncoderOptions.cs | 4 +- .../Formats/Pbm/PbmComponentType.cs | 26 ++++++++++ src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 34 +++++++++---- src/ImageSharp/Formats/Pbm/PbmEncoder.cs | 4 +- src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs | 16 +++--- src/ImageSharp/Formats/Pbm/PbmMetadata.cs | 9 ++-- src/ImageSharp/Formats/Pbm/PlainDecoder.cs | 8 +-- src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 8 +-- .../Formats/Pbm/PbmDecoderTests.cs | 50 ++++++++++--------- .../Formats/Pbm/PbmMetadataTests.cs | 20 ++++---- 12 files changed, 123 insertions(+), 76 deletions(-) create mode 100644 src/ImageSharp/Formats/Pbm/PbmComponentType.cs diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs index 2a171456a..a469ced8a 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -14,8 +14,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// internal class BinaryDecoder { - private static L8 white = new L8(255); - private static L8 black = new L8(0); + private static L8 white = new(255); + private static L8 black = new(0); /// /// Decode the specified pixels. @@ -25,16 +25,16 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// The pixel array to encode into. /// The stream to read the data from. /// The ColorType to decode. - /// The maximum expected pixel value + /// Data type of the pixles components. /// /// Thrown if an invalid combination of setting is requested. /// - public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, int maxPixelValue) + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) where TPixel : unmanaged, IPixel { if (colorType == PbmColorType.Grayscale) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { ProcessGrayscale(configuration, pixels, stream); } @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm } else if (colorType == PbmColorType.Rgb) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { ProcessRgb(configuration, pixels, stream); } diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs index 626026726..8b32c18c2 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs @@ -22,16 +22,16 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// The bytestream to write to. /// The input image. /// The ColorType to use. - /// The maximum expected pixel value + /// Data type of the pixles components. /// /// Thrown if an invalid combination of setting is requested. /// - public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, int maxPixelValue) + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) where TPixel : unmanaged, IPixel { if (colorType == PbmColorType.Grayscale) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { WriteGrayscale(configuration, stream, image); } @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm } else if (colorType == PbmColorType.Rgb) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { WriteRgb(configuration, stream, image); } diff --git a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs index c5c409ec8..988d9e560 100644 --- a/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs +++ b/src/ImageSharp/Formats/Pbm/IPbmEncoderOptions.cs @@ -19,8 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm PbmColorType? ColorType { get; } /// - /// Gets the maximum pixel value, per component. + /// Gets the Data Type of the pixel components. /// - int? MaxPixelValue { get; } + PbmComponentType? ComponentType { get; } } } diff --git a/src/ImageSharp/Formats/Pbm/PbmComponentType.cs b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs new file mode 100644 index 000000000..26272021c --- /dev/null +++ b/src/ImageSharp/Formats/Pbm/PbmComponentType.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Pbm +{ + /// + /// The data type of the components of the pixels. + /// + public enum PbmComponentType : byte + { + /// + /// Single bit per pixel, exclusively for . + /// + Bit = 0, + + /// + /// 8 bits unsigned integer per component. + /// + Byte = 1, + + /// + /// 16 bits unsigned integer per component. + /// + Short = 2 + } +} diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 8bac0bfd1..749fc0292 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// internal sealed class PbmDecoderCore : IImageDecoderInternals { + private int maxPixelValue; + /// /// Initializes a new instance of the class. /// @@ -36,9 +38,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm public Size PixelSize { get; private set; } /// - /// Gets the maximum pixel value + /// Gets the component data type /// - public int MaxPixelValue { get; private set; } + public PbmComponentType ComponentType { get; private set; } /// /// Gets the Encoding of pixels @@ -53,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// Size IImageDecoderInternals.Dimensions => this.PixelSize; - private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.MaxPixelValue is not 255 and not 65535; + private bool NeedsUpscaling => this.ColorType != PbmColorType.BlackAndWhite && this.maxPixelValue is not 255 and not 65535; /// public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) @@ -79,7 +81,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm { this.ProcessHeader(stream); - int bitsPerPixel = this.MaxPixelValue > 255 ? 16 : 8; + // BlackAndWhite pixels are encoded into a byte. + int bitsPerPixel = this.ComponentType == PbmComponentType.Short ? 16 : 8; return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.PixelSize.Width, this.PixelSize.Height, this.Metadata); } @@ -143,12 +146,21 @@ namespace SixLabors.ImageSharp.Formats.Pbm stream.SkipWhitespaceAndComments(); if (this.ColorType != PbmColorType.BlackAndWhite) { - this.MaxPixelValue = stream.ReadDecimal(); + this.maxPixelValue = stream.ReadDecimal(); + if (this.maxPixelValue > 255) + { + this.ComponentType = PbmComponentType.Short; + } + else + { + this.ComponentType = PbmComponentType.Byte; + } + stream.SkipWhitespaceAndComments(); } else { - this.MaxPixelValue = 1; + this.ComponentType = PbmComponentType.Bit; } this.PixelSize = new Size(width, height); @@ -156,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm PbmMetadata meta = this.Metadata.GetPbmMetadata(); meta.Encoding = this.Encoding; meta.ColorType = this.ColorType; - meta.MaxPixelValue = this.MaxPixelValue; + meta.ComponentType = this.ComponentType; } private void ProcessPixels(BufferedReadStream stream, Buffer2D pixels) @@ -164,19 +176,19 @@ namespace SixLabors.ImageSharp.Formats.Pbm { if (this.Encoding == PbmEncoding.Binary) { - BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.MaxPixelValue); + BinaryDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); } else { - PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.MaxPixelValue); + PlainDecoder.Process(this.Configuration, pixels, stream, this.ColorType, this.ComponentType); } } private void ProcessUpscaling(Image image) where TPixel : unmanaged, IPixel { - int maxAllocationValue = (this.MaxPixelValue > 255) ? 65535 : 255; - float factor = maxAllocationValue / this.MaxPixelValue; + int maxAllocationValue = this.ComponentType == PbmComponentType.Short ? 65535 : 255; + float factor = maxAllocationValue / this.maxPixelValue; image.Mutate(x => x.Brightness(factor)); } } diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs index 7984dddac..75d666063 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoder.cs @@ -46,9 +46,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm public PbmColorType? ColorType { get; set; } /// - /// Gets or sets the maximum pixel value, per component. + /// Gets or sets the data type of the pixels components. /// - public int? MaxPixelValue { get; set; } + public PbmComponentType? ComponentType { get; set; } /// public void Encode(Image image, Stream stream) diff --git a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs index 158786e3c..9d1f39edf 100644 --- a/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmEncoderCore.cs @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// Gets the maximum pixel value, per component. /// - private int maxPixelValue; + private PbmComponentType componentType; /// /// Initializes a new instance of the class. @@ -87,8 +87,11 @@ namespace SixLabors.ImageSharp.Formats.Pbm this.colorType = this.options.ColorType ?? metadata.ColorType; if (this.colorType != PbmColorType.BlackAndWhite) { - this.maxPixelValue = this.options.MaxPixelValue ?? metadata.MaxPixelValue; - this.maxPixelValue = Math.Max(this.maxPixelValue, PbmConstants.MaxLength); + this.componentType = this.options.ComponentType ?? metadata.ComponentType; + } + else + { + this.componentType = PbmComponentType.Bit; } } @@ -151,7 +154,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm if (this.colorType != PbmColorType.BlackAndWhite) { - Utf8Formatter.TryFormat(this.maxPixelValue, buffer.Slice(written), out bytesWritten); + int maxPixelValue = this.componentType == PbmComponentType.Short ? 65535 : 255; + Utf8Formatter.TryFormat(maxPixelValue, buffer.Slice(written), out bytesWritten); written += bytesWritten; buffer[written++] = NewLine; } @@ -172,11 +176,11 @@ namespace SixLabors.ImageSharp.Formats.Pbm { if (this.encoding == PbmEncoding.Plain) { - PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.maxPixelValue); + PlainEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); } else { - BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.maxPixelValue); + BinaryEncoder.WritePixels(this.configuration, stream, image, this.colorType, this.componentType); } } } diff --git a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs index 869b1b06d..a00ae46de 100644 --- a/src/ImageSharp/Formats/Pbm/PbmMetadata.cs +++ b/src/ImageSharp/Formats/Pbm/PbmMetadata.cs @@ -11,7 +11,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// /// Initializes a new instance of the class. /// - public PbmMetadata() => this.MaxPixelValue = this.ColorType == PbmColorType.BlackAndWhite ? 1 : 255; + public PbmMetadata() => + this.ComponentType = this.ColorType == PbmColorType.BlackAndWhite ? PbmComponentType.Bit : PbmComponentType.Byte; /// /// Initializes a new instance of the class. @@ -21,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { this.Encoding = other.Encoding; this.ColorType = other.ColorType; - this.MaxPixelValue = other.MaxPixelValue; + this.ComponentType = other.ComponentType; } /// @@ -35,9 +36,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm public PbmColorType ColorType { get; set; } = PbmColorType.Grayscale; /// - /// Gets or sets the maximum pixel value. + /// Gets or sets the data type of the pixel components. /// - public int MaxPixelValue { get; set; } + public PbmComponentType ComponentType { get; set; } /// public IDeepCloneable DeepClone() => new PbmMetadata(this); diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs index 4521f9b64..a9e90d788 100644 --- a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -25,13 +25,13 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// The pixel array to encode into. /// The stream to read the data from. /// The ColorType to decode. - /// The maximum expected pixel value - public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, int maxPixelValue) + /// Data type of the pixles components. + public static void Process(Configuration configuration, Buffer2D pixels, BufferedReadStream stream, PbmColorType colorType, PbmComponentType componentType) where TPixel : unmanaged, IPixel { if (colorType == PbmColorType.Grayscale) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { ProcessGrayscale(configuration, pixels, stream); } @@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm } else if (colorType == PbmColorType.Rgb) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { ProcessRgb(configuration, pixels, stream); } diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index 8d534b7a9..75e6f56e9 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -36,13 +36,13 @@ namespace SixLabors.ImageSharp.Formats.Pbm /// The bytestream to write to. /// The input image. /// The ColorType to use. - /// The maximum expected pixel value - public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, int maxPixelValue) + /// Data type of the pixles components. + public static void WritePixels(Configuration configuration, Stream stream, ImageFrame image, PbmColorType colorType, PbmComponentType componentType) where TPixel : unmanaged, IPixel { if (colorType == PbmColorType.Grayscale) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { WriteGrayscale(configuration, stream, image); } @@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm } else if (colorType == PbmColorType.Rgb) { - if (maxPixelValue < 256) + if (componentType == PbmComponentType.Byte) { WriteRgb(configuration, stream, image); } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs index 51bf61d23..97237bca5 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs @@ -14,16 +14,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm public class PbmDecoderTests { [Theory] - [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite)] - [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite)] - [InlineData(GrayscalePlain, PbmColorType.Grayscale)] - [InlineData(GrayscalePlainMagick, PbmColorType.Grayscale)] - [InlineData(GrayscaleBinary, PbmColorType.Grayscale)] - [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale)] - [InlineData(RgbPlain, PbmColorType.Rgb)] - [InlineData(RgbPlainMagick, PbmColorType.Rgb)] - [InlineData(RgbBinary, PbmColorType.Rgb)] - public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType) + [InlineData(BlackAndWhitePlain, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] + [InlineData(BlackAndWhiteBinary, PbmColorType.BlackAndWhite, PbmComponentType.Bit)] + [InlineData(GrayscalePlain, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscalePlainMagick, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscaleBinary, PbmColorType.Grayscale, PbmComponentType.Byte)] + [InlineData(GrayscaleBinaryWide, PbmColorType.Grayscale, PbmComponentType.Short)] + [InlineData(RgbPlain, PbmColorType.Rgb, PbmComponentType.Byte)] + [InlineData(RgbPlainMagick, PbmColorType.Rgb, PbmComponentType.Byte)] + [InlineData(RgbBinary, PbmColorType.Rgb, PbmComponentType.Byte)] + public void ImageLoadCanDecode(string imagePath, PbmColorType expectedColorType, PbmComponentType expectedComponentType) { // Arrange var testFile = TestFile.Create(imagePath); @@ -34,9 +34,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm // Assert Assert.NotNull(image); - PbmMetadata bitmapMetadata = image.Metadata.GetPbmMetadata(); - Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedColorType, bitmapMetadata.ColorType); + PbmMetadata metadata = image.Metadata.GetPbmMetadata(); + Assert.NotNull(metadata); + Assert.Equal(expectedColorType, metadata.ColorType); + Assert.Equal(expectedComponentType, metadata.ComponentType); } [Theory] @@ -77,21 +78,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm } [Theory] - [WithFile(BlackAndWhitePlain, PixelTypes.L8, true)] - [WithFile(BlackAndWhiteBinary, PixelTypes.L8, true)] - [WithFile(GrayscalePlain, PixelTypes.L8, true)] - [WithFile(GrayscalePlainNormalized, PixelTypes.L8, true)] - [WithFile(GrayscaleBinary, PixelTypes.L8, true)] - [WithFile(GrayscaleBinaryWide, PixelTypes.L16, true)] - [WithFile(RgbPlain, PixelTypes.Rgb24, false)] - [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, false)] - [WithFile(RgbBinary, PixelTypes.Rgb24, false)] - public void DecodeReferenceImage(TestImageProvider provider, bool isGrayscale) + [WithFile(BlackAndWhitePlain, PixelTypes.L8, "pbm")] + [WithFile(BlackAndWhiteBinary, PixelTypes.L8, "pbm")] + [WithFile(GrayscalePlain, PixelTypes.L8, "pgm")] + [WithFile(GrayscalePlainNormalized, PixelTypes.L8, "pgm")] + [WithFile(GrayscaleBinary, PixelTypes.L8, "pgm")] + [WithFile(GrayscaleBinaryWide, PixelTypes.L16, "pgm")] + [WithFile(RgbPlain, PixelTypes.Rgb24, "ppm")] + [WithFile(RgbPlainNormalized, PixelTypes.Rgb24, "ppm")] + [WithFile(RgbBinary, PixelTypes.Rgb24, "ppm")] + public void DecodeReferenceImage(TestImageProvider provider, string extension) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); - image.DebugSave(provider); + image.DebugSave(provider, extension: extension); + bool isGrayscale = extension is "pgm" or "pbm"; image.CompareToReferenceOutput(provider, grayscale: isGrayscale); } } diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs index 00b4f443d..7915d224a 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs @@ -20,8 +20,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm var clone = (PbmMetadata)meta.DeepClone(); clone.ColorType = PbmColorType.Rgb; + clone.ComponentType = PbmComponentType.Short; Assert.False(meta.ColorType.Equals(clone.ColorType)); + Assert.False(meta.ComponentType.Equals(clone.ComponentType)); } [Theory] @@ -63,14 +65,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm } [Theory] - [InlineData(BlackAndWhitePlain, 1)] - [InlineData(BlackAndWhiteBinary, 1)] - [InlineData(GrayscaleBinary, 255)] - [InlineData(GrayscaleBinaryWide, 65535)] - [InlineData(GrayscalePlain, 15)] - [InlineData(RgbBinary, 255)] - [InlineData(RgbPlain, 15)] - public void Identify_DetectsCorrectMaxPixelValue(string imagePath, int expectedMaxPixelValue) + [InlineData(BlackAndWhitePlain, PbmComponentType.Bit)] + [InlineData(BlackAndWhiteBinary, PbmComponentType.Bit)] + [InlineData(GrayscaleBinary, PbmComponentType.Byte)] + [InlineData(GrayscaleBinaryWide, PbmComponentType.Short)] + [InlineData(GrayscalePlain, PbmComponentType.Byte)] + [InlineData(RgbBinary, PbmComponentType.Byte)] + [InlineData(RgbPlain, PbmComponentType.Byte)] + public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentType expectedComponentType) { var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); @@ -78,7 +80,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm Assert.NotNull(imageInfo); PbmMetadata bitmapMetadata = imageInfo.Metadata.GetPbmMetadata(); Assert.NotNull(bitmapMetadata); - Assert.Equal(expectedMaxPixelValue, bitmapMetadata.MaxPixelValue); + Assert.Equal(expectedComponentType, bitmapMetadata.ComponentType); } } } From 1e24da4bef7fb43918071e1c9b31c1a7e9bda619 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 9 Dec 2021 11:16:57 +0100 Subject: [PATCH 198/228] improve test naming --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index cc6632af1..80c8cc6a0 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [InlineData(300)] // Group of single SharedArrayPoolBuffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers - public void MemoryGroupFinalizer_ReturnsToPool(int length) + public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) { if (!TestEnvironment.RunsOnCI) { @@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators [Theory] [InlineData(300)] // Group of single SharedArrayPoolBuffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer - public void MemoryOwnerFinalizer_ReturnsToPool(int length) + public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) { if (!TestEnvironment.RunsOnCI) { From 1ae1d8ed6675f4b5d1812b892461edc5b977025e Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Thu, 9 Dec 2021 14:19:52 +0100 Subject: [PATCH 199/228] Use nint for offset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index abaf72089..1b7e5633f 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static int Vp8_Sse16xN_Sse2(Span a, Span b, int numPairs) { Vector128 sum = Vector128.Zero; - int offset = 0; + nint offset = 0; for (int i = 0; i < numPairs; i++) { // Load values. @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static int Vp8_Sse16xN_Avx2(Span a, Span b, int numPairs) { Vector256 sum = Vector256.Zero; - int offset = 0; + nint offset = 0; for (int i = 0; i < numPairs; i++) { // Load values. From 8e1c3fa6bfab40d00d4edabf5147679ee3d1d3c3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 9 Dec 2021 14:23:46 +0100 Subject: [PATCH 200/228] Move GetReference outside of the loop --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 1b7e5633f..8938ede0a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -184,11 +184,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { Vector128 sum = Vector128.Zero; nint offset = 0; + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); for (int i = 0; i < numPairs; i++) { // Load values. - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); Vector128 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset)); Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, offset)); Vector128 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps)); @@ -209,11 +209,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { Vector256 sum = Vector256.Zero; nint offset = 0; + ref byte aRef = ref MemoryMarshal.GetReference(a); + ref byte bRef = ref MemoryMarshal.GetReference(b); for (int i = 0; i < numPairs; i++) { // Load values. - ref byte aRef = ref MemoryMarshal.GetReference(a); - ref byte bRef = ref MemoryMarshal.GetReference(b); var a0 = Vector256.Create( Unsafe.As>(ref Unsafe.Add(ref aRef, offset)), Unsafe.As>(ref Unsafe.Add(ref aRef, offset + WebpConstants.Bps))); From dd051e2248bc67880981fa48194a90a08add5eff Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 9 Dec 2021 20:03:05 +0100 Subject: [PATCH 201/228] Remove RunSerial from tests --- .../Formats/Bmp/BmpDecoderTests.cs | 1 - .../Formats/Bmp/BmpEncoderTests.cs | 7 +++---- .../Formats/GeneralFormatTests.cs | 8 +++----- .../Formats/Gif/GifDecoderTests.cs | 3 +-- .../Formats/Gif/GifEncoderTests.cs | 3 +-- .../Formats/Gif/GifMetadataTests.cs | 5 ++--- .../Formats/Jpg/JpegDecoderTests.cs | 3 +-- .../Formats/Jpg/JpegEncoderTests.cs | 13 ++++++------- .../Formats/Png/PngDecoderTests.cs | 3 +-- .../Formats/Png/PngEncoderTests.cs | 19 +++++++++---------- .../Formats/Tga/TgaDecoderTests.cs | 3 +-- .../Formats/Tga/TgaEncoderTests.cs | 5 ++--- .../Formats/Tiff/TiffDecoderTests.cs | 5 ++--- .../Formats/Tiff/TiffEncoderTests.cs | 1 - .../Formats/Tiff/TiffMetadataTests.cs | 3 +-- .../Formats/WebP/WebpDecoderTests.cs | 1 - .../Formats/WebP/WebpEncoderTests.cs | 1 - 17 files changed, 33 insertions(+), 51 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 4e6dc36dc..f85bc78fc 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -19,7 +19,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index d645f0b60..073cf5fcf 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -17,19 +17,18 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Bmp { - [Collection("RunSerial")] [Trait("Format", "Bmp")] public class BmpEncoderTests { public static readonly TheoryData BitsPerPixel = - new TheoryData + new() { BmpBitsPerPixel.Pixel24, BmpBitsPerPixel.Pixel32 }; public static readonly TheoryData RatioFiles = - new TheoryData + new() { { Car, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, { V5Header, 3780, 3780, PixelResolutionUnit.PixelsPerMeter }, @@ -37,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp }; public static readonly TheoryData BmpBitsPerPixelFiles = - new TheoryData + new() { { Bit1, BmpBitsPerPixel.Pixel1 }, { Bit4, BmpBitsPerPixel.Pixel4 }, diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 26eff2650..73c332f66 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Png; -using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -16,7 +15,6 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats { - [Collection("RunSerial")] public class GeneralFormatTests { /// @@ -34,7 +32,7 @@ namespace SixLabors.ImageSharp.Tests.Formats /// /// The collection of image files to test against. /// - protected static readonly List Files = new List + protected static readonly List Files = new() { TestFile.Create(TestImages.Jpeg.Baseline.Calliphora), TestFile.Create(TestImages.Bmp.Car), @@ -85,8 +83,8 @@ namespace SixLabors.ImageSharp.Tests.Formats } public static readonly TheoryData QuantizerNames = - new TheoryData - { + new() + { nameof(KnownQuantizers.Octree), nameof(KnownQuantizers.WebSafe), nameof(KnownQuantizers.Werner), diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index 824ca535b..6bf606ac9 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -17,13 +17,12 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifDecoderTests { private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; - private static GifDecoder GifDecoder => new GifDecoder(); + private static GifDecoder GifDecoder => new(); public static readonly string[] MultiFrameTestFiles = { diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index 00d43b6ff..cb24de81b 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -13,7 +13,6 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifEncoderTests { @@ -21,7 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0015F); public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 59f7ebb74..5699b4741 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs @@ -12,12 +12,11 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Gif { - [Collection("RunSerial")] [Trait("Format", "Gif")] public class GifMetadataTests { public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Gif.Rings, (int)ImageMetadata.DefaultHorizontalResolution, (int)ImageMetadata.DefaultVerticalResolution, PixelResolutionUnit.PixelsPerInch }, { TestImages.Gif.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, @@ -25,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif }; public static readonly TheoryData RepeatFiles = - new TheoryData + new() { { TestImages.Gif.Cheers, 0 }, { TestImages.Gif.Receipt, 1 }, diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index dcdfc3e42..08d8d9038 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -21,8 +21,7 @@ using Xunit.Abstractions; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { // TODO: Scatter test cases into multiple test classes - [Collection("RunSerial")] - [Trait("Format", "Jpg")] + [Trait("Format", "Jpg")] public partial class JpegDecoderTests { public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 62952f537..18eae9fbd 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -19,23 +19,22 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Jpg { - [Collection("RunSerial")] [Trait("Format", "Jpg")] public class JpegEncoderTests { - private static JpegEncoder JpegEncoder => new JpegEncoder(); + private static JpegEncoder JpegEncoder => new(); - private static JpegDecoder JpegDecoder => new JpegDecoder(); + private static JpegDecoder JpegDecoder => new(); public static readonly TheoryData QualityFiles = - new TheoryData + new() { { TestImages.Jpeg.Baseline.Calliphora, 80 }, { TestImages.Jpeg.Progressive.Fb, 75 } }; public static readonly TheoryData BitsPerPixel_Quality = - new TheoryData + new() { { JpegColorType.YCbCrRatio420, 40 }, { JpegColorType.YCbCrRatio420, 60 }, @@ -49,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; public static readonly TheoryData Grayscale_Quality = - new TheoryData + new() { { 40 }, { 60 }, @@ -57,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg }; public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Jpeg.Baseline.Ratio1x1, 1, 1, PixelResolutionUnit.AspectRatio }, { TestImages.Jpeg.Baseline.Snake, 300, 300, PixelResolutionUnit.PixelsPerInch }, diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 9fc4d03dd..543a544d0 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -16,13 +16,12 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Png { - [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; - private static PngDecoder PngDecoder => new PngDecoder(); + private static PngDecoder PngDecoder => new(); public static readonly string[] CommonTestImages = { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 9e99dded8..3cc879d6b 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -15,21 +15,20 @@ using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Png { - [Collection("RunSerial")] [Trait("Format", "Png")] public partial class PngEncoderTests { - private static PngEncoder PngEncoder => new PngEncoder(); + private static PngEncoder PngEncoder => new(); public static readonly TheoryData PngBitDepthFiles = - new TheoryData + new() { { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 }, { TestImages.Png.Bpp1, PngBitDepth.Bit1 } }; public static readonly TheoryData PngTrnsFiles = - new TheoryData + new() { { TestImages.Png.Gray1BitTrans, PngBitDepth.Bit1, PngColorType.Grayscale }, { TestImages.Png.Gray2BitTrans, PngBitDepth.Bit2, PngColorType.Grayscale }, @@ -43,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// /// All types except Palette /// - public static readonly TheoryData PngColorTypes = new TheoryData + public static readonly TheoryData PngColorTypes = new() { PngColorType.RgbWithAlpha, PngColorType.Rgb, @@ -51,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png PngColorType.GrayscaleWithAlpha, }; - public static readonly TheoryData PngFilterMethods = new TheoryData + public static readonly TheoryData PngFilterMethods = new() { PngFilterMethod.None, PngFilterMethod.Sub, @@ -65,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png /// All types except Palette /// public static readonly TheoryData CompressionLevels - = new TheoryData + = new() { PngCompressionLevel.Level0, PngCompressionLevel.Level1, @@ -79,12 +78,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png PngCompressionLevel.Level9, }; - public static readonly TheoryData PaletteSizes = new TheoryData + public static readonly TheoryData PaletteSizes = new() { 30, 55, 100, 201, 255 }; - public static readonly TheoryData PaletteLargeOnly = new TheoryData + public static readonly TheoryData PaletteLargeOnly = new() { 80, 100, 120, 230 }; @@ -96,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png }; public static readonly TheoryData RatioFiles = - new TheoryData + new() { { TestImages.Png.Splash, 11810, 11810, PixelResolutionUnit.PixelsPerMeter }, { TestImages.Png.Ratio1x4, 1, 4, PixelResolutionUnit.AspectRatio }, diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index 1c53ff6a1..ffe7a048c 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -15,11 +15,10 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { - [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaDecoderTests { - private static TgaDecoder TgaDecoder => new TgaDecoder(); + private static TgaDecoder TgaDecoder => new(); [Theory] [WithFile(Gray8BitTopLeft, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 4c768a1a5..642f53c0b 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -11,19 +11,18 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Tga { - [Collection("RunSerial")] [Trait("Format", "Tga")] public class TgaEncoderTests { public static readonly TheoryData BitsPerPixel = - new TheoryData + new() { TgaBitsPerPixel.Pixel24, TgaBitsPerPixel.Pixel32 }; public static readonly TheoryData TgaBitsPerPixelFiles = - new TheoryData + new() { { Gray8BitBottomLeft, TgaBitsPerPixel.Pixel8 }, { Bit16BottomLeft, TgaBitsPerPixel.Pixel16 }, diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 4a13bbe62..b7bd5275d 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -15,15 +15,14 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { - [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffDecoderTests { public static readonly string[] MultiframeTestImages = Multiframes; - private static TiffDecoder TiffDecoder => new TiffDecoder(); + private static TiffDecoder TiffDecoder => new(); - private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + private static MagickReferenceDecoder ReferenceDecoder => new(); [Theory] [WithFile(RgbUncompressedTiled, PixelTypes.Rgba32)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index d85ed16a7..aded52cd9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -11,7 +11,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { - [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffEncoderTests : TiffEncoderBaseTester { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index cdd9616a7..64c029b44 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -16,11 +16,10 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { - [Collection("RunSerial")] [Trait("Format", "Tiff")] public class TiffMetadataTests { - private static TiffDecoder TiffDecoder => new TiffDecoder(); + private static TiffDecoder TiffDecoder => new(); [Fact] public void TiffMetadata_CloneIsDeep() diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 2cbeff2ce..22342e612 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -12,7 +12,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Webp; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Formats.Webp { - [Collection("RunSerial")] [Trait("Format", "Webp")] public class WebpDecoderTests { diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index ec0040a46..607d4c764 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -11,7 +11,6 @@ using static SixLabors.ImageSharp.Tests.TestImages.Webp; namespace SixLabors.ImageSharp.Tests.Formats.Webp { - [Collection("RunSerial")] [Trait("Format", "Webp")] public class WebpEncoderTests { From 749452503b6a7f638a1adf31b391a577569c4080 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 10 Dec 2021 13:25:14 +0100 Subject: [PATCH 202/228] Write EOF indicated for plain encoding --- src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 3 +++ tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index 75e6f56e9..2868922ea 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -66,6 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm { WriteBlackAndWhite(configuration, stream, image); } + + // Write EOF indicator, as some encoders expect it. + stream.WriteByte(Space); } private static void WriteGrayscale(Configuration configuration, Stream stream, ImageFrame image) diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs index 2ca49a39d..e9b496ce4 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmEncoderTests.cs @@ -72,7 +72,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Pbm using (var memStream = new MemoryStream()) { input.Save(memStream, options); - memStream.Position = 0; + + // EOF indicator for plain is a Space. + memStream.Seek(-1, SeekOrigin.End); + int lastByte = memStream.ReadByte(); + Assert.Equal(0x20, lastByte); + + memStream.Seek(0, SeekOrigin.Begin); using (var output = Image.Load(memStream)) { PbmMetadata meta = output.Metadata.GetPbmMetadata(); From b2bc25c89f2d825a9c4a7d8c38cfade34a93e890 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 10 Dec 2021 13:49:55 +0100 Subject: [PATCH 203/228] No need to await async PbmDecoder methods --- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index 62cef176d..c00e4affe 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -54,9 +54,8 @@ namespace SixLabors.ImageSharp.Formats.Pbm } /// - public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => await this.DecodeAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); + public Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => this.DecodeAsync(configuration, stream, cancellationToken); /// public IImageInfo Identify(Configuration configuration, Stream stream) @@ -68,16 +67,12 @@ namespace SixLabors.ImageSharp.Formats.Pbm } /// - public async Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + public Task IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(stream, nameof(stream)); - // The introduction of a local variable that refers to an object the implements - // IDisposable means you must use async/await, where the compiler generates the - // state machine and a continuation. var decoder = new PbmDecoderCore(configuration); - return await decoder.IdentifyAsync(configuration, stream, cancellationToken) - .ConfigureAwait(false); + return decoder.IdentifyAsync(configuration, stream, cancellationToken); } } } From efece702028311256169fd15196225fa172c8596 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 10 Dec 2021 14:05:59 +0100 Subject: [PATCH 204/228] Partial revert of b2bc25c --- src/ImageSharp/Formats/Pbm/PbmDecoder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs index c00e4affe..2eebbb1d9 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoder.cs @@ -54,8 +54,9 @@ namespace SixLabors.ImageSharp.Formats.Pbm } /// - public Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) - => this.DecodeAsync(configuration, stream, cancellationToken); + public async Task DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken) + => await this.DecodeAsync(configuration, stream, cancellationToken) + .ConfigureAwait(false); /// public IImageInfo Identify(Configuration configuration, Stream stream) From 3730a0259ad12432277e82e62d77cc799dee18f3 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 10 Dec 2021 14:34:51 +0100 Subject: [PATCH 205/228] Fix build after merge --- src/ImageSharp/Formats/Pbm/BinaryDecoder.cs | 10 +++++----- src/ImageSharp/Formats/Pbm/BinaryEncoder.cs | 15 ++++++++++----- src/ImageSharp/Formats/Pbm/PlainDecoder.cs | 10 +++++----- src/ImageSharp/Formats/Pbm/PlainEncoder.cs | 15 ++++++++++----- .../ImageComparison/ImageComparingUtils.cs | 4 ++-- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs index a469ced8a..33af30434 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryDecoder.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { stream.Read(rowSpan); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL8Bytes( configuration, rowSpan, @@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { stream.Read(rowSpan); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL16Bytes( configuration, rowSpan, @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { stream.Read(rowSpan); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromRgb24Bytes( configuration, rowSpan, @@ -139,7 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { stream.Read(rowSpan); - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromRgb48Bytes( configuration, rowSpan, @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm } } - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL8( configuration, rowSpan, diff --git a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs index 8b32c18c2..332ab9b50 100644 --- a/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/BinaryEncoder.cs @@ -62,13 +62,14 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8Bytes( configuration, @@ -86,13 +87,14 @@ namespace SixLabors.ImageSharp.Formats.Pbm const int bytesPerPixel = 2; int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToL16Bytes( configuration, @@ -110,13 +112,14 @@ namespace SixLabors.ImageSharp.Formats.Pbm const int bytesPerPixel = 3; int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgb24Bytes( configuration, @@ -134,13 +137,14 @@ namespace SixLabors.ImageSharp.Formats.Pbm const int bytesPerPixel = 6; int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width * bytesPerPixel); Span rowSpan = row.GetSpan(); for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgb48Bytes( configuration, @@ -157,6 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -165,7 +170,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm int startBit = 0; for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8( configuration, diff --git a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs index a9e90d788..aeb527dd2 100644 --- a/src/ImageSharp/Formats/Pbm/PlainDecoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainDecoder.cs @@ -75,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm rowSpan[x] = new L8(value); } - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL8( configuration, rowSpan, @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm rowSpan[x] = new L16(value); } - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL16( configuration, rowSpan, @@ -131,7 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm rowSpan[x] = new Rgb24(red, green, blue); } - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromRgb24( configuration, rowSpan, @@ -161,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm rowSpan[x] = new Rgb48(red, green, blue); } - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromRgb48( configuration, rowSpan, @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm rowSpan[x] = value == 0 ? White : Black; } - Span pixelSpan = pixels.GetRowSpan(y); + Span pixelSpan = pixels.DangerousGetRowSpan(y); PixelOperations.Instance.FromL8( configuration, rowSpan, diff --git a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs index 2868922ea..a64ae38a7 100644 --- a/src/ImageSharp/Formats/Pbm/PlainEncoder.cs +++ b/src/ImageSharp/Formats/Pbm/PlainEncoder.cs @@ -76,6 +76,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -84,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8( configuration, pixelSpan, @@ -108,6 +109,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -116,7 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToL16( configuration, pixelSpan, @@ -140,6 +142,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -148,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgb24( configuration, pixelSpan, @@ -178,6 +181,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -186,7 +190,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToRgb48( configuration, pixelSpan, @@ -216,6 +220,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm { int width = image.Width; int height = image.Height; + Buffer2D pixelBuffer = image.PixelBuffer; MemoryAllocator allocator = configuration.MemoryAllocator; using IMemoryOwner row = allocator.Allocate(width); Span rowSpan = row.GetSpan(); @@ -224,7 +229,7 @@ namespace SixLabors.ImageSharp.Formats.Pbm for (int y = 0; y < height; y++) { - Span pixelSpan = image.GetPixelRowSpan(y); + Span pixelSpan = pixelBuffer.DangerousGetRowSpan(y); PixelOperations.Instance.ToL8( configuration, pixelSpan, diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs index 4de1b9a19..1801d6b59 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs @@ -8,9 +8,9 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.Tga +namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison { - public static class TgaTestUtils + public static class ImageComparingUtils { public static void CompareWithReferenceDecoder( TestImageProvider provider, From c6f9050a5e06abdf815699f2f33885f71859b0dc Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 4 Dec 2021 20:55:41 +0100 Subject: [PATCH 206/228] Initial Abgr32 pixel format implementation --- shared-infrastructure | 2 +- src/ImageSharp/Advanced/AotCompilerTools.cs | 2 + src/ImageSharp/Color/Color.Conversions.cs | 24 ++ .../Helpers/Shuffle/IComponentShuffle.cs | 70 ++++ src/ImageSharp/ImageSharp.csproj | 9 + src/ImageSharp/PixelFormats/IPixel.cs | 6 + .../PixelFormats/PixelImplementations/A8.cs | 4 + .../PixelImplementations/Abgr32.cs | 394 ++++++++++++++++++ .../PixelImplementations/Argb32.cs | 10 + .../PixelImplementations/Bgr24.cs | 9 + .../PixelImplementations/Bgr565.cs | 4 + .../PixelImplementations/Bgra32.cs | 10 + .../PixelImplementations/Bgra4444.cs | 4 + .../PixelImplementations/Bgra5551.cs | 4 + .../PixelImplementations/Byte4.cs | 4 + .../PixelImplementations/HalfSingle.cs | 4 + .../PixelImplementations/HalfVector2.cs | 4 + .../PixelImplementations/HalfVector4.cs | 4 + .../PixelFormats/PixelImplementations/L16.cs | 7 + .../PixelFormats/PixelImplementations/L8.cs | 4 + .../PixelFormats/PixelImplementations/La16.cs | 8 + .../PixelFormats/PixelImplementations/La32.cs | 12 + .../PixelImplementations/NormalizedByte2.cs | 4 + .../PixelImplementations/NormalizedByte4.cs | 4 + .../PixelImplementations/NormalizedShort2.cs | 4 + .../PixelImplementations/NormalizedShort4.cs | 4 + .../PixelOperations/Abgr32.PixelOperations.cs | 26 ++ .../Abgr32.PixelOperations.Generated.cs | 346 +++++++++++++++ .../Abgr32.PixelOperations.Generated.tt | 18 + .../Generated/_Common.ttinclude | 3 + .../PixelFormats/PixelImplementations/Rg32.cs | 4 + .../PixelImplementations/Rgb24.cs | 9 + .../PixelImplementations/Rgb48.cs | 9 + .../PixelImplementations/Rgba1010102.cs | 4 + .../PixelImplementations/Rgba32.cs | 10 + .../PixelImplementations/Rgba64.cs | 37 ++ .../PixelImplementations/RgbaVector.cs | 4 + .../PixelImplementations/Short2.cs | 4 + .../PixelImplementations/Short4.cs | 4 + .../PixelOperations{TPixel}.Generated.cs | 72 ++++ .../PixelOperations{TPixel}.Generated.tt | 3 + .../PixelFormats/Utils/PixelConverter.cs | 125 +++++- .../Color/ColorTests.CastFrom.cs | 13 + .../Color/ColorTests.CastTo.cs | 13 + .../Color/ColorTests.ConstructFrom.cs | 13 + .../ImageSharp.Tests/Image/ImageCloneTests.cs | 26 ++ .../PixelFormats/Abgr32Tests.cs | 148 +++++++ .../PixelFormats/Bgra32Tests.cs | 11 +- ...ConverterTests.ReferenceImplementations.cs | 15 + .../PixelFormats/PixelConverterTests.cs | 59 +++ ...elOperationsTests.Specialized.Generated.cs | 47 +-- .../Generated/_Common.ttinclude | 2 + .../PixelOperations/PixelOperationsTests.cs | 45 ++ .../PixelFormats/Rgba32Tests.cs | 16 + .../PixelFormats/Rgba64Tests.cs | 24 ++ .../PixelFormats/Short4Tests.cs | 18 + tests/ImageSharp.Tests/TestFormat.cs | 4 + .../TestUtilities/PixelTypes.cs | 2 + .../Tests/TestUtilityExtensionsTests.cs | 5 +- 59 files changed, 1717 insertions(+), 37 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs create mode 100644 src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt create mode 100644 tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs diff --git a/shared-infrastructure b/shared-infrastructure index 59ce17f5a..a042aba17 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 +Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 diff --git a/src/ImageSharp/Advanced/AotCompilerTools.cs b/src/ImageSharp/Advanced/AotCompilerTools.cs index b90a6ce3c..f5714fa5e 100644 --- a/src/ImageSharp/Advanced/AotCompilerTools.cs +++ b/src/ImageSharp/Advanced/AotCompilerTools.cs @@ -74,6 +74,7 @@ namespace SixLabors.ImageSharp.Advanced Seed(); Seed(); + Seed(); Seed(); Seed(); Seed(); @@ -148,6 +149,7 @@ namespace SixLabors.ImageSharp.Advanced Image img = default; img.CloneAs(default); img.CloneAs(default); + img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); img.CloneAs(default); diff --git a/src/ImageSharp/Color/Color.Conversions.cs b/src/ImageSharp/Color/Color.Conversions.cs index bf7869e53..5c10bfaa0 100644 --- a/src/ImageSharp/Color/Color.Conversions.cs +++ b/src/ImageSharp/Color/Color.Conversions.cs @@ -89,6 +89,17 @@ namespace SixLabors.ImageSharp this.boxedHighPrecisionPixel = null; } + /// + /// Initializes a new instance of the struct. + /// + /// The containing the color information. + [MethodImpl(InliningOptions.ShortMethod)] + public Color(Abgr32 pixel) + { + this.data = new Rgba64(pixel); + this.boxedHighPrecisionPixel = null; + } + /// /// Initializes a new instance of the struct. /// @@ -177,6 +188,19 @@ namespace SixLabors.ImageSharp return value; } + [MethodImpl(InliningOptions.ShortMethod)] + internal Abgr32 ToAbgr32() + { + if (this.boxedHighPrecisionPixel is null) + { + return this.data.ToAbgr32(); + } + + Abgr32 value = default; + value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4()); + return value; + } + [MethodImpl(InliningOptions.ShortMethod)] internal Rgb24 ToRgb24() { diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 929b78692..8d1236ee9 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -194,4 +194,74 @@ namespace SixLabors.ImageSharp } } } + + internal readonly struct XWZYShuffle4 : IShuffle4 + { + public byte Control + { + [MethodImpl(InliningOptions.ShortMethod)] + get => SimdUtils.Shuffle.MmShuffle(1, 2, 3, 0); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + { + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; + + for (int i = 0; i < n; i++) + { + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // tmp1 = [0 Z 0 X] + // tmp2 = [W 0 Y 0] + // tmp3=ROTL(16, tmp2) = [Y 0 W 0] + // tmp1 + tmp3 = [Y Z W X] + uint tmp1 = packed & 0xFF00FF00; + uint tmp2 = packed & 0x00FF00FF; + uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; + } + } + } + + internal readonly struct XZWYShuffle4 : IShuffle4 + { + public byte Control + { + [MethodImpl(InliningOptions.ShortMethod)] + get => SimdUtils.Shuffle.MmShuffle(1, 3, 2, 0); + } + + [MethodImpl(InliningOptions.ShortMethod)] + public void RunFallbackShuffle(ReadOnlySpan source, Span dest) + { + ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); + ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); + int n = source.Length / 4; + + for (int i = 0; i < n; i++) + { + uint packed = Unsafe.Add(ref sBase, i); + + // packed = [W Z Y X] + // tmp1 = [0 0 0 X] + // tmp2 = [W Z 0 0] + // tmp3 = [0 0 Y 0] + // tmp4=ROTR(8, tmp2) = [0 W Z 0] + // tmp5=ROTL(16, tmp3) = [Y 0 0 0] + // tmp1+ tmp3 + tmp4 = [Y W Z X] + uint tmp1 = packed & 0x000000FF; + uint tmp2 = packed & 0xFFFF0000; + uint tmp3 = packed & 0x0000FF00; + uint tmp4 = tmp2 >> 8; + uint tmp5 = tmp3 << 16; + + Unsafe.Add(ref dBase, i) = tmp1 + tmp4 + tmp5; + } + } + } } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index f90e40edb..b138f8d5e 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -75,6 +75,11 @@ True Block8x8F.Generated.tt + + True + True + Abgr32.PixelOperations.Generated.tt + True True @@ -166,6 +171,10 @@ TextTemplatingFileGenerator Block8x8F.Generated.cs + + TextTemplatingFileGenerator + Abgr32.PixelOperations.Generated.cs + TextTemplatingFileGenerator PixelOperations{TPixel}.Generated.cs diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 12b5bc784..09f8cc955 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -79,6 +79,12 @@ namespace SixLabors.ImageSharp.PixelFormats /// The value. void FromBgra32(Bgra32 source); + /// + /// Initializes the pixel instance from an value. + /// + /// The value. + void FromAbgr32(Abgr32 source); + /// /// Initializes the pixel instance from an value. /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs index cca7ff7db..afb07433f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/A8.cs @@ -87,6 +87,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.PackedValue = source.A; + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = source.A; + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs new file mode 100644 index 000000000..157ac2a83 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -0,0 +1,394 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Packed pixel type containing four 8-bit unsigned normalized values ranging from 0 to 255. + /// The color components are stored in alpha, red, green, and blue order (least significant to most significant byte). + /// + /// Ranges from [0, 0, 0, 0] to [1, 1, 1, 1] in vector form. + /// + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + [StructLayout(LayoutKind.Sequential)] + public partial struct Abgr32 : IPixel, IPackedVector + { + /// + /// Gets or sets the alpha component. + /// + public byte A; + + /// + /// Gets or sets the blue component. + /// + public byte B; + + /// + /// Gets or sets the green component. + /// + public byte G; + + /// + /// Gets or sets the red component. + /// + public byte R; + + /// + /// The maximum byte value. + /// + private static readonly Vector4 MaxBytes = new(255); + + /// + /// The half vector value. + /// + private static readonly Vector4 Half = new(0.5F); + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + this.A = byte.MaxValue; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(byte r, byte g, byte b, byte a) + { + this.R = r; + this.G = g; + this.B = b; + this.A = a; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The alpha component. + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(float r, float g, float b, float a = 1) + : this() => this.Pack(r, g, b, a); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(Vector3 vector) + : this() => this.Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The vector containing the components for the packed vector. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(Vector4 vector) + : this() => this.Pack(ref vector); + + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public Abgr32(uint packed) + : this() => this.Abgr = packed; + + /// + /// Gets or sets the packed representation of the Abgrb32 struct. + /// + public uint Abgr + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => Unsafe.As(ref Unsafe.AsRef(this)); + + [MethodImpl(InliningOptions.ShortMethod)] + set => Unsafe.As(ref this) = value; + } + + /// + public uint PackedValue + { + [MethodImpl(InliningOptions.ShortMethod)] + readonly get => this.Abgr; + + [MethodImpl(InliningOptions.ShortMethod)] + set => this.Abgr = value; + } + + /// + /// Converts an to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Color(Abgr32 source) => new(source); + + /// + /// Converts a to . + /// + /// The . + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public static implicit operator Abgr32(Color color) => color.ToAbgr32(); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator ==(Abgr32 left, Abgr32 right) => left.Equals(right); + + /// + /// Compares two objects for equality. + /// + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the parameter is not equal to the parameter; otherwise, false. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static bool operator !=(Abgr32 left, Abgr32 right) => !left.Equals(right); + + /// + public readonly PixelOperations CreatePixelOperations() => new PixelOperations(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromScaledVector4(Vector4 vector) => this.FromVector4(vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToScaledVector4() => this.ToVector4(); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromVector4(Vector4 vector) => this.Pack(ref vector); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Vector4 ToVector4() => new Vector4(this.R, this.G, this.B, this.A) / MaxBytes; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = source.PackedValue; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgr24(Bgr24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromArgb32(Argb32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromBgra32(Bgra32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL8(L8 source) + { + this.R = source.PackedValue; + this.G = source.PackedValue; + this.B = source.PackedValue; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromL16(L16 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.PackedValue); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa16(La16 source) + { + this.R = source.L; + this.G = source.L; + this.B = source.L; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromLa32(La32 source) + { + byte rgb = ColorNumerics.DownScaleFrom16BitTo8Bit(source.L); + this.R = rgb; + this.G = rgb; + this.B = rgb; + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb24(Rgb24 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba32(Rgba32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void ToRgba32(ref Rgba32 dest) + { + dest.R = this.R; + dest.G = this.G; + dest.B = this.B; + dest.A = this.A; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgb48(Rgb48 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = byte.MaxValue; + } + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromRgba64(Rgba64 source) + { + this.R = ColorNumerics.DownScaleFrom16BitTo8Bit(source.R); + this.G = ColorNumerics.DownScaleFrom16BitTo8Bit(source.G); + this.B = ColorNumerics.DownScaleFrom16BitTo8Bit(source.B); + this.A = ColorNumerics.DownScaleFrom16BitTo8Bit(source.A); + } + + /// + public override readonly bool Equals(object obj) => obj is Abgr32 abgr32 && this.Equals(abgr32); + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public readonly bool Equals(Abgr32 other) => this.Abgr == other.Abgr; + + /// + /// Gets a string representation of the packed vector. + /// + /// A string representation of the packed vector. + public override readonly string ToString() => $"Abgr({this.A}, {this.B}, {this.G}, {this.R})"; + + /// + [MethodImpl(InliningOptions.ShortMethod)] + public override readonly int GetHashCode() => this.Abgr.GetHashCode(); + + /// + /// Packs the four floats into a color. + /// + /// The x-component + /// The y-component + /// The z-component + /// The w-component + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(float x, float y, float z, float w) + { + var value = new Vector4(x, y, z, w); + this.Pack(ref value); + } + + /// + /// Packs a into a uint. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector3 vector) + { + var value = new Vector4(vector, 1); + this.Pack(ref value); + } + + /// + /// Packs a into a color. + /// + /// The vector containing the values to pack. + [MethodImpl(InliningOptions.ShortMethod)] + private void Pack(ref Vector4 vector) + { + vector *= MaxBytes; + vector += Half; + vector = Numerics.Clamp(vector, Vector4.Zero, MaxBytes); + + this.R = (byte)vector.X; + this.G = (byte)vector.Y; + this.B = (byte)vector.Z; + this.A = (byte)vector.W; + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs index 8c1b04ff1..2ec85de93 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Argb32.cs @@ -230,6 +230,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 22e983a65..882306928 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -185,6 +185,15 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = source.B; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) => this = source.Bgr; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs index 5585310b9..bd21d0425 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr565.cs @@ -99,6 +99,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromVector4(source.ToVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromVector4(source.ToVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs index be4e178c2..34769e32d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra32.cs @@ -165,6 +165,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgr24(Bgr24 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs index 3578f1dd3..059d61136 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra4444.cs @@ -102,6 +102,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs index 0254397c3..b6cc1f878 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgra5551.cs @@ -124,6 +124,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs index 0995f8417..e1ed4577e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Byte4.cs @@ -124,6 +124,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromRgba32(Rgba32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void ToRgba32(ref Rgba32 dest) => dest.FromScaledVector4(this.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs index b0ef0f6a9..51b6af640 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfSingle.cs @@ -88,6 +88,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs index 8be826130..1fff37757 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector2.cs @@ -99,6 +99,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs index 955b274ac..94051e263 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/HalfVector4.cs @@ -104,6 +104,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs index 6d1128dd2..c40e301de 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L16.cs @@ -91,6 +91,13 @@ namespace SixLabors.ImageSharp.PixelFormats ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs index ffff60be5..70d031aa1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/L8.cs @@ -83,6 +83,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.PackedValue = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs index 877aaed81..72f188fa3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La16.cs @@ -112,6 +112,14 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.L = ColorNumerics.Get8BitBT709Luminance(source.R, source.G, source.B); + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs index f19f22813..d9104aa4f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/La32.cs @@ -127,6 +127,18 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.L = ColorNumerics.Get16BitBT709Luminance( + ColorNumerics.UpscaleFrom8BitTo16Bit(source.R), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.G), + ColorNumerics.UpscaleFrom8BitTo16Bit(source.B)); + + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs index 62eaf949d..78c83a543 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte2.cs @@ -107,6 +107,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs index 2e81b3e2d..432461f75 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedByte4.cs @@ -109,6 +109,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs index b97aaacec..60fe2368a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort2.cs @@ -108,6 +108,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs index f2e8aedd8..01b9777b0 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/NormalizedShort4.cs @@ -110,6 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs new file mode 100644 index 000000000..439c5529a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Abgr32.PixelOperations.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using SixLabors.ImageSharp.Formats; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Abgr32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + private static readonly Lazy LazyInfo = + new Lazy(() => PixelTypeInfo.Create(PixelAlphaRepresentation.Unassociated), true); + + /// + public override PixelTypeInfo GetPixelTypeInfo() => LazyInfo.Value; + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs new file mode 100644 index 000000000..6386d315c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs @@ -0,0 +1,346 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// + +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using SixLabors.ImageSharp.PixelFormats.Utils; + +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Abgr32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + /// + public override void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + source.CopyTo(destinationPixels.Slice(0, source.Length)); + } + + /// + public override void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + sourcePixels.CopyTo(destinationPixels.Slice(0, sourcePixels.Length)); + } + /// + public override void FromVector4Destructive( + Configuration configuration, + Span sourceVectors, + Span destinationPixels, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.FromVector4(configuration, this, sourceVectors, destinationPixels, modifiers.Remove(PixelConversionModifiers.Scale)); + } + + /// + public override void ToVector4( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destVectors, + PixelConversionModifiers modifiers) + { + Vector4Converters.RgbaCompatible.ToVector4(configuration, this, sourcePixels, destVectors, modifiers.Remove(PixelConversionModifiers.Scale)); + } + /// + public override void ToRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgba32(source, dest); + } + + /// + public override void FromRgba32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToAbgr32(source, dest); + } + /// + public override void ToArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToArgb32(source, dest); + } + + /// + public override void FromArgb32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToAbgr32(source, dest); + } + /// + public override void ToBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgra32(source, dest); + } + + /// + public override void FromBgra32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToAbgr32(source, dest); + } + /// + public override void ToRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgb24(source, dest); + } + + /// + public override void FromRgb24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToAbgr32(source, dest); + } + /// + public override void ToBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgr24(source, dest); + } + + /// + public override void FromBgr24( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToAbgr32(source, dest); + } + /// + public override void ToL8( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToL16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToLa16( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToLa32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToRgb48( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToRgba64( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void ToBgra5551( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromAbgr32(sp); + } + } + /// + public override void From( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + PixelOperations.Instance.ToAbgr32(configuration, sourcePixels, destinationPixels.Slice(0, sourcePixels.Length)); + } + + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt new file mode 100644 index 000000000..071c74fbb --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.tt @@ -0,0 +1,18 @@ +<#@include file="_Common.ttinclude" #> +<#@ output extension=".cs" #> +namespace SixLabors.ImageSharp.PixelFormats +{ + /// + /// Provides optimized overrides for bulk operations. + /// + public partial struct Abgr32 + { + /// + /// Provides optimized overrides for bulk operations. + /// + internal partial class PixelOperations : PixelOperations + { + <# GenerateAllDefaultConversionMethods("Abgr32"); #> + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index 784ecf6fb..b6b0a14a7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude @@ -17,6 +17,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; private static readonly string[] CommonPixelTypes = { "Argb32", + "Abgr32", "Bgr24", "Bgra32", "L8", @@ -34,6 +35,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; { "Rgba32", "Argb32", + "Abgr32", "Bgra32", "Rgb24", "Bgr24" @@ -43,6 +45,7 @@ using SixLabors.ImageSharp.PixelFormats.Utils; private static readonly string[] Rgba32CompatibleTypes = { "Argb32", + "Abgr32", "Bgra32", "Rgb24", "Bgr24" diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs index 12b6e153f..d408f301b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rg32.cs @@ -93,6 +93,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs index 3b5bdb3d5..00f072268 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb24.cs @@ -153,6 +153,15 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = source.B; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromL8(L8 source) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs index d16b7db7a..90c15ae26 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgb48.cs @@ -186,6 +186,15 @@ namespace SixLabors.ImageSharp.PixelFormats this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void ToRgba32(ref Rgba32 dest) diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs index e68726018..11e11bc6c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba1010102.cs @@ -96,6 +96,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs index 3dc6490f1..2d250f71d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba32.cs @@ -333,6 +333,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = source.A; } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = source.R; + this.G = source.G; + this.B = source.B; + this.A = source.A; + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs index 4cfa0bf97..bf7452592 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Rgba64.cs @@ -94,6 +94,19 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); } + /// + /// Initializes a new instance of the struct. + /// + /// A structure of 4 bytes in ABGR byte order. + [MethodImpl(InliningOptions.ShortMethod)] + public Rgba64(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + /// /// Initializes a new instance of the struct. /// @@ -250,6 +263,16 @@ namespace SixLabors.ImageSharp.PixelFormats this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); } + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) + { + this.R = ColorNumerics.UpscaleFrom8BitTo16Bit(source.R); + this.G = ColorNumerics.UpscaleFrom8BitTo16Bit(source.G); + this.B = ColorNumerics.UpscaleFrom8BitTo16Bit(source.B); + this.A = ColorNumerics.UpscaleFrom8BitTo16Bit(source.A); + } + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); @@ -380,6 +403,20 @@ namespace SixLabors.ImageSharp.PixelFormats return new Argb32(r, g, b, a); } + /// + /// Convert to . + /// + /// The . + [MethodImpl(InliningOptions.ShortMethod)] + public readonly Abgr32 ToAbgr32() + { + byte r = ColorNumerics.DownScaleFrom16BitTo8Bit(this.R); + byte g = ColorNumerics.DownScaleFrom16BitTo8Bit(this.G); + byte b = ColorNumerics.DownScaleFrom16BitTo8Bit(this.B); + byte a = ColorNumerics.DownScaleFrom16BitTo8Bit(this.A); + return new Abgr32(r, g, b, a); + } + /// /// Convert to . /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs index cd6f53c4e..e582e6166 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/RgbaVector.cs @@ -134,6 +134,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs index 24f6b4d1d..f0117707c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short2.cs @@ -111,6 +111,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs index 86a519297..fd58477d9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Short4.cs @@ -116,6 +116,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra32(Bgra32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void FromAbgr32(Abgr32 source) => this.FromScaledVector4(source.ToScaledVector4()); + /// [MethodImpl(InliningOptions.ShortMethod)] public void FromBgra5551(Bgra5551 source) => this.FromScaledVector4(source.ToScaledVector4()); diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs index cc36c7d13..ea107b35a 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.cs @@ -82,6 +82,78 @@ namespace SixLabors.ImageSharp.PixelFormats this.ToArgb32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); } + /// + /// Converts all pixels in 'source` span of into a span of -s. + /// + /// A to configure internal operations. + /// The source of data. + /// The to the destination pixels. + public virtual void FromAbgr32(Configuration configuration, ReadOnlySpan source, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(source, destinationPixels, nameof(destinationPixels)); + + ref Abgr32 sourceBaseRef = ref MemoryMarshal.GetReference(source); + ref TPixel destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < source.Length; i++) + { + ref Abgr32 sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref TPixel dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromAbgr32(sp); + } + } + + /// + /// A helper for that expects a byte span. + /// The layout of the data in 'sourceBytes' must be compatible with layout. + /// + /// A to configure internal operations. + /// The to the source bytes. + /// The to the destination pixels. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void FromAbgr32Bytes(Configuration configuration, ReadOnlySpan sourceBytes, Span destinationPixels, int count) + { + this.FromAbgr32(configuration, MemoryMarshal.Cast(sourceBytes).Slice(0, count), destinationPixels); + } + + /// + /// Converts all pixels of the 'sourcePixels` span to a span of -s. + /// + /// A to configure internal operations + /// The span of source pixels + /// The destination span of data. + public virtual void ToAbgr32(Configuration configuration, ReadOnlySpan sourcePixels, Span destinationPixels) + { + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref TPixel sourceBaseRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destBaseRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref TPixel sp = ref Unsafe.Add(ref sourceBaseRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destBaseRef, i); + + dp.FromScaledVector4(sp.ToScaledVector4()); + } + } + + /// + /// A helper for that expects a byte span as destination. + /// The layout of the data in 'destBytes' must be compatible with layout. + /// + /// A to configure internal operations + /// The to the source pixels. + /// The to the destination bytes. + /// The number of pixels to convert. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ToAbgr32Bytes(Configuration configuration, ReadOnlySpan sourcePixels, Span destBytes, int count) + { + this.ToAbgr32(configuration, sourcePixels.Slice(0, count), MemoryMarshal.Cast(destBytes)); + } + /// /// Converts all pixels in 'source` span of into a span of -s. /// diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt index 21ed328fa..e8cf6f9a5 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt @@ -112,6 +112,9 @@ namespace SixLabors.ImageSharp.PixelFormats GenerateFromMethods("Argb32"); GenerateToDestFormatMethods("Argb32"); + GenerateFromMethods("Abgr32"); + GenerateToDestFormatMethods("Abgr32"); + GenerateFromMethods("Bgr24"); GenerateToDestFormatMethods("Bgr24"); diff --git a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs index 7215fa860..907399d5f 100644 --- a/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs +++ b/src/ImageSharp/PixelFormats/Utils/PixelConverter.cs @@ -18,8 +18,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils /// internal static class PixelConverter { + /// + /// Optimized converters from . + /// public static class FromRgba32 { + // Input pixels have: X = R, Y = G, Z = B and W = A. + /// /// Converts a representing a collection of /// pixels to a representing @@ -38,6 +43,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + /// /// Converts a representing a collection of /// pixels to a representing @@ -57,8 +71,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(3, 0, 1, 2)); } + /// + /// Optimized converters from . + /// public static class FromArgb32 { + // Input pixels have: X = A, Y = R, Z = G and W = B. + /// /// Converts a representing a collection of /// pixels to a representing @@ -77,6 +96,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + /// /// Converts a representing a collection of /// pixels to a representing @@ -96,8 +124,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); } + /// + /// Optimized converters from . + /// public static class FromBgra32 { + // Input pixels have: X = B, Y = G, Z = R and W = A. + /// /// Converts a representing a collection of /// pixels to a representing @@ -110,12 +143,21 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils /// /// Converts a representing a collection of /// pixels to a representing - /// a collection of pixels. + /// a collection of pixels. /// [MethodImpl(InliningOptions.ShortMethod)] public static void ToRgba32(ReadOnlySpan source, Span dest) => SimdUtils.Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + /// /// Converts a representing a collection of /// pixels to a representing @@ -135,8 +177,66 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle4Slice3(source, dest, default); } + /// + /// Optimized converters from . + /// + public static class FromAbgr32 + { + // Input pixels have: X = A, Y = B, Z = G and W = R. + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToArgb32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgba32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgra32(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4(source, dest, default); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToRgb24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 1, 2, 3)); + + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToBgr24(ReadOnlySpan source, Span dest) + => SimdUtils.Shuffle4Slice3(source, dest, new DefaultShuffle4Slice3(0, 3, 2, 1)); + } + + /// + /// Optimized converters from . + /// public static class FromRgb24 { + // Input pixels have: X = R, Y = G and Z = B. + /// /// Converts a representing a collection of /// pixels to a representing @@ -164,6 +264,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(3, 0, 1, 2)); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(0, 1, 2, 3)); + /// /// Converts a representing a collection of /// pixels to a representing @@ -174,8 +283,13 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils => SimdUtils.Shuffle3(source, dest, new DefaultShuffle3(0, 1, 2)); } + /// + /// Optimized converters from . + /// public static class FromBgr24 { + // Input pixels have: X = B, Y = G and Z = R. + /// /// Converts a representing a collection of /// pixels to a representing @@ -203,6 +317,15 @@ namespace SixLabors.ImageSharp.PixelFormats.Utils public static void ToBgra32(ReadOnlySpan source, Span dest) => SimdUtils.Pad3Shuffle4(source, dest, default); + /// + /// Converts a representing a collection of + /// pixels to a representing + /// a collection of pixels. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public static void ToAbgr32(ReadOnlySpan source, Span dest) + => SimdUtils.Pad3Shuffle4(source, dest, new DefaultPad3Shuffle4(2, 1, 0, 3)); + /// /// Converts a representing a collection of /// pixels to a representing diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs index 38b94f486..ec03847df 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastFrom.cs @@ -63,6 +63,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(source, data); } + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + Color color = source; + + // Assert: + Abgr32 data = color.ToPixel(); + Assert.Equal(source, data); + } + [Fact] public void Rgb24() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs index bfc290c2e..1997cc6b1 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs @@ -64,6 +64,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(source, data); } + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Abgr32 data = color; + Assert.Equal(source, data); + } + [Fact] public void Rgb24() { diff --git a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs index 89276014b..d61361c80 100644 --- a/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs +++ b/tests/ImageSharp.Tests/Color/ColorTests.ConstructFrom.cs @@ -63,6 +63,19 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(source, data); } + [Fact] + public void Abgr32() + { + var source = new Abgr32(1, 22, 33, 231); + + // Act: + var color = new Color(source); + + // Assert: + Abgr32 data = color.ToPixel(); + Assert.Equal(source, data); + } + [Fact] public void Rgb24() { diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index f60264334..c1012e2df 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -56,6 +56,32 @@ namespace SixLabors.ImageSharp.Tests }); } + [Theory] + [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] + public void CloneAs_ToAbgr32(TestImageProvider provider) + { + using (Image image = provider.GetImage()) + using (Image clone = image.CloneAs()) + { + for (int y = 0; y < image.Height; y++) + { + Span row = image.GetPixelRowSpan(y); + Span rowClone = clone.GetPixelRowSpan(y); + + for (int x = 0; x < image.Width; x++) + { + Rgba32 expected = row[x]; + Abgr32 actual = rowClone[x]; + + Assert.Equal(expected.R, actual.R); + Assert.Equal(expected.G, actual.G); + Assert.Equal(expected.B, actual.B); + Assert.Equal(expected.A, actual.A); + } + } + } + } + [Theory] [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToBgr24(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs new file mode 100644 index 000000000..278de3357 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/Abgr32Tests.cs @@ -0,0 +1,148 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Numerics; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.PixelFormats +{ + [Trait("Category", "PixelFormats")] + public class Abgr32Tests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + var color1 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue); + var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); + + Assert.Equal(color1, color2); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + var color1 = new Abgr32(0, 0, byte.MaxValue, byte.MaxValue); + var color2 = new Abgr32(byte.MaxValue, byte.MaxValue, byte.MaxValue); + + Assert.NotEqual(color1, color2); + } + + public static readonly TheoryData ColorData = + new() + { + { 1, 2, 3, 4 }, + { 4, 5, 6, 7 }, + { 0, 255, 42, 0 }, + { 1, 2, 3, 255 } + }; + + [Theory] + [MemberData(nameof(ColorData))] + public void Constructor(byte b, byte g, byte r, byte a) + { + var p = new Abgr32(r, g, b, a); + + Assert.Equal(r, p.R); + Assert.Equal(g, p.G); + Assert.Equal(b, p.B); + Assert.Equal(a, p.A); + } + + [Fact] + public unsafe void ByteLayoutIsSequentialBgra() + { + var color = new Abgr32(1, 2, 3, 4); + byte* ptr = (byte*)&color; + + Assert.Equal(4, ptr[0]); + Assert.Equal(3, ptr[1]); + Assert.Equal(2, ptr[2]); + Assert.Equal(1, ptr[3]); + } + + [Theory] + [MemberData(nameof(ColorData))] + public void Equality_WhenTrue(byte r, byte g, byte b, byte a) + { + var x = new Abgr32(r, g, b, a); + var y = new Abgr32(r, g, b, a); + + Assert.True(x.Equals(y)); + Assert.True(x.Equals((object)y)); + Assert.Equal(x.GetHashCode(), y.GetHashCode()); + } + + [Theory] + [InlineData(1, 2, 3, 4, 1, 2, 3, 5)] + [InlineData(0, 0, 255, 0, 0, 0, 244, 0)] + [InlineData(0, 255, 0, 0, 0, 244, 0, 0)] + [InlineData(1, 255, 0, 0, 0, 255, 0, 0)] + public void Equality_WhenFalse(byte b1, byte g1, byte r1, byte a1, byte b2, byte g2, byte r2, byte a2) + { + var x = new Abgr32(r1, g1, b1, a1); + var y = new Abgr32(r2, g2, b2, a2); + + Assert.False(x.Equals(y)); + Assert.False(x.Equals((object)y)); + } + + [Fact] + public void FromRgba32() + { + var abgr = default(Abgr32); + abgr.FromRgba32(new Rgba32(1, 2, 3, 4)); + + Assert.Equal(1, abgr.R); + Assert.Equal(2, abgr.G); + Assert.Equal(3, abgr.B); + Assert.Equal(4, abgr.A); + } + + private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( + r / 255f, + g / 255f, + b / 255f, + a / 255f); + + [Fact] + public void FromVector4() + { + var c = default(Abgr32); + c.FromVector4(Vec(1, 2, 3, 4)); + + Assert.Equal(1, c.R); + Assert.Equal(2, c.G); + Assert.Equal(3, c.B); + Assert.Equal(4, c.A); + } + + [Fact] + public void ToVector4() + { + var abgr = new Abgr32(1, 2, 3, 4); + + Assert.Equal(Vec(1, 2, 3, 4), abgr.ToVector4()); + } + + [Fact] + public void Abgr32_FromBgra5551() + { + // arrange + var abgr = default(Abgr32); + uint expected = uint.MaxValue; + + // act + abgr.FromBgra5551(new Bgra5551(1.0f, 1.0f, 1.0f, 1.0f)); + + // assert + Assert.Equal(expected, abgr.PackedValue); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs index 4b8f4c2ea..7a2f29e2c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Bgra32Tests.cs @@ -96,12 +96,13 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats [Fact] public void FromRgba32() { - var rgb = default(Rgb24); - rgb.FromRgba32(new Rgba32(1, 2, 3, 4)); + var bgra = default(Bgra32); + bgra.FromRgba32(new Rgba32(1, 2, 3, 4)); - Assert.Equal(1, rgb.R); - Assert.Equal(2, rgb.G); - Assert.Equal(3, rgb.B); + Assert.Equal(1, bgra.R); + Assert.Equal(2, bgra.G); + Assert.Equal(3, bgra.B); + Assert.Equal(4, bgra.A); } private static Vector4 Vec(byte r, byte g, byte b, byte a = 255) => new Vector4( diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs index 8b3483145..e51ee233c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.ReferenceImplementations.cs @@ -60,6 +60,21 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats return buffer; } + public static byte[] MakeAbgr32ByteArray(byte r, byte g, byte b, byte a) + { + var buffer = new byte[256]; + + for (int i = 0; i < buffer.Length; i += 4) + { + buffer[i] = a; + buffer[i + 1] = b; + buffer[i + 2] = g; + buffer[i + 3] = r; + } + + return buffer; + } + internal static void To( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs index 315f9f776..986f4189b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelConverterTests.cs @@ -56,6 +56,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + + [Theory] + [MemberData(nameof(RgbaData))] + public void ToAbgr32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromRgba32.ToAbgr32(source, actual); + + byte[] expected = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } } public class FromArgb32 : PixelConverterTests @@ -119,5 +133,50 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } } + + public class FromAbgr32 : PixelConverterTests + { + [Theory] + [MemberData(nameof(RgbaData))] + public void ToArgb32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromAbgr32.ToArgb32(source, actual); + + byte[] expected = ReferenceImplementations.MakeArgb32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(RgbaData))] + public void ToRgba32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromAbgr32.ToRgba32(source, actual); + + byte[] expected = ReferenceImplementations.MakeRgba32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } + + [Theory] + [MemberData(nameof(RgbaData))] + public void ToBgra32(byte r, byte g, byte b, byte a) + { + byte[] source = ReferenceImplementations.MakeAbgr32ByteArray(r, g, b, a); + byte[] actual = new byte[source.Length]; + + PixelConverter.FromAbgr32.ToBgra32(source, actual); + + byte[] expected = ReferenceImplementations.MakeBgra32ByteArray(r, g, b, a); + + Assert.Equal(expected, actual); + } + } } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs index 1069eb9ac..10b06df3b 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/PixelOperationsTests.Specialized.Generated.cs @@ -12,8 +12,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations { public partial class PixelOperationsTests { - - public partial class A8_OperationsTests : PixelOperationsTests + public partial class A8_OperationsTests : PixelOperationsTests { public A8_OperationsTests(ITestOutputHelper output) : base(output) @@ -32,7 +31,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Argb32_OperationsTests : PixelOperationsTests { public Argb32_OperationsTests(ITestOutputHelper output) @@ -52,7 +50,25 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } + public partial class Abgr32_OperationsTests : PixelOperationsTests + { + public Abgr32_OperationsTests(ITestOutputHelper output) + : base(output) + { + } + protected override PixelOperations Operations => Abgr32.PixelOperations.Instance; + + [Fact] + public void IsSpecialImplementation() => Assert.IsType(PixelOperations.Instance); + + [Fact] + public void PixelTypeInfoHasCorrectAlphaRepresentation() + { + var alphaRepresentation = this.Operations.GetPixelTypeInfo().AlphaRepresentation; + Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); + } + } public partial class Bgr24_OperationsTests : PixelOperationsTests { public Bgr24_OperationsTests(ITestOutputHelper output) @@ -72,7 +88,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Bgr565_OperationsTests : PixelOperationsTests { public Bgr565_OperationsTests(ITestOutputHelper output) @@ -92,7 +107,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Bgra32_OperationsTests : PixelOperationsTests { public Bgra32_OperationsTests(ITestOutputHelper output) @@ -112,7 +126,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Bgra4444_OperationsTests : PixelOperationsTests { public Bgra4444_OperationsTests(ITestOutputHelper output) @@ -132,7 +145,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Bgra5551_OperationsTests : PixelOperationsTests { public Bgra5551_OperationsTests(ITestOutputHelper output) @@ -152,7 +164,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Byte4_OperationsTests : PixelOperationsTests { public Byte4_OperationsTests(ITestOutputHelper output) @@ -172,7 +183,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class HalfSingle_OperationsTests : PixelOperationsTests { public HalfSingle_OperationsTests(ITestOutputHelper output) @@ -192,7 +202,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class HalfVector2_OperationsTests : PixelOperationsTests { public HalfVector2_OperationsTests(ITestOutputHelper output) @@ -212,7 +221,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class HalfVector4_OperationsTests : PixelOperationsTests { public HalfVector4_OperationsTests(ITestOutputHelper output) @@ -232,7 +240,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class L16_OperationsTests : PixelOperationsTests { public L16_OperationsTests(ITestOutputHelper output) @@ -252,7 +259,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class L8_OperationsTests : PixelOperationsTests { public L8_OperationsTests(ITestOutputHelper output) @@ -272,7 +278,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class La16_OperationsTests : PixelOperationsTests { public La16_OperationsTests(ITestOutputHelper output) @@ -292,7 +297,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class La32_OperationsTests : PixelOperationsTests { public La32_OperationsTests(ITestOutputHelper output) @@ -312,7 +316,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class NormalizedByte2_OperationsTests : PixelOperationsTests { public NormalizedByte2_OperationsTests(ITestOutputHelper output) @@ -332,7 +335,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class NormalizedByte4_OperationsTests : PixelOperationsTests { public NormalizedByte4_OperationsTests(ITestOutputHelper output) @@ -352,7 +354,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class NormalizedShort2_OperationsTests : PixelOperationsTests { public NormalizedShort2_OperationsTests(ITestOutputHelper output) @@ -372,7 +373,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class NormalizedShort4_OperationsTests : PixelOperationsTests { public NormalizedShort4_OperationsTests(ITestOutputHelper output) @@ -392,7 +392,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Rg32_OperationsTests : PixelOperationsTests { public Rg32_OperationsTests(ITestOutputHelper output) @@ -412,7 +411,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Rgb24_OperationsTests : PixelOperationsTests { public Rgb24_OperationsTests(ITestOutputHelper output) @@ -432,7 +430,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Rgb48_OperationsTests : PixelOperationsTests { public Rgb48_OperationsTests(ITestOutputHelper output) @@ -452,7 +449,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Rgba1010102_OperationsTests : PixelOperationsTests { public Rgba1010102_OperationsTests(ITestOutputHelper output) @@ -472,7 +468,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Rgba32_OperationsTests : PixelOperationsTests { public Rgba32_OperationsTests(ITestOutputHelper output) @@ -492,7 +487,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Rgba64_OperationsTests : PixelOperationsTests { public Rgba64_OperationsTests(ITestOutputHelper output) @@ -512,7 +506,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class RgbaVector_OperationsTests : PixelOperationsTests { public RgbaVector_OperationsTests(ITestOutputHelper output) @@ -532,7 +525,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.Unassociated, alphaRepresentation); } } - public partial class Short2_OperationsTests : PixelOperationsTests { public Short2_OperationsTests(ITestOutputHelper output) @@ -552,7 +544,6 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations Assert.Equal(PixelAlphaRepresentation.None, alphaRepresentation); } } - public partial class Short4_OperationsTests : PixelOperationsTests { public Short4_OperationsTests(ITestOutputHelper output) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude index 8c436eecc..6ef3e914f 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/Generated/_Common.ttinclude @@ -16,6 +16,7 @@ using Xunit.Abstractions; { "A8", "Argb32", + "Abgr32", "Bgra32", "Bgra4444", "Bgra5551", @@ -38,6 +39,7 @@ using Xunit.Abstractions; { "A8", "Argb32", + "Abgr32", "Bgr24", "Bgr565", "Bgra32", diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs index a2688359f..2a89c1d39 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations/PixelOperationsTests.cs @@ -322,6 +322,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations public static readonly TheoryData Generic_To_Data = new TheoryData { + new TestPixel(), new TestPixel(), new TestPixel(), new TestPixel(), @@ -586,6 +587,50 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelOperations (s, d) => this.Operations.ToBgra32Bytes(this.Configuration, s, d.GetSpan(), count)); } + [Theory] + [MemberData(nameof(ArraySizesData))] + public void FromAbgr32Bytes(int count) + { + byte[] source = CreateByteTestData(count * 4); + var expected = new TPixel[count]; + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + + expected[i].FromAbgr32(new Abgr32(source[i4 + 3], source[i4 + 2], source[i4 + 1], source[i4 + 0])); + } + + TestOperation( + source, + expected, + (s, d) => this.Operations.FromAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + + [Theory] + [MemberData(nameof(ArraySizesData))] + public void ToAbgr32Bytes(int count) + { + TPixel[] source = CreatePixelTestData(count); + byte[] expected = new byte[count * 4]; + var abgr = default(Abgr32); + + for (int i = 0; i < count; i++) + { + int i4 = i * 4; + abgr.FromScaledVector4(source[i].ToScaledVector4()); + expected[i4] = abgr.A; + expected[i4 + 1] = abgr.B; + expected[i4 + 2] = abgr.G; + expected[i4 + 3] = abgr.R; + } + + TestOperation( + source, + expected, + (s, d) => this.Operations.ToAbgr32Bytes(this.Configuration, s, d.GetSpan(), count)); + } + [Theory] [MemberData(nameof(ArraySizesData))] public void FromBgra5551Bytes(int count) diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs index bc66d404b..aff16d6d8 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba32Tests.cs @@ -230,6 +230,22 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void Rgba32_FromAbgr32_ToRgba32() + { + // arrange + var rgba = default(Rgba32); + var actual = default(Abgr32); + var expected = new Abgr32(0x1a, 0, 0x80, 0); + + // act + rgba.FromAbgr32(expected); + actual.FromRgba32(rgba); + + // assert + Assert.Equal(expected, actual); + } + [Fact] public void Rgba32_FromArgb32_ToArgb32() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs index 8daf960d6..e08cd2950 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Rgba64Tests.cs @@ -183,6 +183,16 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void ConstructFrom_Abgr32() + { + var expected = new Rgba64(5140, 9766, 19532, 29555); + var source = new Abgr32(20, 38, 76, 115); + var actual = new Rgba64(source); + + Assert.Equal(expected, actual); + } + [Fact] public void ConstructFrom_Rgb24() { @@ -257,6 +267,20 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void ToAbgr32_Retval() + { + // arrange + var source = new Rgba64(5140, 9766, 19532, 29555); + var expected = new Abgr32(20, 38, 76, 115); + + // act + var actual = source.ToAbgr32(); + + // assert + Assert.Equal(expected, actual); + } + [Fact] public void ToRgb24_Retval() { diff --git a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs index 3709205ac..a95ac9ab9 100644 --- a/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs +++ b/tests/ImageSharp.Tests/PixelFormats/Short4Tests.cs @@ -150,6 +150,24 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats Assert.Equal(expected, actual); } + [Fact] + public void Short4_FromAbgrb32_ToRgba32() + { + // arrange + var short4 = default(Short4); + var actual = default(Abgr32); + var expected = new Abgr32(20, 38, 0, 255); + + // act + short4.FromAbgr32(expected); + Rgba32 temp = default; + short4.ToRgba32(ref temp); + actual.FromRgba32(temp); + + // assert + Assert.Equal(expected, actual); + } + [Fact] public void Short4_FromRgb48_ToRgb48() { diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index c8d0633d7..6c2b97eb6 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -310,6 +310,10 @@ namespace SixLabors.ImageSharp.Tests { } + public void FromAbgr32(Abgr32 source) + { + } + public void FromL8(L8 source) { } diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index eb840231c..8ba383125 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -69,6 +69,8 @@ namespace SixLabors.ImageSharp.Tests La32 = 1 << 26, + Abgr32 = 1 << 27, + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper // "All" is handled as a separate, individual case instead of using bitwise OR diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index bb9ed8260..1a46f91e5 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -112,14 +112,15 @@ namespace SixLabors.ImageSharp.Tests [Fact] public void ExpandAllTypes_2() { - PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; + PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Abgr32 | PixelTypes.RgbaVector; IEnumerable> expanded = pixelTypes.ExpandAllTypes(); - Assert.Equal(3, expanded.Count()); + Assert.Equal(4, expanded.Count()); AssertContainsPixelType(PixelTypes.Rgba32, expanded); AssertContainsPixelType(PixelTypes.Bgra32, expanded); + AssertContainsPixelType(PixelTypes.Abgr32, expanded); AssertContainsPixelType(PixelTypes.RgbaVector, expanded); } From 98e44174293bbc0718e4cbdeca7039a2ea4b105c Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 6 Dec 2021 22:23:17 +0100 Subject: [PATCH 207/228] Fix pixel conversion tests --- .../Helpers/Shuffle/IComponentShuffle.cs | 4 +-- .../PixelImplementations/Abgr32.cs | 2 +- .../Argb32.PixelOperations.Generated.cs | 27 +++++++++++++++++++ .../Bgr24.PixelOperations.Generated.cs | 27 +++++++++++++++++++ .../Bgra32.PixelOperations.Generated.cs | 27 +++++++++++++++++++ .../Bgra5551.PixelOperations.Generated.cs | 20 ++++++++++++++ .../L16.PixelOperations.Generated.cs | 20 ++++++++++++++ .../Generated/L8.PixelOperations.Generated.cs | 20 ++++++++++++++ .../La16.PixelOperations.Generated.cs | 20 ++++++++++++++ .../La32.PixelOperations.Generated.cs | 20 ++++++++++++++ .../Rgb24.PixelOperations.Generated.cs | 27 +++++++++++++++++++ .../Rgb48.PixelOperations.Generated.cs | 20 ++++++++++++++ .../Rgba32.PixelOperations.Generated.cs | 27 +++++++++++++++++++ .../Rgba64.PixelOperations.Generated.cs | 20 ++++++++++++++ 14 files changed, 278 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 8d1236ee9..2fc2b4430 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -219,8 +219,8 @@ namespace SixLabors.ImageSharp // tmp2 = [W 0 Y 0] // tmp3=ROTL(16, tmp2) = [Y 0 W 0] // tmp1 + tmp3 = [Y Z W X] - uint tmp1 = packed & 0xFF00FF00; - uint tmp2 = packed & 0x00FF00FF; + uint tmp1 = packed & 0x00FF00FF; + uint tmp2 = packed & 0xFF00FF00; uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); Unsafe.Add(ref dBase, i) = tmp1 + tmp3; diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs index 157ac2a83..0a95fc1ff 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.PixelFormats /// [MethodImpl(InliningOptions.ShortMethod)] - public void FromAbgr32(Abgr32 source) => this.PackedValue = source.PackedValue; + public void FromAbgr32(Abgr32 source) => this = source; /// [MethodImpl(InliningOptions.ShortMethod)] diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs index cedd1762d..0f183df0d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs @@ -85,6 +85,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromRgba32.ToArgb32(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromArgb32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToArgb32(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs index c98e35656..ea08c6391 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs @@ -112,6 +112,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToBgr24(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgr24.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgr24(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs index 02bb67532..0ec9a552c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs @@ -112,6 +112,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToBgra32(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromBgra32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToBgra32(source, dest); + } + /// public override void ToRgb24( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs index a02ffc3a4..877ce1a6f 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromBgra5551(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs index 954ef2d98..94eb3229b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL16(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs index b3d809de5..a748590f7 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromL8(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs index 14618d026..f47cd6c40 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa16(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs index 9620a1df4..f0c2c3323 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromLa32(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs index 2fe7f3c20..75f677cdd 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs @@ -112,6 +112,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToRgb24(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgb24.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgb24(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs index 031008fe1..22b96fd5a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgb48(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs index 16f96d2da..093182c82 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs @@ -66,6 +66,33 @@ namespace SixLabors.ImageSharp.PixelFormats PixelConverter.FromArgb32.ToRgba32(source, dest); } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromRgba32.ToAbgr32(source, dest); + } + + /// + public override void FromAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ReadOnlySpan source = MemoryMarshal.Cast(sourcePixels); + Span dest = MemoryMarshal.Cast(destinationPixels); + PixelConverter.FromAbgr32.ToRgba32(source, dest); + } + /// public override void ToBgra32( Configuration configuration, ReadOnlySpan sourcePixels, diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs index 1f1571e91..ce1b53e66 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs @@ -59,6 +59,26 @@ namespace SixLabors.ImageSharp.PixelFormats } } /// + public override void ToAbgr32( + Configuration configuration, + ReadOnlySpan sourcePixels, + Span destinationPixels) + { + Guard.NotNull(configuration, nameof(configuration)); + Guard.DestinationShouldNotBeTooShort(sourcePixels, destinationPixels, nameof(destinationPixels)); + + ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); + ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); + + for (int i = 0; i < sourcePixels.Length; i++) + { + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + + dp.FromRgba64(sp); + } + } + /// public override void ToBgr24( Configuration configuration, ReadOnlySpan sourcePixels, From a8d3cebde8690896a1919df4fac585ad48360dd8 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 6 Dec 2021 22:31:23 +0100 Subject: [PATCH 208/228] Remove dead code from IComponentShuffle --- .../Helpers/Shuffle/IComponentShuffle.cs | 37 ------------------- 1 file changed, 37 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 2fc2b4430..4c02deb2d 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -227,41 +227,4 @@ namespace SixLabors.ImageSharp } } } - - internal readonly struct XZWYShuffle4 : IShuffle4 - { - public byte Control - { - [MethodImpl(InliningOptions.ShortMethod)] - get => SimdUtils.Shuffle.MmShuffle(1, 3, 2, 0); - } - - [MethodImpl(InliningOptions.ShortMethod)] - public void RunFallbackShuffle(ReadOnlySpan source, Span dest) - { - ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); - ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; - - for (int i = 0; i < n; i++) - { - uint packed = Unsafe.Add(ref sBase, i); - - // packed = [W Z Y X] - // tmp1 = [0 0 0 X] - // tmp2 = [W Z 0 0] - // tmp3 = [0 0 Y 0] - // tmp4=ROTR(8, tmp2) = [0 W Z 0] - // tmp5=ROTL(16, tmp3) = [Y 0 0 0] - // tmp1+ tmp3 + tmp4 = [Y W Z X] - uint tmp1 = packed & 0x000000FF; - uint tmp2 = packed & 0xFFFF0000; - uint tmp3 = packed & 0x0000FF00; - uint tmp4 = tmp2 >> 8; - uint tmp5 = tmp3 << 16; - - Unsafe.Add(ref dBase, i) = tmp1 + tmp4 + tmp5; - } - } - } } From 296e412f3fe0ff9adc364d6139d45bb3f4d375f7 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 10 Dec 2021 16:57:05 +0100 Subject: [PATCH 209/228] Fixes after rebasing to latest master --- .gitattributes | 8 ++------ tests/ImageSharp.Tests/Image/ImageCloneTests.cs | 17 +++++++++-------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/.gitattributes b/.gitattributes index 355b64dce..70ced6903 100644 --- a/.gitattributes +++ b/.gitattributes @@ -87,6 +87,7 @@ *.eot binary *.exe binary *.otf binary +*.pbm binary *.pdf binary *.ppt binary *.pptx binary @@ -94,6 +95,7 @@ *.snk binary *.ttc binary *.ttf binary +*.wbmp binary *.woff binary *.woff2 binary *.xls binary @@ -124,9 +126,3 @@ *.dds filter=lfs diff=lfs merge=lfs -text *.ktx filter=lfs diff=lfs merge=lfs -text *.ktx2 filter=lfs diff=lfs merge=lfs -text -*.pam filter=lfs diff=lfs merge=lfs -text -*.pbm filter=lfs diff=lfs merge=lfs -text -*.pgm filter=lfs diff=lfs merge=lfs -text -*.ppm filter=lfs diff=lfs merge=lfs -text -*.pnm filter=lfs diff=lfs merge=lfs -text -*.wbmp filter=lfs diff=lfs merge=lfs -text diff --git a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs index c1012e2df..9d978ee52 100644 --- a/tests/ImageSharp.Tests/Image/ImageCloneTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageCloneTests.cs @@ -60,15 +60,17 @@ namespace SixLabors.ImageSharp.Tests [WithTestPatternImages(9, 9, PixelTypes.Rgba32)] public void CloneAs_ToAbgr32(TestImageProvider provider) { - using (Image image = provider.GetImage()) - using (Image clone = image.CloneAs()) + using Image image = provider.GetImage(); + using Image clone = image.CloneAs(); + + image.ProcessPixelRows(clone, static (imageAccessor, cloneAccessor) => { - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < imageAccessor.Height; y++) { - Span row = image.GetPixelRowSpan(y); - Span rowClone = clone.GetPixelRowSpan(y); + Span row = imageAccessor.GetRowSpan(y); + Span rowClone = cloneAccessor.GetRowSpan(y); - for (int x = 0; x < image.Width; x++) + for (int x = 0; x < cloneAccessor.Width; x++) { Rgba32 expected = row[x]; Abgr32 actual = rowClone[x]; @@ -76,10 +78,9 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(expected.R, actual.R); Assert.Equal(expected.G, actual.G); Assert.Equal(expected.B, actual.B); - Assert.Equal(expected.A, actual.A); } } - } + }); } [Theory] From 364bbbb5f79038848d78b13b07ee7e56d6619c36 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 11 Dec 2021 09:33:37 +0300 Subject: [PATCH 210/228] Fixed old runtimes remote executor tests --- shared-infrastructure | 2 +- .../Formats/Jpg/JpegColorConverterTests.cs | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/shared-infrastructure b/shared-infrastructure index a042aba17..59ce17f5a 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 +Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index f1e3684de..a5414ba1d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -24,12 +24,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private const int TestBufferLength = 40; - private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX; +#else + private static readonly HwIntrinsics IntrinsicsConfig = HwIntrinsics.AllowAll; +#endif - public static readonly TheoryData Seeds = new() { 1, 2, 3 }; + private static readonly ApproximateColorSpaceComparer ColorSpaceComparer = new(epsilon: Precision); private static readonly ColorSpaceConverter ColorSpaceConverter = new(); + public static readonly TheoryData Seeds = new() { 1, 2, 3 }; + public JpegColorConverterTests(ITestOutputHelper output) { this.Output = output; @@ -107,7 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + IntrinsicsConfig); static void RunTest(string arg) => ValidateConversion( @@ -144,7 +150,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + IntrinsicsConfig); static void RunTest(string arg) => ValidateConversion( @@ -181,7 +187,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + IntrinsicsConfig); static void RunTest(string arg) => ValidateConversion( @@ -218,7 +224,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + IntrinsicsConfig); static void RunTest(string arg) => ValidateConversion( @@ -255,7 +261,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX); + IntrinsicsConfig); static void RunTest(string arg) => ValidateConversion( From 7a9357c1166d401377293d905c5a5a9e02d3269c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 11 Dec 2021 09:36:51 +0300 Subject: [PATCH 211/228] Small qol fixes --- .../Formats/Jpg/JpegColorConverterTests.cs | 50 ++++++++----------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs index a5414ba1d..91f87610e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public class JpegColorConverterTests { - private static readonly float MaxColorChannelValue = 255f; + private const float MaxColorChannelValue = 255f; private const float Precision = 0.1F / 255; @@ -122,13 +122,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.Deserialize(arg)); } -#if SUPPORTS_RUNTIME_INTRINSICS - [Theory] - [MemberData(nameof(Seeds))] - public void FromYCbCrAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed); -#endif - [Theory] [MemberData(nameof(Seeds))] public void FromCmykBasic(int seed) => @@ -159,13 +152,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.Deserialize(arg)); } -#if SUPPORTS_RUNTIME_INTRINSICS - [Theory] - [MemberData(nameof(Seeds))] - public void FromCmykAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed); -#endif - [Theory] [MemberData(nameof(Seeds))] public void FromGrayscaleBasic(int seed) => @@ -196,13 +182,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.Deserialize(arg)); } -#if SUPPORTS_RUNTIME_INTRINSICS - [Theory] - [MemberData(nameof(Seeds))] - public void FromGrayscaleAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed); -#endif - [Theory] [MemberData(nameof(Seeds))] public void FromRgbBasic(int seed) => @@ -233,13 +212,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg FeatureTestRunner.Deserialize(arg)); } -#if SUPPORTS_RUNTIME_INTRINSICS - [Theory] - [MemberData(nameof(Seeds))] - public void FromRgbAvx2(int seed) => - this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed); -#endif - [Theory] [MemberData(nameof(Seeds))] public void FromYccKBasic(int seed) => @@ -271,6 +243,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } #if SUPPORTS_RUNTIME_INTRINSICS + [Theory] + [MemberData(nameof(Seeds))] + public void FromYCbCrAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromYCbCrAvx(8), 3, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromCmykAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromCmykAvx(8), 4, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromGrayscaleAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromGrayscaleAvx(8), 1, seed); + + [Theory] + [MemberData(nameof(Seeds))] + public void FromRgbAvx2(int seed) => + this.TestConverter(new JpegColorConverterBase.FromRgbAvx(8), 3, seed); + [Theory] [MemberData(nameof(Seeds))] public void FromYccKAvx2(int seed) => From 816c754657acfea4d6f8a381da0a714819d82716 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 11 Dec 2021 09:38:00 +0300 Subject: [PATCH 212/228] Removed obsolete Vector4Pair --- src/ImageSharp/Common/Tuples/Vector4Pair.cs | 82 --------------------- 1 file changed, 82 deletions(-) delete mode 100644 src/ImageSharp/Common/Tuples/Vector4Pair.cs diff --git a/src/ImageSharp/Common/Tuples/Vector4Pair.cs b/src/ImageSharp/Common/Tuples/Vector4Pair.cs deleted file mode 100644 index 6294a6177..000000000 --- a/src/ImageSharp/Common/Tuples/Vector4Pair.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Numerics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace SixLabors.ImageSharp.Tuples -{ - /// - /// Its faster to process multiple Vector4-s together, so let's pair them! - /// On AVX2 this pair should be convertible to of ! - /// TODO: Investigate defining this as union with an Octet.OfSingle type. - /// - [StructLayout(LayoutKind.Sequential)] - internal struct Vector4Pair - { - public Vector4 A; - - public Vector4 B; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void MultiplyInplace(float value) - { - this.A *= value; - this.B *= value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(Vector4 value) - { - this.A += value; - this.B += value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void AddInplace(ref Vector4Pair other) - { - this.A += other.A; - this.B += other.B; - } - - /// . - /// Downscale method, specific to Jpeg color conversion. Works only if Vector{float}.Count == 4! /// TODO: Move it somewhere else. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscalePreVector8(float downscaleFactor) - { - ref Vector a = ref Unsafe.As>(ref this.A); - a = a.FastRound(); - - ref Vector b = ref Unsafe.As>(ref this.B); - b = b.FastRound(); - - // Downscale by 1/factor - var scale = new Vector4(1 / downscaleFactor); - this.A *= scale; - this.B *= scale; - } - - /// - /// AVX2-only Downscale method, specific to Jpeg color conversion. - /// TODO: Move it somewhere else. - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RoundAndDownscaleVector8(float downscaleFactor) - { - ref Vector self = ref Unsafe.As>(ref this); - Vector v = self; - v = v.FastRound(); - - // Downscale by 1/factor - v *= new Vector(1 / downscaleFactor); - self = v; - } - - public override string ToString() - { - return $"{nameof(Vector4Pair)}({this.A}, {this.B})"; - } - } -} From 21cf5b42c17e59147db51abeaa7b90672d69d339 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 11 Dec 2021 09:56:49 +0300 Subject: [PATCH 213/228] Fixed merging errors --- .../ColorConverters/JpegColorConverterBase.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs index 5537113a3..808ca687b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverterBase.cs @@ -206,12 +206,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters this.ComponentCount = componentBuffers.Count; - this.Component0 = componentBuffers[0].GetRowSpan(row); + this.Component0 = componentBuffers[0].DangerousGetRowSpan(row); // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span.Empty; + this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span.Empty; } /// @@ -223,12 +223,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters { DebugGuard.MustBeGreaterThan(processors.Count, 0, nameof(processors)); - this.Component0 = componentBuffers[0].DangerousGetRowSpan(row); + this.ComponentCount = processors.Count; + + this.Component0 = processors[0].GetColorBufferRowSpan(row); // In case of grayscale, Component1 and Component2 point to Component0 memory area - this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].DangerousGetRowSpan(row) : this.Component0; - this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].DangerousGetRowSpan(row) : this.Component0; - this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].DangerousGetRowSpan(row) : Span.Empty; + this.Component1 = this.ComponentCount > 1 ? processors[1].GetColorBufferRowSpan(row) : this.Component0; + this.Component2 = this.ComponentCount > 2 ? processors[2].GetColorBufferRowSpan(row) : this.Component0; + this.Component3 = this.ComponentCount > 3 ? processors[3].GetColorBufferRowSpan(row) : Span.Empty; } internal ComponentValues( From f7fc4c6c027c7748d392a62cd7300f91d9b1f975 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 11 Dec 2021 15:45:24 +0100 Subject: [PATCH 214/228] Use native integers as loop counters --- .../Common/Helpers/BitOperations.cs | 26 +++++++ .../Helpers/Shuffle/IComponentShuffle.cs | 46 ++++++------ .../PixelImplementations/Abgr32.cs | 8 ++- .../PixelImplementations/Bgr24.cs | 7 +- .../Abgr32.PixelOperations.Generated.cs | 42 +++++------ .../Argb32.PixelOperations.Generated.cs | 42 +++++------ .../Bgr24.PixelOperations.Generated.cs | 42 +++++------ .../Bgra32.PixelOperations.Generated.cs | 42 +++++------ .../Bgra5551.PixelOperations.Generated.cs | 72 +++++++++---------- .../L16.PixelOperations.Generated.cs | 72 +++++++++---------- .../Generated/L8.PixelOperations.Generated.cs | 72 +++++++++---------- .../La16.PixelOperations.Generated.cs | 72 +++++++++---------- .../La32.PixelOperations.Generated.cs | 72 +++++++++---------- .../Rgb24.PixelOperations.Generated.cs | 42 +++++------ .../Rgb48.PixelOperations.Generated.cs | 72 +++++++++---------- .../Rgba32.PixelOperations.Generated.cs | 42 +++++------ .../Rgba64.PixelOperations.Generated.cs | 72 +++++++++---------- .../Generated/_Common.ttinclude | 6 +- 18 files changed, 440 insertions(+), 409 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/BitOperations.cs diff --git a/src/ImageSharp/Common/Helpers/BitOperations.cs b/src/ImageSharp/Common/Helpers/BitOperations.cs new file mode 100644 index 000000000..b4b6c890c --- /dev/null +++ b/src/ImageSharp/Common/Helpers/BitOperations.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Runtime.CompilerServices; + +#if !NETCOREAPP3_1 +namespace SixLabors.ImageSharp.Common.Helpers +{ + /// + /// Polyfill for System.Numerics.BitOperations class, introduced in .NET Core 3.0. + /// + /// + public static class BitOperations + { + /// + /// Rotates the specified value left by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to roate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) + => (value << offset) | (value >> (32 - offset)); + } +} +#endif diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 4c02deb2d..89bac8e10 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -3,8 +3,10 @@ using System; using System.Buffers.Binary; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using SixLabors.ImageSharp.Common.Helpers; // The JIT can detect and optimize rotation idioms ROTL (Rotate Left) // and ROTR (Rotate Right) emitting efficient CPU instructions: @@ -97,15 +99,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (int i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (int)i); // packed = [W Z Y X] // ROTL(8, packed) = [Z Y X W] - Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); + Unsafe.Add(ref dBase, (int)i) = (packed << 8) | (packed >> 24); } } } @@ -123,15 +125,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (int i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (int)i); // packed = [W Z Y X] // REVERSE(packedArgb) = [X Y Z W] - Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); + Unsafe.Add(ref dBase, (int)i) = BinaryPrimitives.ReverseEndianness(packed); } } } @@ -149,15 +151,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (int i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (int)i); // packed = [W Z Y X] // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, i) = (packed >> 8) | (packed << 24); + Unsafe.Add(ref dBase, (int)i) = (packed >> 8) | (packed << 24); } } } @@ -175,11 +177,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (int i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (int)i); // packed = [W Z Y X] // tmp1 = [W 0 Y 0] @@ -188,9 +190,9 @@ namespace SixLabors.ImageSharp // tmp1 + tmp3 = [W X Y Z] uint tmp1 = packed & 0xFF00FF00; uint tmp2 = packed & 0x00FF00FF; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, (int)i) = tmp1 + tmp3; } } } @@ -208,11 +210,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - int n = source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (int i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (int)i); // packed = [W Z Y X] // tmp1 = [0 Z 0 X] @@ -221,9 +223,9 @@ namespace SixLabors.ImageSharp // tmp1 + tmp3 = [Y Z W X] uint tmp1 = packed & 0x00FF00FF; uint tmp2 = packed & 0xFF00FF00; - uint tmp3 = (tmp2 << 16) | (tmp2 >> 16); + uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, (int)i) = tmp1 + tmp3; } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs index 0a95fc1ff..ca8f5c144 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Abgr32.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -214,9 +215,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromBgr24(Bgr24 source) { - this.R = source.R; - this.G = source.G; - this.B = source.B; + // We can assign the Bgr24 value directly to last three bytes of this instance. + ref byte thisRef = ref Unsafe.As(ref this); + ref byte thisRefFromB = ref Unsafe.AddByteOffset(ref thisRef, new IntPtr(1)); + Unsafe.As(ref thisRefFromB) = source; this.A = byte.MaxValue; } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs index 882306928..81c178348 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/Bgr24.cs @@ -189,9 +189,10 @@ namespace SixLabors.ImageSharp.PixelFormats [MethodImpl(InliningOptions.ShortMethod)] public void FromAbgr32(Abgr32 source) { - this.R = source.R; - this.G = source.G; - this.B = source.B; + // We can assign this instances value directly to last three bytes of the Abgr32. + ref byte sourceRef = ref Unsafe.As(ref source); + ref byte sourceRefFromB = ref Unsafe.AddByteOffset(ref sourceRef, new IntPtr(1)); + this = Unsafe.As(ref sourceRefFromB); } /// diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs index 6386d315c..1333fbb6a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs @@ -204,10 +204,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromAbgr32(sp); } @@ -224,10 +224,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromAbgr32(sp); } @@ -244,10 +244,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromAbgr32(sp); } @@ -264,10 +264,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromAbgr32(sp); } @@ -284,10 +284,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromAbgr32(sp); } @@ -304,10 +304,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromAbgr32(sp); } @@ -324,10 +324,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Abgr32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromAbgr32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs index 0f183df0d..fa646512d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs @@ -204,10 +204,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromArgb32(sp); } @@ -224,10 +224,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromArgb32(sp); } @@ -244,10 +244,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromArgb32(sp); } @@ -264,10 +264,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromArgb32(sp); } @@ -284,10 +284,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromArgb32(sp); } @@ -304,10 +304,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromArgb32(sp); } @@ -324,10 +324,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Argb32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromArgb32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs index ea08c6391..b193251ce 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs @@ -204,10 +204,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgr24(sp); } @@ -224,10 +224,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgr24(sp); } @@ -244,10 +244,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgr24(sp); } @@ -264,10 +264,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgr24(sp); } @@ -284,10 +284,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgr24(sp); } @@ -304,10 +304,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgr24(sp); } @@ -324,10 +324,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgr24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgr24(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs index 0ec9a552c..4426bc0d9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs @@ -204,10 +204,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra32(sp); } @@ -224,10 +224,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra32(sp); } @@ -244,10 +244,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra32(sp); } @@ -264,10 +264,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra32(sp); } @@ -284,10 +284,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra32(sp); } @@ -304,10 +304,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra32(sp); } @@ -324,10 +324,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs index 877ce1a6f..90ace9181 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -130,10 +130,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -150,10 +150,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -190,10 +190,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -210,10 +210,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -230,10 +230,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -250,10 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } @@ -270,10 +270,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Bgra5551 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromBgra5551(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs index 94eb3229b..8c5b2e99a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -130,10 +130,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -150,10 +150,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -190,10 +190,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -210,10 +210,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -230,10 +230,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -250,10 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } @@ -270,10 +270,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL16(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs index a748590f7..f61dc25ec 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -130,10 +130,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -150,10 +150,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -190,10 +190,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -210,10 +210,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -230,10 +230,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -250,10 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } @@ -270,10 +270,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref L8 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromL8(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs index f47cd6c40..feec77618 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -130,10 +130,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -150,10 +150,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -190,10 +190,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -210,10 +210,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -230,10 +230,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -250,10 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } @@ -270,10 +270,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La16 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa16(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs index f0c2c3323..ddf94261e 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -130,10 +130,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -150,10 +150,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -190,10 +190,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -210,10 +210,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -230,10 +230,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -250,10 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } @@ -270,10 +270,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref La32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromLa32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs index 75f677cdd..e89b17f72 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs @@ -204,10 +204,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb24(sp); } @@ -224,10 +224,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb24(sp); } @@ -244,10 +244,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb24(sp); } @@ -264,10 +264,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb24(sp); } @@ -284,10 +284,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb24(sp); } @@ -304,10 +304,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb24(sp); } @@ -324,10 +324,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb24 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb24(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs index 22b96fd5a..017124ef1 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -130,10 +130,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -150,10 +150,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -190,10 +190,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -210,10 +210,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -230,10 +230,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -250,10 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } @@ -270,10 +270,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgb48 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgb48(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs index 093182c82..483c3f69c 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs @@ -185,10 +185,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba32(sp); } @@ -205,10 +205,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba32(sp); } @@ -225,10 +225,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba32(sp); } @@ -245,10 +245,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba32(sp); } @@ -265,10 +265,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba32(sp); } @@ -285,10 +285,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba64 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba32(sp); } @@ -305,10 +305,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba32 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs index ce1b53e66..a82fb26b4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs @@ -50,10 +50,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Argb32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -70,10 +70,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Abgr32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -90,10 +90,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgr24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -110,10 +110,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -130,10 +130,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L8 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref L8 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -150,10 +150,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref L16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref L16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La16 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref La16 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -190,10 +190,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref La32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref La32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -210,10 +210,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb24 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -230,10 +230,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgba32 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -250,10 +250,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Rgb48 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } @@ -270,10 +270,10 @@ namespace SixLabors.ImageSharp.PixelFormats ref Rgba64 sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref Bgra5551 destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); dp.FromRgba64(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index b6b0a14a7..9da66a5a9 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude @@ -104,10 +104,10 @@ using SixLabors.ImageSharp.PixelFormats.Utils; ref <#=fromPixelType#> sourceRef = ref MemoryMarshal.GetReference(sourcePixels); ref <#=toPixelType#> destRef = ref MemoryMarshal.GetReference(destinationPixels); - for (int i = 0; i < sourcePixels.Length; i++) + for (nint i = 0; i < sourcePixels.Length; i++) { - ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, i); - ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, i); + ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, (int)i); + ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, (int)i); dp.From<#=fromPixelType#>(sp); } From 94735822f597bd2289da9d36560ade86d8d51ce0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 11 Dec 2021 15:56:33 +0100 Subject: [PATCH 215/228] Add ActiveIssue attribute to AllocateSingleMemoryOwner_Finalization_ReturnsToPool --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 80c8cc6a0..c4bb03d02 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -310,6 +310,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } + [ActiveIssue("https://github.com/SixLabors/ImageSharp/issues/1887", TestPlatforms.OSX)] [Theory] [InlineData(300)] // Group of single SharedArrayPoolBuffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer From 1ce4429001258ab7d548f7f88eedfa3a60bd474b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 11 Dec 2021 23:24:27 +0100 Subject: [PATCH 216/228] Optimizing native integer usage --- .../Common/Helpers/BitOperations.cs | 2 +- .../Helpers/Shuffle/IComponentShuffle.cs | 40 ++++++++-------- .../Abgr32.PixelOperations.Generated.cs | 28 +++++------ .../Argb32.PixelOperations.Generated.cs | 28 +++++------ .../Bgr24.PixelOperations.Generated.cs | 28 +++++------ .../Bgra32.PixelOperations.Generated.cs | 28 +++++------ .../Bgra5551.PixelOperations.Generated.cs | 48 +++++++++---------- .../L16.PixelOperations.Generated.cs | 48 +++++++++---------- .../Generated/L8.PixelOperations.Generated.cs | 48 +++++++++---------- .../La16.PixelOperations.Generated.cs | 48 +++++++++---------- .../La32.PixelOperations.Generated.cs | 48 +++++++++---------- .../Rgb24.PixelOperations.Generated.cs | 28 +++++------ .../Rgb48.PixelOperations.Generated.cs | 48 +++++++++---------- .../Rgba32.PixelOperations.Generated.cs | 28 +++++------ .../Rgba64.PixelOperations.Generated.cs | 48 +++++++++---------- .../Generated/_Common.ttinclude | 4 +- 16 files changed, 275 insertions(+), 275 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/BitOperations.cs b/src/ImageSharp/Common/Helpers/BitOperations.cs index b4b6c890c..af56efd56 100644 --- a/src/ImageSharp/Common/Helpers/BitOperations.cs +++ b/src/ImageSharp/Common/Helpers/BitOperations.cs @@ -3,7 +3,7 @@ using System.Runtime.CompilerServices; -#if !NETCOREAPP3_1 +#if !NETCOREAPP3_1_OR_GREATER namespace SixLabors.ImageSharp.Common.Helpers { /// diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 89bac8e10..1922c787c 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -99,15 +99,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + nint n = (nint)source.Length / 4; - for (nuint i = 0; i < n; i++) + for (nint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (int)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // ROTL(8, packed) = [Z Y X W] - Unsafe.Add(ref dBase, (int)i) = (packed << 8) | (packed >> 24); + Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); } } } @@ -125,15 +125,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + nint n = (nint)source.Length / 4; - for (nuint i = 0; i < n; i++) + for (nint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (int)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // REVERSE(packedArgb) = [X Y Z W] - Unsafe.Add(ref dBase, (int)i) = BinaryPrimitives.ReverseEndianness(packed); + Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); } } } @@ -151,15 +151,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + nint n = (nint)source.Length / 4; - for (nuint i = 0; i < n; i++) + for (nint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (int)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, (int)i) = (packed >> 8) | (packed << 24); + Unsafe.Add(ref dBase, i) = (packed >> 8) | (packed << 24); } } } @@ -177,11 +177,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + nint n = (nint)source.Length / 4; - for (nuint i = 0; i < n; i++) + for (nint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (int)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // tmp1 = [W 0 Y 0] @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp uint tmp2 = packed & 0x00FF00FF; uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, (int)i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } } } @@ -210,11 +210,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + nint n = (nint)source.Length / 4; - for (nuint i = 0; i < n; i++) + for (nint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (int)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // tmp1 = [0 Z 0 X] @@ -225,7 +225,7 @@ namespace SixLabors.ImageSharp uint tmp2 = packed & 0xFF00FF00; uint tmp3 = BitOperations.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, (int)i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } } } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs index 1333fbb6a..026025f76 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Abgr32.PixelOperations.Generated.cs @@ -206,8 +206,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromAbgr32(sp); } @@ -226,8 +226,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromAbgr32(sp); } @@ -246,8 +246,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromAbgr32(sp); } @@ -266,8 +266,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromAbgr32(sp); } @@ -286,8 +286,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromAbgr32(sp); } @@ -306,8 +306,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromAbgr32(sp); } @@ -326,8 +326,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Abgr32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromAbgr32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs index fa646512d..4b9f68a68 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Argb32.PixelOperations.Generated.cs @@ -206,8 +206,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } @@ -226,8 +226,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } @@ -246,8 +246,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } @@ -266,8 +266,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } @@ -286,8 +286,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } @@ -306,8 +306,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } @@ -326,8 +326,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Argb32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Argb32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromArgb32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs index b193251ce..c85e4ee3b 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgr24.PixelOperations.Generated.cs @@ -206,8 +206,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } @@ -226,8 +226,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } @@ -246,8 +246,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } @@ -266,8 +266,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } @@ -286,8 +286,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } @@ -306,8 +306,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } @@ -326,8 +326,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgr24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgr24(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs index 4426bc0d9..ed1366b0a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra32.PixelOperations.Generated.cs @@ -206,8 +206,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } @@ -226,8 +226,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } @@ -246,8 +246,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } @@ -266,8 +266,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } @@ -286,8 +286,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } @@ -306,8 +306,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } @@ -326,8 +326,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs index 90ace9181..0dfa46c14 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Bgra5551.PixelOperations.Generated.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -112,8 +112,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -172,8 +172,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -192,8 +192,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -212,8 +212,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -252,8 +252,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Bgra5551 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromBgra5551(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs index 8c5b2e99a..19e0548ae 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L16.PixelOperations.Generated.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -112,8 +112,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -172,8 +172,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -192,8 +192,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -212,8 +212,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -252,8 +252,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromL16(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs index f61dc25ec..8997449d3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/L8.PixelOperations.Generated.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -112,8 +112,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -172,8 +172,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -192,8 +192,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -212,8 +212,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -252,8 +252,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref L8 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref L8 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromL8(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs index feec77618..8166862fe 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La16.PixelOperations.Generated.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -112,8 +112,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -172,8 +172,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -192,8 +192,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -212,8 +212,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -252,8 +252,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La16 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La16 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa16(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs index ddf94261e..32a0f24e3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/La32.PixelOperations.Generated.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -112,8 +112,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -172,8 +172,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -192,8 +192,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -212,8 +212,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -252,8 +252,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref La32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref La32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromLa32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs index e89b17f72..53a82989d 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb24.PixelOperations.Generated.cs @@ -206,8 +206,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } @@ -226,8 +226,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } @@ -246,8 +246,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } @@ -266,8 +266,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } @@ -286,8 +286,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } @@ -306,8 +306,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } @@ -326,8 +326,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb24 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb24(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs index 017124ef1..d1c5ab2e3 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgb48.PixelOperations.Generated.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -112,8 +112,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -172,8 +172,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -192,8 +192,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -212,8 +212,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -252,8 +252,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgb48 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgb48(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs index 483c3f69c..2608a74fc 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba32.PixelOperations.Generated.cs @@ -187,8 +187,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } @@ -207,8 +207,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } @@ -227,8 +227,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } @@ -247,8 +247,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } @@ -267,8 +267,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } @@ -287,8 +287,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba64 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba64 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } @@ -307,8 +307,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba32 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba32(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs index a82fb26b4..f6445039a 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/Rgba64.PixelOperations.Generated.cs @@ -52,8 +52,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Argb32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Argb32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Abgr32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Abgr32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -92,8 +92,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgr24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgr24 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -112,8 +112,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -132,8 +132,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L8 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref L8 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -152,8 +152,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref L16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref L16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -172,8 +172,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La16 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La16 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -192,8 +192,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref La32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref La32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -212,8 +212,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb24 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb24 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -232,8 +232,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgba32 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgba32 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -252,8 +252,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Rgb48 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Rgb48 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } @@ -272,8 +272,8 @@ namespace SixLabors.ImageSharp.PixelFormats for (nint i = 0; i < sourcePixels.Length; i++) { - ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref Bgra5551 dp = ref Unsafe.Add(ref destRef, (int)i); + ref Rgba64 sp = ref Unsafe.Add(ref sourceRef, i); + ref Bgra5551 dp = ref Unsafe.Add(ref destRef, i); dp.FromRgba64(sp); } diff --git a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude index 9da66a5a9..9a6ddd7d4 100644 --- a/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude +++ b/src/ImageSharp/PixelFormats/PixelImplementations/PixelOperations/Generated/_Common.ttinclude @@ -106,8 +106,8 @@ using SixLabors.ImageSharp.PixelFormats.Utils; for (nint i = 0; i < sourcePixels.Length; i++) { - ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, (int)i); - ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, (int)i); + ref <#=fromPixelType#> sp = ref Unsafe.Add(ref sourceRef, i); + ref <#=toPixelType#> dp = ref Unsafe.Add(ref destRef, i); dp.From<#=fromPixelType#>(sp); } From 36105c902d5aef3ba6f60e11b2e8c26daf0c4117 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 12 Dec 2021 23:39:41 +1100 Subject: [PATCH 217/228] Remove netstandard 1.3 target --- README.md | 2 +- .../Tiff/Compression/Compressors/DeflateCompressor.cs | 7 ------- src/ImageSharp/ImageSharp.csproj | 6 +++--- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ab16bbb76..fdf14b496 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations. -Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. +Built against [.NET Standard 2.0](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios. ## License diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs index 225036f90..c240c06ef 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs @@ -43,15 +43,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors } int size = (int)this.memoryStream.Position; - -#if !NETSTANDARD1_3 byte[] buffer = this.memoryStream.GetBuffer(); this.Output.Write(buffer, 0, size); -#else - this.memoryStream.SetLength(size); - this.memoryStream.Position = 0; - this.memoryStream.CopyTo(this.Output); -#endif } /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index afe10b1f9..0a75c06db 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -23,12 +23,12 @@ - net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472 - net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472 @@ -38,7 +38,7 @@ - netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472 + netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472 From e04ea46df19e47513d9cbffa87c4f0c43aae561a Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 12 Dec 2021 13:41:16 +0100 Subject: [PATCH 218/228] Use Numerics class as polyfill for BitOperations --- .../Common/Helpers/BitOperations.cs | 26 ----------------- src/ImageSharp/Common/Helpers/Numerics.cs | 28 +++++++++++++++++++ .../Helpers/Shuffle/IComponentShuffle.cs | 4 +-- 3 files changed, 30 insertions(+), 28 deletions(-) delete mode 100644 src/ImageSharp/Common/Helpers/BitOperations.cs diff --git a/src/ImageSharp/Common/Helpers/BitOperations.cs b/src/ImageSharp/Common/Helpers/BitOperations.cs deleted file mode 100644 index af56efd56..000000000 --- a/src/ImageSharp/Common/Helpers/BitOperations.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Runtime.CompilerServices; - -#if !NETCOREAPP3_1_OR_GREATER -namespace SixLabors.ImageSharp.Common.Helpers -{ - /// - /// Polyfill for System.Numerics.BitOperations class, introduced in .NET Core 3.0. - /// - /// - public static class BitOperations - { - /// - /// Rotates the specified value left by the specified number of bits. - /// - /// The value to rotate. - /// The number of bits to roate with. - /// The rotated value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static uint RotateLeft(uint value, int offset) - => (value << offset) | (value >> (32 - offset)); - } -} -#endif diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index fa0af823d..df81c39e7 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -907,5 +907,33 @@ namespace SixLabors.ImageSharp /// Divisor value. /// Ceiled division result. public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor; + + /// + /// Rotates the specified value left by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to roate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeft(uint value, int offset) + { +#if SUPPORTS_BITOPERATIONS + return BitOperations.RotateLeft(value, offset); +#else + return RotateLeftSoftwareFallback(value, offset); +#endif + } + +#if !SUPPORTS_BITOPERATIONS + /// + /// Rotates the specified value left by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to roate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateLeftSoftwareFallback(uint value, int offset) + => (value << offset) | (value >> (32 - offset)); +#endif } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index 1922c787c..da60928d3 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp // tmp1 + tmp3 = [W X Y Z] uint tmp1 = packed & 0xFF00FF00; uint tmp2 = packed & 0x00FF00FF; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); + uint tmp3 = Numerics.RotateLeft(tmp2, 16); Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp // tmp1 + tmp3 = [Y Z W X] uint tmp1 = packed & 0x00FF00FF; uint tmp2 = packed & 0xFF00FF00; - uint tmp3 = BitOperations.RotateLeft(tmp2, 16); + uint tmp3 = Numerics.RotateLeft(tmp2, 16); Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } From d7ff98b19b08640c3c4e8dccc858ce397c4d3f26 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 12 Dec 2021 14:29:12 +0100 Subject: [PATCH 219/228] Add ActiveIssue attribute to AllocateMemoryGroup_Finalization_ReturnsToPool test --- .../Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index c4bb03d02..13bf98b50 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -253,6 +253,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } + [ActiveIssue("https://github.com/SixLabors/ImageSharp/issues/1887", TestPlatforms.OSX)] [Theory] [InlineData(300)] // Group of single SharedArrayPoolBuffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer From bae4feef1b915a65bec3fb8542a349c9bced454b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 12 Dec 2021 14:51:31 +0100 Subject: [PATCH 220/228] Change skipping tests on OSX --- .../UniformUnmanagedPoolMemoryAllocatorTests.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs index 13bf98b50..45a7cc278 100644 --- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs +++ b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs @@ -253,13 +253,18 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [ActiveIssue("https://github.com/SixLabors/ImageSharp/issues/1887", TestPlatforms.OSX)] [Theory] [InlineData(300)] // Group of single SharedArrayPoolBuffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) { + if (TestEnvironment.IsOSX) + { + // Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 + return; + } + if (!TestEnvironment.RunsOnCI) { // This may fail in local runs resulting in high memory load. @@ -311,12 +316,17 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators } } - [ActiveIssue("https://github.com/SixLabors/ImageSharp/issues/1887", TestPlatforms.OSX)] [Theory] [InlineData(300)] // Group of single SharedArrayPoolBuffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) { + if (TestEnvironment.IsOSX) + { + // Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 + return; + } + if (!TestEnvironment.RunsOnCI) { // This may fail in local runs resulting in high memory load. From ed611ca48cb11f6421c1a4603c8eacf43f4fa22e Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 12 Dec 2021 16:51:11 +0100 Subject: [PATCH 221/228] Revert submodule changes --- .gitattributes | 8 ++++++-- shared-infrastructure | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitattributes b/.gitattributes index 70ced6903..355b64dce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -87,7 +87,6 @@ *.eot binary *.exe binary *.otf binary -*.pbm binary *.pdf binary *.ppt binary *.pptx binary @@ -95,7 +94,6 @@ *.snk binary *.ttc binary *.ttf binary -*.wbmp binary *.woff binary *.woff2 binary *.xls binary @@ -126,3 +124,9 @@ *.dds filter=lfs diff=lfs merge=lfs -text *.ktx filter=lfs diff=lfs merge=lfs -text *.ktx2 filter=lfs diff=lfs merge=lfs -text +*.pam filter=lfs diff=lfs merge=lfs -text +*.pbm filter=lfs diff=lfs merge=lfs -text +*.pgm filter=lfs diff=lfs merge=lfs -text +*.ppm filter=lfs diff=lfs merge=lfs -text +*.pnm filter=lfs diff=lfs merge=lfs -text +*.wbmp filter=lfs diff=lfs merge=lfs -text diff --git a/shared-infrastructure b/shared-infrastructure index a042aba17..59ce17f5a 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 +Subproject commit 59ce17f5a4e1f956811133f41add7638e74c2836 From 1afcb4cd84b072316c287fc1dc216bc23c097835 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 13 Dec 2021 09:53:44 +1100 Subject: [PATCH 222/228] Update Guard.cs --- src/ImageSharp/Common/Helpers/Guard.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Guard.cs b/src/ImageSharp/Common/Helpers/Guard.cs index 0b5cc21cb..0f6efcb3c 100644 --- a/src/ImageSharp/Common/Helpers/Guard.cs +++ b/src/ImageSharp/Common/Helpers/Guard.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -#if NETSTANDARD1_3 -using System.Reflection; -#endif using System.Runtime.CompilerServices; using SixLabors.ImageSharp; @@ -22,11 +19,7 @@ namespace SixLabors [MethodImpl(InliningOptions.ShortMethod)] public static void MustBeValueType(TValue value, string parameterName) { - if (value.GetType() -#if NETSTANDARD1_3 - .GetTypeInfo() -#endif - .IsValueType) + if (value.GetType().IsValueType) { return; } From 752520cf08119897e3b05b22d36c780d39006a32 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 13 Dec 2021 01:36:35 +0100 Subject: [PATCH 223/228] Use nuint in IComponentShuffle again --- src/ImageSharp/Common/Helpers/Numerics.cs | 32 +++++++++++++- .../Helpers/Shuffle/IComponentShuffle.cs | 42 +++++++++---------- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index df81c39e7..513db0c44 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -912,7 +912,7 @@ namespace SixLabors.ImageSharp /// Rotates the specified value left by the specified number of bits. /// /// The value to rotate. - /// The number of bits to roate with. + /// The number of bits to rotate with. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint RotateLeft(uint value, int offset) @@ -929,11 +929,39 @@ namespace SixLabors.ImageSharp /// Rotates the specified value left by the specified number of bits. /// /// The value to rotate. - /// The number of bits to roate with. + /// The number of bits to rotate with. /// The rotated value. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint RotateLeftSoftwareFallback(uint value, int offset) => (value << offset) | (value >> (32 - offset)); #endif + + /// + /// Rotates the specified value right by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to rotate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateRight(uint value, int offset) + { +#if SUPPORTS_BITOPERATIONS + return BitOperations.RotateRight(value, offset); +#else + return RotateRightSoftwareFallback(value, offset); +#endif + } + +#if !SUPPORTS_BITOPERATIONS + /// + /// Rotates the specified value right by the specified number of bits. + /// + /// The value to rotate. + /// The number of bits to rotate with. + /// The rotated value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint RotateRightSoftwareFallback(uint value, int offset) + => (value >> offset) | (value << (32 - offset)); +#endif } } diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index da60928d3..a1224a767 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -3,10 +3,8 @@ using System; using System.Buffers.Binary; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using SixLabors.ImageSharp.Common.Helpers; // The JIT can detect and optimize rotation idioms ROTL (Rotate Left) // and ROTR (Rotate Right) emitting efficient CPU instructions: @@ -99,15 +97,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nint n = (nint)source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (nint i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); // packed = [W Z Y X] // ROTL(8, packed) = [Z Y X W] - Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); + Unsafe.Add(ref dBase, (IntPtr)(uint)i) = (packed << 8) | (packed >> 24); } } } @@ -125,15 +123,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nint n = (nint)source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (nint i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); // packed = [W Z Y X] // REVERSE(packedArgb) = [X Y Z W] - Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); + Unsafe.Add(ref dBase, (IntPtr)(uint)i) = BinaryPrimitives.ReverseEndianness(packed); } } } @@ -151,15 +149,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nint n = (nint)source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (nint i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); // packed = [W Z Y X] // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, i) = (packed >> 8) | (packed << 24); + Unsafe.Add(ref dBase, (IntPtr)(uint)i) = Numerics.RotateRight(packed, 8); } } } @@ -177,11 +175,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nint n = (nint)source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (nint i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); // packed = [W Z Y X] // tmp1 = [W 0 Y 0] @@ -192,7 +190,7 @@ namespace SixLabors.ImageSharp uint tmp2 = packed & 0x00FF00FF; uint tmp3 = Numerics.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, (IntPtr)(uint)i) = tmp1 + tmp3; } } } @@ -210,11 +208,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nint n = (nint)source.Length / 4; + nuint n = (nuint)(uint)source.Length / 4; - for (nint i = 0; i < n; i++) + for (nuint i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, i); + uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); // packed = [W Z Y X] // tmp1 = [0 Z 0 X] @@ -225,7 +223,7 @@ namespace SixLabors.ImageSharp uint tmp2 = packed & 0xFF00FF00; uint tmp3 = Numerics.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, (IntPtr)(uint)i) = tmp1 + tmp3; } } } From ae84ff67e9f6e6f1d2ef5e30645d241098f4a277 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 13 Dec 2021 16:45:40 +0100 Subject: [PATCH 224/228] Break if there are more then 65534 IFD directories, fixes #1897 --- src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs | 12 ++++++++++-- src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 8b2c6bd3a..26cb12d42 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -18,9 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff private uint nextIfdOffset; + private const int DirectoryMax = 65534; + // used for sequential read big values (actual for multiframe big files) // todo: different tags can link to the same data (stream offset) - investigate - private readonly SortedList lazyLoaders = new SortedList(new DuplicateKeyComparer()); + private readonly SortedList lazyLoaders = new(new DuplicateKeyComparer()); public DirectoryReader(Stream stream) => this.stream = stream; @@ -48,7 +50,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff { return ByteOrder.LittleEndian; } - else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) + + if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian) { return ByteOrder.BigEndian; } @@ -67,6 +70,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.nextIfdOffset = reader.NextIfdOffset; readers.Add(reader); + + if (readers.Count >= DirectoryMax) + { + TiffThrowHelper.ThrowImageFormatException("TIFF image contains too many directories"); + } } // Sequential reading big values. diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index 7cd508c09..be4f59bff 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.lazyLoaders = lazyLoaders; } - public List Values { get; } = new List(); + public List Values { get; } = new(); public uint NextIfdOffset { get; private set; } From f45e184f87547ad4f031dabffad2c1962f16be07 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 13 Dec 2021 16:52:37 +0100 Subject: [PATCH 225/228] Add test for issue #1891 --- .../Formats/Tiff/TiffDecoderTests.cs | 12 ++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Tiff/Issues/Issue1891.tiff | 3 +++ 3 files changed, 16 insertions(+) create mode 100644 tests/Images/Input/Tiff/Issues/Issue1891.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index b7bd5275d..f8256ead9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -380,6 +380,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void TiffDecoder_CanDecode_JpegCompressed(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider, useExactComparer: false); + // https://github.com/SixLabors/ImageSharp/issues/1891 + [Theory] + [WithFile(Issues1891, PixelTypes.Rgba32)] + public void TiffDecoder_ThrowsException_WithTooManyDirectories(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws( + () => + { + using (provider.GetImage(TiffDecoder)) + { + } + }); + [Theory] [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] public void DecodeMultiframe(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 930b550a2..f43c0c9bd 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -840,6 +840,7 @@ namespace SixLabors.ImageSharp.Tests public const string Flower32BitGrayPredictorLittleEndian = "Tiff/flower-minisblack-32_lsb_deflate_predictor.tiff"; public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; + public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; diff --git a/tests/Images/Input/Tiff/Issues/Issue1891.tiff b/tests/Images/Input/Tiff/Issues/Issue1891.tiff new file mode 100644 index 000000000..df2a5e798 --- /dev/null +++ b/tests/Images/Input/Tiff/Issues/Issue1891.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2779c7fb2c2ad858e0d7efcfffd594cc6fb2846e0475a2998a3cda50f289b9b +size 307 From 07d3653ef508a8819e0520be7aaf63e576c9db91 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 14 Dec 2021 11:41:08 +1100 Subject: [PATCH 226/228] Remove QA from config [skip ci] --- .github/ISSUE_TEMPLATE/config.yml | 3 --- .github/ISSUE_TEMPLATE/oss-bug-report.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1326c72e8..62a8bf2b4 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,5 @@ blank_issues_enabled: false contact_links: - - name: Ask a Question - url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AQ%26A - about: Ask a question about this project. - name: Feature Request url: https://github.com/SixLabors/ImageSharp/discussions?discussions_q=category%3AIdeas about: Share ideas for new features for this project. diff --git a/.github/ISSUE_TEMPLATE/oss-bug-report.md b/.github/ISSUE_TEMPLATE/oss-bug-report.md index e0d37de53..9e9567a99 100644 --- a/.github/ISSUE_TEMPLATE/oss-bug-report.md +++ b/.github/ISSUE_TEMPLATE/oss-bug-report.md @@ -1,6 +1,6 @@ --- name: "OSS : Bug Report" -about: Create a report to help us improve the project. +about: Create a report to help us improve the project. OSS Issues are not guaranteed to be triaged. labels: needs triage --- From f6b83835ad560e5c9ebdb546ff4458c419290cc2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 14 Dec 2021 22:28:54 +1100 Subject: [PATCH 227/228] Update PngDecoderCore.cs --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 53 +++++++++----------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index bc2bb9529..ffaa9d567 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Formats.Png private readonly bool ignoreMetadata; /// - /// Gets or sets a value indicating whether to read the header and trns chunks only. + /// Gets or sets a value indicating whether to read the IHDR and tRNS chunks only. /// private readonly bool colorMetadataOnly; @@ -82,16 +82,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// private byte[] paletteAlpha; - /// - /// A value indicating whether the header chunk has been reached. - /// - private bool isHeaderChunkReached; - - /// - /// A value indicating whether the end chunk has been reached. - /// - private bool isEndChunkReached; - /// /// Previous scanline processed. /// @@ -161,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Png Image image = null; try { - while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) + while (this.TryReadChunk(out PngChunk chunk)) { try { @@ -215,8 +205,7 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.End: - this.isEndChunkReached = true; - break; + goto EOF; case PngChunkType.ProprietaryApple: PngThrowHelper.ThrowInvalidChunkType("Proprietary Apple PNG detected! This PNG file is not conform to the specification and cannot be decoded."); break; @@ -228,6 +217,7 @@ namespace SixLabors.ImageSharp.Formats.Png } } + EOF: if (image is null) { PngThrowHelper.ThrowNoData(); @@ -251,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.currentStream.Skip(8); try { - while (!this.isEndChunkReached && this.TryReadChunk(out PngChunk chunk)) + while (this.TryReadChunk(out PngChunk chunk)) { try { @@ -259,7 +249,6 @@ namespace SixLabors.ImageSharp.Formats.Png { case PngChunkType.Header: this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan()); - this.isHeaderChunkReached = true; break; case PngChunkType.Physical: if (this.colorMetadataOnly) @@ -280,6 +269,13 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.Data: + + // Spec says tRNS must be before IDAT so safe to exit. + if (this.colorMetadataOnly) + { + goto EOF; + } + this.SkipChunkDataAndCrc(chunk); break; case PngChunkType.Transparency: @@ -288,10 +284,9 @@ namespace SixLabors.ImageSharp.Formats.Png this.paletteAlpha = alpha; this.AssignTransparentMarkers(alpha, pngMetadata); - if (this.colorMetadataOnly && this.isHeaderChunkReached) + if (this.colorMetadataOnly) { - // Quick exit - this.isEndChunkReached = true; + goto EOF; } break; @@ -338,8 +333,7 @@ namespace SixLabors.ImageSharp.Formats.Png break; case PngChunkType.End: - this.isEndChunkReached = true; - break; + goto EOF; } } finally @@ -347,19 +341,20 @@ namespace SixLabors.ImageSharp.Formats.Png chunk.Data?.Dispose(); // Data is rented in ReadChunkData() } } + + EOF: + if (this.header.Width == 0 && this.header.Height == 0) + { + PngThrowHelper.ThrowNoHeader(); + } + + return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); } finally { this.scanline?.Dispose(); this.previousScanline?.Dispose(); } - - if (this.header.Width == 0 && this.header.Height == 0) - { - PngThrowHelper.ThrowNoHeader(); - } - - return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); } /// @@ -1212,7 +1207,7 @@ namespace SixLabors.ImageSharp.Formats.Png PngChunkType type = this.ReadChunkType(); // NOTE: Reading the Data chunk is the responsible of the caller - // If we're reading color metadata only we're only interested in the Header and Transparancy chunks. + // If we're reading color metadata only we're only interested in the IHDR and tRNS chunks. // We can skip all other chunk data in the stream for better performance. if (type == PngChunkType.Data || (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency)) { From 3ee73a89c535e379ec36e6edf032332a7d466bf5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 16 Dec 2021 17:50:55 +0100 Subject: [PATCH 228/228] Update IComponentShuffle back to int again --- .../Helpers/Shuffle/IComponentShuffle.cs | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs index a1224a767..049c61185 100644 --- a/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs +++ b/src/ImageSharp/Common/Helpers/Shuffle/IComponentShuffle.cs @@ -97,15 +97,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + int n = source.Length / 4; - for (nuint i = 0; i < n; i++) + for (int i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // ROTL(8, packed) = [Z Y X W] - Unsafe.Add(ref dBase, (IntPtr)(uint)i) = (packed << 8) | (packed >> 24); + Unsafe.Add(ref dBase, i) = (packed << 8) | (packed >> 24); } } } @@ -123,15 +123,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + int n = source.Length / 4; - for (nuint i = 0; i < n; i++) + for (int i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // REVERSE(packedArgb) = [X Y Z W] - Unsafe.Add(ref dBase, (IntPtr)(uint)i) = BinaryPrimitives.ReverseEndianness(packed); + Unsafe.Add(ref dBase, i) = BinaryPrimitives.ReverseEndianness(packed); } } } @@ -149,15 +149,15 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + int n = source.Length / 4; - for (nuint i = 0; i < n; i++) + for (int i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // ROTR(8, packedArgb) = [Y Z W X] - Unsafe.Add(ref dBase, (IntPtr)(uint)i) = Numerics.RotateRight(packed, 8); + Unsafe.Add(ref dBase, i) = Numerics.RotateRight(packed, 8); } } } @@ -175,11 +175,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + int n = source.Length / 4; - for (nuint i = 0; i < n; i++) + for (int i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // tmp1 = [W 0 Y 0] @@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp uint tmp2 = packed & 0x00FF00FF; uint tmp3 = Numerics.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, (IntPtr)(uint)i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } } } @@ -208,11 +208,11 @@ namespace SixLabors.ImageSharp { ref uint sBase = ref Unsafe.As(ref MemoryMarshal.GetReference(source)); ref uint dBase = ref Unsafe.As(ref MemoryMarshal.GetReference(dest)); - nuint n = (nuint)(uint)source.Length / 4; + int n = source.Length / 4; - for (nuint i = 0; i < n; i++) + for (int i = 0; i < n; i++) { - uint packed = Unsafe.Add(ref sBase, (IntPtr)(uint)i); + uint packed = Unsafe.Add(ref sBase, i); // packed = [W Z Y X] // tmp1 = [0 Z 0 X] @@ -223,7 +223,7 @@ namespace SixLabors.ImageSharp uint tmp2 = packed & 0xFF00FF00; uint tmp3 = Numerics.RotateLeft(tmp2, 16); - Unsafe.Add(ref dBase, (IntPtr)(uint)i) = tmp1 + tmp3; + Unsafe.Add(ref dBase, i) = tmp1 + tmp3; } } }