From 7c1a1afc6193f55c6d007490a6167c7f0ebe3aa8 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 7 Aug 2021 22:56:02 +0200 Subject: [PATCH 001/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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/511] 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 fdfb547936fdcb55ca71707699e67e8d7b3d0301 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 11 Sep 2021 22:14:11 +0300 Subject: [PATCH 016/511] Support long8 types for exif reader --- src/ImageSharp/Common/Helpers/Numerics.cs | 32 ++++ .../Metadata/Profiles/Exif/ExifDataType.cs | 17 +- .../Metadata/Profiles/Exif/ExifDataTypes.cs | 5 +- .../Metadata/Profiles/Exif/ExifReader.cs | 147 +++++++++++++++++- .../Profiles/Exif/Values/ExifLong8.cs | 53 +++++++ .../Profiles/Exif/Values/ExifLong8Array.cs | 27 ++++ .../Profiles/Exif/Values/ExifNumber.cs | 17 ++ .../Profiles/Exif/Values/ExifNumberArray.cs | 37 +++++ .../Profiles/Exif/Values/ExifSignedLong8.cs | 26 ++++ .../Exif/Values/ExifSignedLong8Array.cs | 22 +++ .../Profiles/Exif/Values/ExifValues.cs | 4 +- src/ImageSharp/Primitives/Number.cs | 75 +++++++-- 12 files changed, 442 insertions(+), 20 deletions(-) create mode 100644 src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs create mode 100644 src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs create mode 100644 src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs create mode 100644 src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index ba5c588ca..885443e86 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -226,6 +226,38 @@ namespace SixLabors.ImageSharp return value; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ulong Clamp(ulong value, ulong min, ulong max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long Clamp(long value, long min, long max) + { + if (value > max) + { + return max; + } + + if (value < min) + { + return min; + } + + return value; + } + /// /// Returns the value clamped to the inclusive range of min and max. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs index 733eb4a79..0185afb50 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs @@ -80,6 +80,21 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// /// Reference to an IFD (32-bit (4-byte) unsigned integer). /// - Ifd = 13 + Ifd = 13, + + /// + /// A 64-bit (8-byte) unsigned integer. + /// + Long8 = 16, + + /// + /// A 64-bit (8-byte) signed integer (2's complement notation). + /// + SignedLong8 = 17, + + /// + /// Reference to an IFD (64-bit (8-byte) unsigned integer). + /// + Ifd8 = 18, } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs index 4f75999bb..ee30c6b08 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs @@ -32,10 +32,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifDataType.Long: case ExifDataType.SignedLong: case ExifDataType.SingleFloat: + case ExifDataType.Ifd: return 4; case ExifDataType.DoubleFloat: case ExifDataType.Rational: - case ExifDataType.SignedRational: + case ExifDataType.Long8: + case ExifDataType.SignedLong8: + case ExifDataType.Ifd8: return 8; default: throw new NotSupportedException(dataType.ToString()); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 6e671b3ec..5c26fde7b 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return values; } - protected override void RegisterExtLoader(uint offset, Action loader) => this.loaders.Add(loader); + protected override void RegisterExtLoader(ulong offset, Action loader) => this.loaders.Add(loader); private void GetThumbnail(uint offset) { @@ -87,6 +87,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif internal abstract class BaseExifReader { private readonly byte[] offsetBuffer = new byte[4]; + private readonly byte[] offsetBuffer8 = new byte[8]; + private readonly byte[] buf8 = new byte[8]; private readonly byte[] buf4 = new byte[4]; private readonly byte[] buf2 = new byte[2]; @@ -119,7 +121,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public bool IsBigEndian { get; protected set; } - protected abstract void RegisterExtLoader(uint offset, Action loader); + protected abstract void RegisterExtLoader(ulong offset, Action loader); /// /// Reads the values to the values collection. @@ -155,6 +157,35 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } + protected void ReadValues64(List values, ulong offset) + { + if (offset > (ulong)this.data.Length) + { + return; + } + + this.Seek(offset); + ulong count = this.ReadUInt64(); + + for (ulong i = 0; i < count; i++) + { + this.ReadValue64(values); + } + } + + protected void ReadSubIfd64(List values) + { + if (this.exifOffset != 0) + { + this.ReadValues64(values, this.exifOffset); + } + + if (this.gpsOffset != 0) + { + this.ReadValues64(values, this.gpsOffset); + } + } + private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) { int dataTypeSize = (int)ExifDataTypes.GetSize(dataType); @@ -186,7 +217,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return Encoding.UTF8.GetString(buffer); } - private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, uint numberOfComponents) + private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, ulong numberOfComponents) { if (buffer.Length == 0) { @@ -269,6 +300,20 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } return ToArray(dataType, buffer, this.ConvertToSingle); + case ExifDataType.Long8: + if (numberOfComponents == 1) + { + return this.ConvertToUInt64(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToUInt64); + case ExifDataType.SignedLong8: + if (numberOfComponents == 1) + { + return this.ConvertToInt64(buffer); + } + + return ToArray(dataType, buffer, this.ConvertToUInt64); case ExifDataType.Undefined: if (numberOfComponents == 1) { @@ -349,6 +394,69 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } + private void ReadValue64(List values) + { + if ((this.data.Length - this.data.Position) < 20) + { + return; + } + + var tag = (ExifTagValue)this.ReadUInt16(); + ExifDataType dataType = EnumUtils.Parse(this.ReadUInt16(), ExifDataType.Unknown); + + ulong numberOfComponents = this.ReadUInt64(); + + this.TryReadSpan(this.offsetBuffer8); + + if (dataType == ExifDataType.Unknown) + { + return; + } + + if (dataType == ExifDataType.Undefined && numberOfComponents == 0) + { + numberOfComponents = 8; + } + + // The StripOffsets, StripByteCounts, TileOffsets, and TileByteCounts tags are allowed to have the datatype TIFF_LONG8 in BigTIFF. + // Old datatypes TIFF_LONG, and TIFF_SHORT where allowed in the TIFF 6.0 specification, are still valid in BigTIFF, too. + // https://www.awaresystems.be/imaging/tiff/bigtiff.html + ExifValue exifValue = exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + + if (exifValue is null) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return; + } + + ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); + if (size > 8) + { + ulong newOffset = this.ConvertToUInt64(this.offsetBuffer8); + if (newOffset > ulong.MaxValue || (newOffset + size) > (ulong)this.data.Length) + { + this.AddInvalidTag(new UnkownExifTag(tag)); + return; + } + + this.RegisterExtLoader(newOffset, () => + { + byte[] dataBuffer = new byte[size]; + this.Seek(newOffset); + if (this.TryReadSpan(dataBuffer)) + { + object value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); + this.Add(values, exifValue, value); + } + }); + } + else + { + object value = this.ConvertValue(dataType, ((Span)this.offsetBuffer8).Slice(0, (int)size), numberOfComponents); + this.Add(values, exifValue, value); + } + } + private void Add(IList values, IExifValue exif, object value) { if (!exif.TrySetValue(value)) @@ -383,8 +491,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private void AddInvalidTag(ExifTag tag) => (this.invalidTags ??= new List()).Add(tag); - private void Seek(long pos) - => this.data.Seek(pos, SeekOrigin.Begin); + private void Seek(ulong pos) + => this.data.Seek((long)pos, SeekOrigin.Begin); private bool TryReadSpan(Span span) { @@ -398,6 +506,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return read == length; } + protected ulong ReadUInt64() => + this.TryReadSpan(this.buf8) + ? this.ConvertToUInt64(this.buf8) + : default; + // Known as Long in Exif Specification. protected uint ReadUInt32() => this.TryReadSpan(this.buf4) @@ -408,6 +521,30 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ? this.ConvertToShort(this.buf2) : default; + private long ConvertToInt64(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + return this.IsBigEndian + ? BinaryPrimitives.ReadInt64BigEndian(buffer) + : BinaryPrimitives.ReadInt64LittleEndian(buffer); + } + + private ulong ConvertToUInt64(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } + + return this.IsBigEndian + ? BinaryPrimitives.ReadUInt64BigEndian(buffer) + : BinaryPrimitives.ReadUInt64LittleEndian(buffer); + } + private double ConvertToDouble(ReadOnlySpan buffer) { if (buffer.Length < 8) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs new file mode 100644 index 000000000..47da962f3 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs @@ -0,0 +1,53 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifLong8 : ExifValue + { + public ExifLong8(ExifTag tag) + : base(tag) + { + } + + public ExifLong8(ExifTagValue tag) + : base(tag) + { + } + + private ExifLong8(ExifLong8 value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Long8; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case long intValue: + if (intValue >= 0) + { + this.Value = (ulong)intValue; + return true; + } + + return false; + default: + return false; + } + } + + public override IExifValue DeepClone() => new ExifLong8(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs new file mode 100644 index 000000000..3cf59adac --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -0,0 +1,27 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifLong8Array : ExifArrayValue + { + public ExifLong8Array(ExifTag tag) + : base(tag) + { + } + + public ExifLong8Array(ExifTagValue tag) + : base(tag) + { + } + + private ExifLong8Array(ExifLong8Array value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.Long8; + + public override IExifValue DeepClone() => new ExifLong8Array(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs index 9e206b23d..d16de4d22 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs @@ -21,6 +21,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { get { + if (this.Value > uint.MaxValue) + { + return ExifDataType.Long8; + } + if (this.Value > ushort.MaxValue) { return ExifDataType.Long; @@ -41,6 +46,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif switch (value) { + case long longValue: + if (longValue >= 0) + { + this.Value = (ulong)longValue; + return true; + } + + return false; + case ulong ulongValue: + this.Value = ulongValue; + + return true; case int intValue: if (intValue >= uint.MinValue) { diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 2d3a93aed..15b29dcec 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -26,6 +26,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif for (int i = 0; i < this.Value.Length; i++) { + if (this.Value[i] > uint.MaxValue) + { + return ExifDataType.Long8; + } + if (this.Value[i] > ushort.MaxValue) { return ExifDataType.Long; @@ -45,6 +50,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif switch (value) { + case long val: + return this.SetSingle(val); + case ulong val: + return this.SetSingle(val); case int val: return this.SetSingle(val); case uint val: @@ -53,6 +62,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return this.SetSingle(val); case ushort val: return this.SetSingle(val); + case long[] array: + return this.SetArray(array); + case ulong[] array: + return this.SetArray(array); case int[] array: return this.SetArray(array); case uint[] array: @@ -74,6 +87,30 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return true; } + private bool SetArray(long[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(ulong[] values) + { + var numbers = new Number[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = values[i]; + } + + this.Value = numbers; + return true; + } + private bool SetArray(int[] values) { var numbers = new Number[values.Length]; diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs new file mode 100644 index 000000000..8362dcf2c --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Globalization; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedLong8 : ExifValue + { + public ExifSignedLong8(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedLong8(ExifSignedLong8 value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedLong8; + + protected override string StringValue => this.Value.ToString(CultureInfo.InvariantCulture); + + public override IExifValue DeepClone() => new ExifSignedLong8(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs new file mode 100644 index 000000000..34ce20c8f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifSignedLong8Array.cs @@ -0,0 +1,22 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Metadata.Profiles.Exif +{ + internal sealed class ExifSignedLong8Array : ExifArrayValue + { + public ExifSignedLong8Array(ExifTagValue tag) + : base(tag) + { + } + + private ExifSignedLong8Array(ExifSignedLong8Array value) + : base(value) + { + } + + public override ExifDataType DataType => ExifDataType.SignedLong8; + + public override IExifValue DeepClone() => new ExifSignedLong8Array(this); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs index af1eee2dc..7e5b35f49 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag); - public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, uint numberOfComponents) => Create(tag, dataType, numberOfComponents != 1); + public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, ulong numberOfComponents) => Create(tag, dataType, numberOfComponents != 1); public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, bool isArray) { @@ -19,10 +19,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifDataType.DoubleFloat: return isArray ? (ExifValue)new ExifDoubleArray(tag) : new ExifDouble(tag); case ExifDataType.SingleFloat: return isArray ? (ExifValue)new ExifFloatArray(tag) : new ExifFloat(tag); case ExifDataType.Long: return isArray ? (ExifValue)new ExifLongArray(tag) : new ExifLong(tag); + case ExifDataType.Long8: return isArray ? (ExifValue)new ExifLong8Array(tag) : new ExifLong8(tag); case ExifDataType.Rational: return isArray ? (ExifValue)new ExifRationalArray(tag) : new ExifRational(tag); case ExifDataType.Short: return isArray ? (ExifValue)new ExifShortArray(tag) : new ExifShort(tag); case ExifDataType.SignedByte: return isArray ? (ExifValue)new ExifSignedByteArray(tag) : new ExifSignedByte(tag); case ExifDataType.SignedLong: return isArray ? (ExifValue)new ExifSignedLongArray(tag) : new ExifSignedLong(tag); + case ExifDataType.SignedLong8: return isArray ? (ExifValue)new ExifSignedLong8Array(tag) : new ExifSignedLong8(tag); case ExifDataType.SignedRational: return isArray ? (ExifValue)new ExifSignedRationalArray(tag) : new ExifSignedRational(tag); case ExifDataType.SignedShort: return isArray ? (ExifValue)new ExifSignedShortArray(tag) : new ExifSignedShort(tag); case ExifDataType.Ascii: return new ExifString(tag); diff --git a/src/ImageSharp/Primitives/Number.cs b/src/ImageSharp/Primitives/Number.cs index e33d2344a..690b72d79 100644 --- a/src/ImageSharp/Primitives/Number.cs +++ b/src/ImageSharp/Primitives/Number.cs @@ -14,19 +14,19 @@ namespace SixLabors.ImageSharp public struct Number : IEquatable, IComparable { [FieldOffset(0)] - private readonly int signedValue; + private readonly long signedValue; [FieldOffset(0)] - private readonly uint unsignedValue; + private readonly ulong unsignedValue; - [FieldOffset(4)] + [FieldOffset(8)] private readonly bool isSigned; /// /// Initializes a new instance of the struct. /// /// The value of the number. - public Number(int value) + public Number(long value) : this() { this.signedValue = value; @@ -37,30 +37,70 @@ namespace SixLabors.ImageSharp /// Initializes a new instance of the struct. /// /// The value of the number. - public Number(uint value) + public Number(ulong value) : this() { this.unsignedValue = value; this.isSigned = false; } + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(long value) => new Number(value); + + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(ulong value) => new Number(value); + /// /// Converts the specified to an instance of this type. /// /// The value. - public static implicit operator Number(int value) => new Number(value); + public static implicit operator Number(int value) => new Number((long)value); /// /// Converts the specified to an instance of this type. /// /// The value. - public static implicit operator Number(uint value) => new Number(value); + public static implicit operator Number(uint value) => new Number((ulong)value); + + /// + /// Converts the specified to an instance of this type. + /// + /// The value. + public static implicit operator Number(short value) => new Number((long)value); /// /// Converts the specified to an instance of this type. /// /// The value. - public static implicit operator Number(ushort value) => new Number((uint)value); + public static implicit operator Number(ushort value) => new Number((ulong)value); + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert. + public static explicit operator long(Number number) + { + return number.isSigned + ? number.signedValue + : (long)Numerics.Clamp(number.unsignedValue, 0, long.MaxValue); + } + + /// + /// Converts the specified to an instance of this type. + /// + /// The to convert. + public static explicit operator ulong(Number number) + { + return number.isSigned + ? (ulong)Numerics.Clamp(number.signedValue, 0, long.MaxValue) + : number.unsignedValue; + } /// /// Converts the specified to a . @@ -69,8 +109,8 @@ namespace SixLabors.ImageSharp public static explicit operator int(Number number) { return number.isSigned - ? number.signedValue - : (int)Numerics.Clamp(number.unsignedValue, 0, int.MaxValue); + ? (int)Numerics.Clamp(number.signedValue, int.MinValue, int.MaxValue) + : (int)Numerics.Clamp(number.unsignedValue, 0, (uint)int.MaxValue); } /// @@ -80,8 +120,19 @@ namespace SixLabors.ImageSharp public static explicit operator uint(Number number) { return number.isSigned - ? (uint)Numerics.Clamp(number.signedValue, 0, int.MaxValue) - : number.unsignedValue; + ? (uint)Numerics.Clamp(number.signedValue, uint.MinValue, uint.MaxValue) + : (uint)Numerics.Clamp(number.unsignedValue, uint.MinValue, uint.MaxValue); + } + + /// + /// Converts the specified to a . + /// + /// The to convert. + public static explicit operator short(Number number) + { + return number.isSigned + ? (short)Numerics.Clamp(number.signedValue, short.MinValue, short.MaxValue) + : (short)Numerics.Clamp(number.unsignedValue, 0, (ushort)short.MaxValue); } /// From 4b3ae005a6eca9783a8b238bbb72b6192d371b4c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 11 Sep 2021 22:26:32 +0300 Subject: [PATCH 017/511] Implement support BigTiff format decoding --- .../Tiff/Compression/TiffBaseDecompressor.cs | 10 ++-- .../Formats/Tiff/Constants/TiffConstants.cs | 10 ++++ .../Formats/Tiff/Ifd/DirectoryReader.cs | 24 ++++---- .../Formats/Tiff/Ifd/EntryReader.cs | 58 +++++++++++++------ .../Formats/Tiff/TiffDecoderCore.cs | 5 ++ .../Formats/Tiff/TiffDecoderOptionsParser.cs | 13 +++-- 6 files changed, 81 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs index 28459d0c5..f1faa6c67 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -35,17 +35,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// The number of bytes to read from the input stream. /// The height of the strip. /// The output buffer for uncompressed data. - public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, int stripHeight, Span buffer) + public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer) { - if (stripByteCount > int.MaxValue) + if (stripOffset > long.MaxValue || stripByteCount > long.MaxValue) { - TiffThrowHelper.ThrowImageFormatException("The StripByteCount value is too big."); + TiffThrowHelper.ThrowImageFormatException("The StripOffset or StripByteCount value is too big."); } - stream.Seek(stripOffset, SeekOrigin.Begin); + stream.Seek((long)stripOffset, SeekOrigin.Begin); this.Decompress(stream, (int)stripByteCount, stripHeight, buffer); - if (stripOffset + stripByteCount < stream.Position) + if ((long)stripOffset + (long)stripByteCount < stream.Position) { TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip."); } diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs index b54545141..b5548c707 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs @@ -35,6 +35,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// public const ushort HeaderMagicNumber = 42; + /// + /// The big tiff header magic number + /// + public const ushort BigTiffHeaderMagicNumber = 43; + + /// + /// The big tiff bytesize of offsets value. + /// + public const ushort BigTiffBytesize = 8; + /// /// RowsPerStrip default value, which is effectively infinity. /// diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 8b2c6bd3a..bd673cf3a 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -16,11 +16,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff { private readonly Stream stream; - private uint nextIfdOffset; + private ulong nextIfdOffset; // 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 SortedList(new DuplicateKeyComparer()); public DirectoryReader(Stream stream) => this.stream = stream; @@ -36,13 +36,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff public IEnumerable Read() { this.ByteOrder = ReadByteOrder(this.stream); - this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader(); - return this.ReadIfds(); + var headerReader = new HeaderReader(this.stream, this.ByteOrder); + headerReader.ReadFileHeader(); + + this.nextIfdOffset = headerReader.FirstIfdOffset; + + return this.ReadIfds(headerReader.IsBigTiff); } private static ByteOrder ReadByteOrder(Stream stream) { - var headerBytes = new byte[2]; + byte[] headerBytes = new byte[2]; stream.Read(headerBytes, 0, 2); if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) { @@ -56,13 +60,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff throw TiffThrowHelper.ThrowInvalidHeader(); } - private IEnumerable ReadIfds() + private IEnumerable ReadIfds(bool isBigTiff) { var readers = new List(); - while (this.nextIfdOffset != 0 && this.nextIfdOffset < this.stream.Length) + while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length) { - var reader = new EntryReader(this.stream, this.ByteOrder, this.nextIfdOffset, this.lazyLoaders); - reader.ReadTags(); + var reader = new EntryReader(this.stream, this.ByteOrder, this.lazyLoaders); + reader.ReadTags(isBigTiff, this.nextIfdOffset); this.nextIfdOffset = reader.NextIfdOffset; @@ -75,7 +79,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff loader(); } - var list = new List(); + var list = new List(readers.Count); foreach (EntryReader reader in readers) { var profile = new ExifProfile(reader.Values, reader.InvalidTags); diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index 123a64cc1..7884242ef 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -12,31 +12,38 @@ namespace SixLabors.ImageSharp.Formats.Tiff { internal class EntryReader : BaseExifReader { - private readonly uint startOffset; + private readonly SortedList lazyLoaders; - private readonly SortedList lazyLoaders; - - public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList lazyLoaders) + public EntryReader(Stream stream, ByteOrder byteOrder, SortedList lazyLoaders) : base(stream) { this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - this.startOffset = ifdOffset; this.lazyLoaders = lazyLoaders; } public List Values { get; } = new List(); - public uint NextIfdOffset { get; private set; } + public ulong NextIfdOffset { get; private set; } - public void ReadTags() + public void ReadTags(bool isBigTiff, ulong ifdOffset) { - this.ReadValues(this.Values, this.startOffset); - this.NextIfdOffset = this.ReadUInt32(); + if (!isBigTiff) + { + this.ReadValues(this.Values, (uint)ifdOffset); + this.NextIfdOffset = this.ReadUInt32(); + + this.ReadSubIfd(this.Values); + } + else + { + this.ReadValues64(this.Values, ifdOffset); + this.NextIfdOffset = this.ReadUInt64(); - this.ReadSubIfd(this.Values); + this.ReadSubIfd64(this.Values); + } } - protected override void RegisterExtLoader(uint offset, Action reader) => + protected override void RegisterExtLoader(ulong offset, Action reader) => this.lazyLoaders.Add(offset, reader); } @@ -46,20 +53,35 @@ namespace SixLabors.ImageSharp.Formats.Tiff : base(stream) => this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - public uint FirstIfdOffset { get; private set; } + public bool IsBigTiff { get; private set; } + + public ulong FirstIfdOffset { get; private set; } - public uint ReadFileHeader() + public void ReadFileHeader() { ushort magic = this.ReadUInt16(); - if (magic != TiffConstants.HeaderMagicNumber) + if (magic == TiffConstants.HeaderMagicNumber) { - TiffThrowHelper.ThrowInvalidHeader(); + this.IsBigTiff = false; + this.FirstIfdOffset = this.ReadUInt32(); + return; + } + else if (magic == TiffConstants.BigTiffHeaderMagicNumber) + { + this.IsBigTiff = true; + + ushort bytesize = this.ReadUInt16(); + ushort reserve = this.ReadUInt16(); + if (bytesize == TiffConstants.BigTiffBytesize && reserve == 0) + { + this.FirstIfdOffset = this.ReadUInt64(); + return; + } } - this.FirstIfdOffset = this.ReadUInt32(); - return this.FirstIfdOffset; + TiffThrowHelper.ThrowInvalidHeader(); } - protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotSupportedException(); + protected override void RegisterExtLoader(ulong offset, Action reader) => throw new NotSupportedException(); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 55af87005..922c1f174 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -432,6 +432,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); } + if (((ulong)width.Value) > int.MaxValue) + { + TiffThrowHelper.ThrowImageFormatException("Too big ImageWidth value"); + } + return (int)width.Value; } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index a8420fabb..a187d444a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -24,12 +24,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The IFD entries container to read the image format information for current frame. public static void VerifyAndParse(this TiffDecoderCore options, ExifProfile exifProfile, TiffFrameMetadata frameMetadata) { - if (exifProfile.GetValue(ExifTag.TileOffsets)?.Value != null) + if (exifProfile.GetValueInternal(ExifTag.TileOffsets) is not null || exifProfile.GetValueInternal(ExifTag.TileByteCounts) is not null) { TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); } - if (exifProfile.GetValue(ExifTag.ExtraSamples)?.Value != null) + if (exifProfile.GetValueInternal(ExifTag.ExtraSamples) is not null) { TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); } @@ -95,12 +95,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff private static void VerifyRequiredFieldsArePresent(ExifProfile exifProfile, TiffFrameMetadata frameMetadata) { - if (exifProfile.GetValue(ExifTag.StripOffsets) == null) + if (exifProfile.GetValueInternal(ExifTag.StripOffsets) is null) { TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); } - if (exifProfile.GetValue(ExifTag.StripByteCounts) == null) + if (exifProfile.GetValueInternal(ExifTag.StripByteCounts) is null) { TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); } @@ -384,7 +384,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff private static void ParseCompression(this TiffDecoderCore options, TiffCompression? compression, ExifProfile exifProfile) { - switch (compression) + // Default 1 (No compression) https://www.awaresystems.be/imaging/tiff/tifftags/compression.html + switch (compression ?? TiffCompression.None) { case TiffCompression.None: { @@ -441,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff default: { - TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported"); + TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format '{compression}' is not supported"); break; } } From ae577bba8738a3c497915958de7948768fb63945 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 11 Sep 2021 22:27:03 +0300 Subject: [PATCH 018/511] Add BigTiff decoder tests --- .../Formats/Tiff/BigTiffDecoderTests.cs | 35 +++ .../Formats/Tiff/TiffDecoderBaseTester.cs | 32 +++ .../Formats/Tiff/TiffDecoderTests.cs | 22 +- tests/ImageSharp.Tests/TestImages.cs | 15 ++ tests/Images/Input/Tiff/BigTiff/BigTIFF.tif | 3 + .../Images/Input/Tiff/BigTiff/BigTIFFLong.tif | 3 + .../Input/Tiff/BigTiff/BigTIFFLong8.tif | 3 + .../Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif | 3 + .../Input/Tiff/BigTiff/BigTIFFMotorola.tif | 3 + .../BigTiff/BigTIFFMotorolaLongStrips.tif | 3 + .../Input/Tiff/BigTiff/BigTIFFSamples.html | 239 ++++++++++++++++++ .../Input/Tiff/BigTiff/BigTIFFSubIFD4.tif | 3 + .../Input/Tiff/BigTiff/BigTIFFSubIFD8.tif | 3 + tests/Images/Input/Tiff/BigTiff/Classic.tif | 3 + tests/Images/Input/Tiff/BigTiff/readme.md | 3 + 15 files changed, 353 insertions(+), 20 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs create mode 100644 tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFLong.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFLong8.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFMotorola.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFMotorolaLongStrips.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD4.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD8.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/Classic.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/readme.md diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs new file mode 100644 index 000000000..fc507513b --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -0,0 +1,35 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming +using System; +using SixLabors.ImageSharp.PixelFormats; + +using Xunit; + +using static SixLabors.ImageSharp.Tests.TestImages.BigTiff; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Collection("RunSerial")] + [Trait("Format", "Tiff")] + [Trait("Format", "BigTiff")] + public class BigTiffDecoderTests : TiffDecoderBaseTester + { + [Theory] + [WithFile(BigTIFF, PixelTypes.Rgba32)] + [WithFile(BigTIFFLong, PixelTypes.Rgba32)] + [WithFile(BigTIFFLong8, PixelTypes.Rgba32)] + [WithFile(BigTIFFMotorola, PixelTypes.Rgba32)] + [WithFile(BigTIFFMotorolaLongStrips, PixelTypes.Rgba32)] + [WithFile(BigTIFFSubIFD4, PixelTypes.Rgba32)] + [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] + public void TiffDecoder_CanDecode(TestImageProvider provider) + where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); + + [Theory] + [WithFile(BigTIFFLong8Tiles, PixelTypes.Rgba32)] + public void ThrowsNotSupported(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs new file mode 100644 index 000000000..ff70eaf8a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +// ReSharper disable InconsistentNaming +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + public abstract class TiffDecoderBaseTester + { + protected static TiffDecoder TiffDecoder => new TiffDecoder(); + + protected static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); + + protected static void TestTiffDecoder(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + image.DebugSave(provider); + image.CompareToOriginal( + provider, + useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder ?? ReferenceDecoder); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index b64d22991..e49f45932 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -4,12 +4,9 @@ // ReSharper disable InconsistentNaming using System; using System.IO; -using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; -using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; @@ -19,20 +16,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [Collection("RunSerial")] [Trait("Format", "Tiff")] - public class TiffDecoderTests + public class TiffDecoderTests : TiffDecoderBaseTester { public static readonly string[] MultiframeTestImages = Multiframes; - private static TiffDecoder TiffDecoder => new TiffDecoder(); - - private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); - [Theory] [WithFile(RgbUncompressedTiled, PixelTypes.Rgba32)] [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] public void ThrowsNotSupported(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); + where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); [Theory] [InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] @@ -397,16 +390,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff image.DebugSaveMultiFrame(provider); image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, ReferenceDecoder); } - - private static void TestTiffDecoder(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(TiffDecoder); - image.DebugSave(provider); - image.CompareToOriginal( - provider, - useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), - referenceDecoder ?? ReferenceDecoder); - } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d1a6624af..9df6472a1 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -672,5 +672,20 @@ namespace SixLabors.ImageSharp.Tests public static readonly string[] Metadata = { SampleMetadata }; } + + public static class BigTiff + { + public const string BasePath = "Tiff/BigTiff/"; + + public const string BigTIFF = BasePath + "BigTIFF.tif"; + public const string BigTIFFLong = BasePath + "BigTIFFLong.tif"; + public const string BigTIFFLong8 = BasePath + "BigTIFFLong8.tif"; + public const string BigTIFFLong8Tiles = BasePath + "BigTIFFLong8Tiles.tif"; + public const string BigTIFFMotorola = BasePath + "BigTIFFMotorola.tif"; + public const string BigTIFFMotorolaLongStrips = BasePath + "BigTIFFMotorolaLongStrips.tif"; + + public const string BigTIFFSubIFD4 = BasePath + "BigTIFFSubIFD4.tif"; + public const string BigTIFFSubIFD8 = BasePath + "BigTIFFSubIFD8.tif"; + } } } diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF.tif new file mode 100644 index 000000000..b27834479 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ddb202145a9bce7670cc372ee578de5a53cd52cc8d5ae8a9ebdc9f9c4f4a7e81 +size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong.tif new file mode 100644 index 000000000..b390c0a97 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90178643a159ec50335e9314836df924233debeb100763af0f77cd1be3cf58ab +size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8.tif new file mode 100644 index 000000000..e1815c750 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b38e61ccb01e10e26fb10c335fc6fca9ad087b0fb0df833e54bb02d1246e20d5 +size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif new file mode 100644 index 000000000..c69218948 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFLong8Tiles.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a2d92b0c430cefc390f13961e00950ee7246b013335594dd249ba823eb3c3fdb +size 12564 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorola.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorola.tif new file mode 100644 index 000000000..68c2b3a41 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorola.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ace8a27dbed9f918993615e545a12310b84ad94bc6af8e256258e69731f1c7ce +size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorolaLongStrips.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorolaLongStrips.tif new file mode 100644 index 000000000..cc62e0362 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFMotorolaLongStrips.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c5ebdc3774955d3b47644f57bd023e764a93ca2271118152ae920b34c1784bb +size 12480 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html new file mode 100644 index 000000000..c674e57b5 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html @@ -0,0 +1,239 @@ + + + + +

+ These images were created by AWare Systems. +

+

Index

+

+ Classic.tif
+ BigTIFF.tif
+ BigTIFFMotorola.tif
+ BigTIFFLong.tif
+ BigTIFFLong8.tif
+ BigTIFFMotorolaLongStrips.tif
+ BigTIFFLong8Tiles.tif
+ BigTIFFSubIFD4.tif
+ BigTIFFSubIFD8.tif
+

+

Classic.tif

+

+ Classic.tif is a basic Classic TIFF file. All files in this package have the same actual image content, + so this TIFF file serves as a reference. +

+

+ Format: Classic TIFF
+ Byte Order: Intel
+ Ifd Offset: 12302
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 8
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Short): 12288
+

+

BigTIFF.tif

+

+ BigTIFF.tif ressembles Classic.tif as close as possible. Except that it's a BigTIFF, that is... +

+

+ Format: BigTIFF
+ Byte Order: Intel
+ Ifd Offset: 12304
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 16
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Short): 12288
+

+

BigTIFFMotorola.tif

+

+ BigTIFFMotorola.tif reverses the byte order. +

+

+ Format: BigTIFF
+ Byte Order: Motorola
+ Ifd Offset: 12304
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 16
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Short): 12288
+

+

BigTIFFLong.tif

+

+ All previous TIFFs specify DataType Short for StripOffsets and StripByteCounts tags. This BigTIFF instead specifies DataType Long, for these tags. +

+

+ Format: BigTIFF
+ Byte Order: Intel
+ Ifd Offset: 12304
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Long): 16
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Long): 12288
+

+

BigTIFFLong8.tif

+

+ This next one specifies DataType Long8, for StripOffsets and StripByteCounts tags. +

+

+ Format: BigTIFF
+ Byte Order: Intel
+ Ifd Offset: 12304
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Long8): 16
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Long8): 12288
+

+

BigTIFFMotorolaLongStrips.tif

+

+ This BigTIFF has Motorola byte order, plus, it's divided over two strips. StripOffsets and StripByteCounts tags have DataType Long, so their actual value fits inside the IFD. +

+

+ Format: BigTIFF
+ Byte Order: Motorola
+ Ifd Offset: 12304
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (2 Long): 16, 6160
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 32
+     StripByteCounts (2 Long): 6144, 6144
+

+

BigTIFFLong8Tiles.tif

+

+ BigTIFFLong8Tiles.tif is a tiled BigTIFF. TileOffsets and TileByteCounts tags specify DataType Long8. +

+

+ Format: BigTIFF
+ Byte Order: Intel
+ Ifd Offset: 12368
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     SamplesPerPixel (1 Short): 3
+     TileWidth (1 Short): 32
+     TileLength (1 Short): 32
+     TileOffsets (4 Long8): 16, 3088, 6160, 9232
+     TileByteCounts (4 Long8): 3072, 3072, 3072, 3072
+

+

BigTIFFSubIFD4.tif

+

+ This BigTIFF contains two pages, the second page showing almost the same image content as the first, except that the black square is white, and text color is black. + Both pages point to a downsample SubIFD, using SubIFDs DataType TIFF_IFD. +

+

+ Format: BigTIFF
+ Byte Order: Intel
+ Ifd Offset: 15572
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 3284
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Short): 12288
+     SubIFDs (1 IFD): 3088
+ SubIfd Offset: 3088
+     NewSubFileType (1 Long): 1
+     ImageWidth (1 Short): 32
+     ImageLength (1 Short): 32
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 16
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 32
+     StripByteCounts (1 Short): 3072
+ Ifd Offset: 31324
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 19036
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Short): 12288
+     SubIFDs (1 IFD): 18840
+ SubIfd Offset: 18840
+     NewSubFileType (1 Long): 1
+     ImageWidth (1 Short): 32
+     ImageLength (1 Short): 32
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 15768
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 32
+     StripByteCounts (1 Short): 3072
+

+

BigTIFFSubIFD8.tif

+

+ BigTIFFSubIFD4.tif is very much the same as BigTIFFSubIFD4.tif, except that the new DataType TIFF_IFD8 is used for the SubIFDs tag. +

+

+ Format: BigTIFF
+ Byte Order: Intel
+ Ifd Offset: 15572
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 3284
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Short): 12288
+     SubIFDs (1 IFD8): 3088
+ SubIfd Offset: 3088
+     NewSubFileType (1 Long): 1
+     ImageWidth (1 Short): 32
+     ImageLength (1 Short): 32
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 16
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 32
+     StripByteCounts (1 Short): 3072
+ Ifd Offset: 31324
+     ImageWidth (1 Short): 64
+     ImageLength (1 Short): 64
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 19036
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 64
+     StripByteCounts (1 Short): 12288
+     SubIFDs (1 IFD8): 18840
+ SubIfd Offset: 18840
+     NewSubFileType (1 Long): 1
+     ImageWidth (1 Short): 32
+     ImageLength (1 Short): 32
+     BitsPerSample (3 Short): 8, 8, 8
+     PhotometricInterpretation (1 Short): RGB
+     StripOffsets (1 Short): 15768
+     SamplesPerPixel (1 Short): 3
+     RowsPerStrip (1 Short): 32
+     StripByteCounts (1 Short): 3072
+

+ + diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD4.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD4.tif new file mode 100644 index 000000000..cd7bdf0b5 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD4.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85c8da46abc2284f0ddac10bd03a7c98ee51024840b7e43f41f1c6a09bc02e7e +size 31520 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD8.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD8.tif new file mode 100644 index 000000000..bb0b3bf18 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFSubIFD8.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93e5ac30f507bec7936746ad6e109631c09f9b2332081e986063219ad2452a4a +size 31520 diff --git a/tests/Images/Input/Tiff/BigTiff/Classic.tif b/tests/Images/Input/Tiff/BigTiff/Classic.tif new file mode 100644 index 000000000..1fa6be78b --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/Classic.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfa7fcf6927be5de644beb238067479e8d834d0cbe2257b00302f5dde84a1c1a +size 12404 diff --git a/tests/Images/Input/Tiff/BigTiff/readme.md b/tests/Images/Input/Tiff/BigTiff/readme.md new file mode 100644 index 000000000..be5d28906 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/readme.md @@ -0,0 +1,3 @@ +BigTIFF samples. + +Downloaded from AWARE SYSTEMS: https://www.awaresystems.be/imaging/tiff/bigtiff.html \ No newline at end of file From c5c4f64c89e6a274dbbc00aa24a49d93c21ba3d3 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 11 Sep 2021 22:28:54 +0300 Subject: [PATCH 019/511] format detector for BigTiff --- .../Formats/Tiff/TiffConfigurationModule.cs | 4 +- .../Formats/Tiff/TiffImageFormatDetector.cs | 61 +++++++++++++++++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs index e96dba207..5dfda95e8 100644 --- a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs +++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); + configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector(false)); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs index f7e6f7a99..297da2dd8 100644 --- a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs @@ -10,8 +10,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff ///
public sealed class TiffImageFormatDetector : IImageFormatDetector { + private readonly bool isBigTiff; + + /// + /// Initializes a new instance of the class. + /// + /// if set to true [is big tiff]. + public TiffImageFormatDetector(bool isBigTiff) => this.isBigTiff = isBigTiff; + /// - public int HeaderSize => 4; + public int HeaderSize => this.isBigTiff ? 8 : 4; /// public IImageFormat DetectFormat(ReadOnlySpan header) @@ -26,9 +34,52 @@ namespace SixLabors.ImageSharp.Formats.Tiff private bool IsSupportedFileFormat(ReadOnlySpan header) { - return header.Length >= this.HeaderSize && - ((header[0] == 0x49 && header[1] == 0x49 && header[2] == 0x2A && header[3] == 0x00) || // Little-endian - (header[0] == 0x4D && header[1] == 0x4D && header[2] == 0x00 && header[3] == 0x2A)); // Big-endian + if (header.Length >= this.HeaderSize) + { + if (header[0] == 0x49 && header[1] == 0x49) + { + // Little-endian + if (!this.isBigTiff) + { + if (header[2] == 0x2A && header[3] == 0x00) + { + return true; + } + } + else + { + if (header[2] == 0x2B && header[3] == 0x00 && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) + { + return true; + } + } + } + else if (header[0] == 0x4D && header[1] == 0x4D) + { + // Big-endian + if (header[2] == 0x00 && (header[3] == 0x2A || header[3] == 0x2B)) + { + return true; + } + + if (!this.isBigTiff) + { + if (header[2] == 0 && header[3] == 0x2A) + { + return true; + } + } + else + { + if (header[2] == 0 && header[3] == 0x2A && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) + { + return true; + } + } + } + } + + return false; } } -} \ No newline at end of file +} From a6c038b52c0ed3256fc04a9f559c65722a769b98 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 11:07:37 +0300 Subject: [PATCH 020/511] Revert Number changes --- src/ImageSharp/Common/Helpers/Numerics.cs | 32 -------- .../Profiles/Exif/Values/ExifNumber.cs | 17 ----- .../Profiles/Exif/Values/ExifNumberArray.cs | 37 --------- src/ImageSharp/Primitives/Number.cs | 75 +++---------------- 4 files changed, 12 insertions(+), 149 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 885443e86..ba5c588ca 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -226,38 +226,6 @@ namespace SixLabors.ImageSharp return value; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ulong Clamp(ulong value, ulong min, ulong max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long Clamp(long value, long min, long max) - { - if (value > max) - { - return max; - } - - if (value < min) - { - return min; - } - - return value; - } - /// /// Returns the value clamped to the inclusive range of min and max. /// diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs index d16de4d22..9e206b23d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumber.cs @@ -21,11 +21,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { get { - if (this.Value > uint.MaxValue) - { - return ExifDataType.Long8; - } - if (this.Value > ushort.MaxValue) { return ExifDataType.Long; @@ -46,18 +41,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif switch (value) { - case long longValue: - if (longValue >= 0) - { - this.Value = (ulong)longValue; - return true; - } - - return false; - case ulong ulongValue: - this.Value = ulongValue; - - return true; case int intValue: if (intValue >= uint.MinValue) { diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 15b29dcec..2d3a93aed 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -26,11 +26,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif for (int i = 0; i < this.Value.Length; i++) { - if (this.Value[i] > uint.MaxValue) - { - return ExifDataType.Long8; - } - if (this.Value[i] > ushort.MaxValue) { return ExifDataType.Long; @@ -50,10 +45,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif switch (value) { - case long val: - return this.SetSingle(val); - case ulong val: - return this.SetSingle(val); case int val: return this.SetSingle(val); case uint val: @@ -62,10 +53,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return this.SetSingle(val); case ushort val: return this.SetSingle(val); - case long[] array: - return this.SetArray(array); - case ulong[] array: - return this.SetArray(array); case int[] array: return this.SetArray(array); case uint[] array: @@ -87,30 +74,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return true; } - private bool SetArray(long[] values) - { - var numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } - - private bool SetArray(ulong[] values) - { - var numbers = new Number[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = values[i]; - } - - this.Value = numbers; - return true; - } - private bool SetArray(int[] values) { var numbers = new Number[values.Length]; diff --git a/src/ImageSharp/Primitives/Number.cs b/src/ImageSharp/Primitives/Number.cs index 690b72d79..e33d2344a 100644 --- a/src/ImageSharp/Primitives/Number.cs +++ b/src/ImageSharp/Primitives/Number.cs @@ -14,19 +14,19 @@ namespace SixLabors.ImageSharp public struct Number : IEquatable, IComparable { [FieldOffset(0)] - private readonly long signedValue; + private readonly int signedValue; [FieldOffset(0)] - private readonly ulong unsignedValue; + private readonly uint unsignedValue; - [FieldOffset(8)] + [FieldOffset(4)] private readonly bool isSigned; /// /// Initializes a new instance of the struct. /// /// The value of the number. - public Number(long value) + public Number(int value) : this() { this.signedValue = value; @@ -37,70 +37,30 @@ namespace SixLabors.ImageSharp /// Initializes a new instance of the struct. ///
/// The value of the number. - public Number(ulong value) + public Number(uint value) : this() { this.unsignedValue = value; this.isSigned = false; } - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(long value) => new Number(value); - - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(ulong value) => new Number(value); - /// /// Converts the specified to an instance of this type. /// /// The value. - public static implicit operator Number(int value) => new Number((long)value); + public static implicit operator Number(int value) => new Number(value); /// /// Converts the specified to an instance of this type. /// /// The value. - public static implicit operator Number(uint value) => new Number((ulong)value); - - /// - /// Converts the specified to an instance of this type. - /// - /// The value. - public static implicit operator Number(short value) => new Number((long)value); + public static implicit operator Number(uint value) => new Number(value); /// /// Converts the specified to an instance of this type. /// /// The value. - public static implicit operator Number(ushort value) => new Number((ulong)value); - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert. - public static explicit operator long(Number number) - { - return number.isSigned - ? number.signedValue - : (long)Numerics.Clamp(number.unsignedValue, 0, long.MaxValue); - } - - /// - /// Converts the specified to an instance of this type. - /// - /// The to convert. - public static explicit operator ulong(Number number) - { - return number.isSigned - ? (ulong)Numerics.Clamp(number.signedValue, 0, long.MaxValue) - : number.unsignedValue; - } + public static implicit operator Number(ushort value) => new Number((uint)value); /// /// Converts the specified to a . @@ -109,8 +69,8 @@ namespace SixLabors.ImageSharp public static explicit operator int(Number number) { return number.isSigned - ? (int)Numerics.Clamp(number.signedValue, int.MinValue, int.MaxValue) - : (int)Numerics.Clamp(number.unsignedValue, 0, (uint)int.MaxValue); + ? number.signedValue + : (int)Numerics.Clamp(number.unsignedValue, 0, int.MaxValue); } /// @@ -120,19 +80,8 @@ namespace SixLabors.ImageSharp public static explicit operator uint(Number number) { return number.isSigned - ? (uint)Numerics.Clamp(number.signedValue, uint.MinValue, uint.MaxValue) - : (uint)Numerics.Clamp(number.unsignedValue, uint.MinValue, uint.MaxValue); - } - - /// - /// Converts the specified to a . - /// - /// The to convert. - public static explicit operator short(Number number) - { - return number.isSigned - ? (short)Numerics.Clamp(number.signedValue, short.MinValue, short.MaxValue) - : (short)Numerics.Clamp(number.unsignedValue, 0, (ushort)short.MaxValue); + ? (uint)Numerics.Clamp(number.signedValue, 0, int.MaxValue) + : number.unsignedValue; } /// From c6383861d22ca81fad55af57c9d58b1aa0af411c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 11:08:50 +0300 Subject: [PATCH 021/511] Correct read Long8 tags --- .../Formats/Tiff/TiffDecoderCore.cs | 18 +-- .../Metadata/Profiles/Exif/ExifReader.cs | 28 +++- .../Profiles/Exif/Values/ExifLong8Array.cs | 128 +++++++++++++++++- 3 files changed, 163 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 922c1f174..4ab920ff8 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -211,16 +211,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff var frame = new ImageFrame(this.Configuration, width, height, imageFrameMetaData); int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; - Number[] stripOffsets = tags.GetValue(ExifTag.StripOffsets)?.Value; - Number[] stripByteCounts = tags.GetValue(ExifTag.StripByteCounts)?.Value; + ExifLong8Array stripOffsets = tags.GetValueInternal(ExifTag.StripOffsets) as ExifLong8Array; + ExifLong8Array stripByteCounts = tags.GetValueInternal(ExifTag.StripByteCounts) as ExifLong8Array; if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) { - this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken); + this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets.Value, stripByteCounts.Value, cancellationToken); } else { - this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken); + this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets.Value, stripByteCounts.Value, cancellationToken); } return frame; @@ -276,7 +276,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// An array of byte offsets to each strip in the image. /// An array of the size of each strip (in bytes). /// The token to monitor cancellation. - private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, ulong[] stripOffsets, ulong[] stripByteCounts, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int stripsPerPixel = this.BitsPerSample.Channels; @@ -329,8 +329,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff { decompressor.Decompress( this.inputStream, - (uint)stripOffsets[stripIndex], - (uint)stripByteCounts[stripIndex], + stripOffsets[stripIndex], + stripByteCounts[stripIndex], stripHeight, stripBuffers[planeIndex].GetSpan()); stripIndex += stripsPerPlane; @@ -357,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The strip offsets. /// The strip byte counts. /// The token to monitor cancellation. - private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, ulong[] stripOffsets, ulong[] stripByteCounts, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. @@ -413,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; } - decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripHeight, stripBufferSpan); + decompressor.Decompress(this.inputStream, stripOffsets[stripIndex], stripByteCounts[stripIndex], stripHeight, stripBufferSpan); colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 5c26fde7b..b9628ad73 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -420,8 +420,34 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif // The StripOffsets, StripByteCounts, TileOffsets, and TileByteCounts tags are allowed to have the datatype TIFF_LONG8 in BigTIFF. // Old datatypes TIFF_LONG, and TIFF_SHORT where allowed in the TIFF 6.0 specification, are still valid in BigTIFF, too. + // Likewise, tags that point to other IFDs, like e.g. the SubIFDs tag, are now allowed to have the datatype TIFF_IFD8 in BigTIFF. + // Again, the old datatypes TIFF_IFD, and the hardly recommendable TIFF_LONG, are still valid, too. // https://www.awaresystems.be/imaging/tiff/bigtiff.html - ExifValue exifValue = exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + ExifValue exifValue = null; + switch (tag) + { + case ExifTagValue.StripOffsets: + exifValue = new ExifLong8Array(ExifTagValue.StripOffsets); + break; + case ExifTagValue.StripByteCounts: + exifValue = new ExifLong8Array(ExifTagValue.StripByteCounts); + break; + case ExifTagValue.TileOffsets: + exifValue = new ExifLong8Array(ExifTagValue.TileOffsets); + break; + case ExifTagValue.TileByteCounts: + exifValue = new ExifLong8Array(ExifTagValue.TileByteCounts); + break; + //case ExifTagValue.SubIFDOffset: + // exifValue = new ExifLong8Array(ExifTagValue.SubIFDOffset); + // break; + //case ExifTagValue.GPSIFDOffset: + // exifValue = new ExifLong8Array(ExifTagValue.GPSIFDOffset); + // break; + default: + exifValue = exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + break; + } if (exifValue is null) { diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index 3cf59adac..17fe888f7 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -20,8 +20,134 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { } - public override ExifDataType DataType => ExifDataType.Long8; + public override ExifDataType DataType + { + get + { + if (this.Value is null) + { + return ExifDataType.Long; + } + + for (int i = 0; i < this.Value.Length; i++) + { + if (this.Value[i] > uint.MaxValue) + { + return ExifDataType.Long8; + } + } + + return ExifDataType.Long; + } + } + + public override bool TrySetValue(object value) + { + if (base.TrySetValue(value)) + { + return true; + } + + switch (value) + { + case int val: + return this.SetSingle((ulong)val); + case uint val: + return this.SetSingle((ulong)val); + case short val: + return this.SetSingle((ulong)val); + case ushort val: + return this.SetSingle((ulong)val); + case long[] array: + return this.SetArray(array); + case ulong[] array: + return this.SetArray(array); + case int[] array: + return this.SetArray(array); + case uint[] array: + return this.SetArray(array); + case short[] array: + return this.SetArray(array); + case ushort[] array: + return this.SetArray(array); + } + + return false; + } public override IExifValue DeepClone() => new ExifLong8Array(this); + + + private bool SetSingle(ulong value) + { + this.Value = new[] { value }; + return true; + } + + private bool SetArray(long[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(ulong[] values) + { + this.Value = values; + return true; + } + + private bool SetArray(int[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(uint[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(short[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)values[i]; + } + + this.Value = numbers; + return true; + } + + private bool SetArray(ushort[] values) + { + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)values[i]; + } + + this.Value = numbers; + return true; + } } } From 393659f3863ecc8351bf0a6ad2a95a5b29137517 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 11:35:46 +0300 Subject: [PATCH 022/511] Add IsBigTif flag property in metadata --- src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs | 3 +++ src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs | 4 ++-- .../Formats/Tiff/TiffDecoderMetadataCreator.cs | 13 ++++--------- src/ImageSharp/Formats/Tiff/TiffMetadata.cs | 8 ++++++++ 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index bd673cf3a..e8a087123 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -29,6 +29,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public ByteOrder ByteOrder { get; private set; } + public bool IsBigTiff { get; private set; } + /// /// Reads image file directories. /// @@ -40,6 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff headerReader.ReadFileHeader(); this.nextIfdOffset = headerReader.FirstIfdOffset; + this.IsBigTiff = headerReader.IsBigTiff; return this.ReadIfds(headerReader.IsBigTiff); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 4ab920ff8..3aae61448 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -154,7 +154,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff frames.Add(frame); } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff); // TODO: Tiff frames can have different sizes ImageFrame root = frames[0]; @@ -180,7 +180,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff ExifProfile rootFrameExifProfile = directories.First(); var rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, rootFrameExifProfile); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, reader.IsBigTiff, rootFrameExifProfile); int width = GetImageWidth(rootFrameExifProfile); int height = GetImageHeight(rootFrameExifProfile); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index 6f8a81a82..ddaf6fa96 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// internal static class TiffDecoderMetadataCreator { - public static ImageMetadata Create(List> frames, bool ignoreMetadata, ByteOrder byteOrder) + public static ImageMetadata Create(List> frames, bool ignoreMetadata, ByteOrder byteOrder, bool isBigTiff) where TPixel : unmanaged, IPixel { if (frames.Count < 1) @@ -26,13 +26,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); } - var imageMetaData = new ImageMetadata(); - ExifProfile exifProfileRootFrame = frames[0].Metadata.ExifProfile; - - SetResolution(imageMetaData, exifProfileRootFrame); - - TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); - tiffMetadata.ByteOrder = byteOrder; + ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].Metadata.ExifProfile); if (!ignoreMetadata) { @@ -56,13 +50,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff return imageMetaData; } - public static ImageMetadata Create(ByteOrder byteOrder, ExifProfile exifProfile) + public static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile) { var imageMetaData = new ImageMetadata(); SetResolution(imageMetaData, exifProfile); TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); tiffMetadata.ByteOrder = byteOrder; + tiffMetadata.IsBigTiff = isBigTiff; return imageMetaData; } diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index cf1ab3754..d537981db 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -26,6 +26,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public ByteOrder ByteOrder { get; set; } + /// + /// Gets or sets a value indicating whether this instance is BigTiff format. + /// + /// + /// true if this instance BigTiff format; otherwise, false. + /// + public bool IsBigTiff { get; set; } + /// public IDeepCloneable DeepClone() => new TiffMetadata(this); } From 6755286a8ee98363262d4a6edcae87f47af06a44 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 11:09:40 +0300 Subject: [PATCH 023/511] Add BigTiff into ConfigurationModule, add identify tests --- .../Formats/Tiff/TiffConfigurationModule.cs | 2 + .../Formats/Tiff/BigTiffDecoderTests.cs | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs index 5dfda95e8..656483435 100644 --- a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs +++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs @@ -14,6 +14,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector(false)); + + configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector(true)); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index fc507513b..7843ba4b9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -3,6 +3,8 @@ // ReSharper disable InconsistentNaming using System; +using System.IO; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -31,5 +33,54 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(BigTIFFLong8Tiles, PixelTypes.Rgba32)] public void ThrowsNotSupported(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); + + [Theory] + [InlineData(BigTIFF, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFLong, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFLong8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFMotorola, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFMotorolaLongStrips, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFSubIFD4, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo info = Image.Identify(stream); + + Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel); + Assert.Equal(expectedWidth, info.Width); + Assert.Equal(expectedHeight, info.Height); + Assert.NotNull(info.Metadata); + Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution); + Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); + Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); + + ImageSharp.Formats.Tiff.TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata(); + Assert.NotNull(tiffmeta); + Assert.True(tiffmeta.IsBigTiff); + } + } + + [Theory] + [InlineData(BigTIFFLong, ImageSharp.ByteOrder.LittleEndian)] + [InlineData(BigTIFFMotorola, ImageSharp.ByteOrder.BigEndian)] + public void ByteOrder(string imagePath, ByteOrder expectedByteOrder) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo info = Image.Identify(stream); + + Assert.NotNull(info.Metadata); + Assert.Equal(expectedByteOrder, info.Metadata.GetTiffMetadata().ByteOrder); + + stream.Seek(0, SeekOrigin.Begin); + + using var img = Image.Load(stream); + Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); + } + } } } From a4a0d1c2e73ec6c60478e5b4c290995546b4383b Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 12:05:31 +0300 Subject: [PATCH 024/511] Correct parse strip tags --- .../Formats/Tiff/TiffDecoderCore.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 3aae61448..9822cf63b 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -41,6 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff ///
private ByteOrder byteOrder; + /// + /// Indicating whether is BigTiff format. + /// + private bool isBigTiff; + /// /// Initializes a new instance of the class. /// @@ -145,6 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff IEnumerable directories = reader.Read(); this.byteOrder = reader.ByteOrder; + this.isBigTiff = reader.IsBigTiff; var frames = new List>(); foreach (ExifProfile ifd in directories) @@ -211,16 +217,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff var frame = new ImageFrame(this.Configuration, width, height, imageFrameMetaData); int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; - ExifLong8Array stripOffsets = tags.GetValueInternal(ExifTag.StripOffsets) as ExifLong8Array; - ExifLong8Array stripByteCounts = tags.GetValueInternal(ExifTag.StripByteCounts) as ExifLong8Array; + + ulong[] stripOffsets; + ulong[] stripByteCounts; + if (this.isBigTiff) + { + stripOffsets = tags.GetValueInternal(ExifTag.StripOffsets)?.GetValue() as ulong[]; + stripByteCounts = tags.GetValueInternal(ExifTag.StripByteCounts)?.GetValue() as ulong[]; + } + else + { + stripOffsets = tags.GetValue(ExifTag.StripOffsets)?.Value.Select(s => (ulong)(uint)s).ToArray(); + stripByteCounts = tags.GetValue(ExifTag.StripByteCounts)?.Value.Select(s => (ulong)(uint)s).ToArray(); + } if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) { - this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets.Value, stripByteCounts.Value, cancellationToken); + this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken); } else { - this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets.Value, stripByteCounts.Value, cancellationToken); + this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken); } return frame; From d1c78f8d226c0359ed0dcfe8219d8e53364f4bd3 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 12:06:10 +0300 Subject: [PATCH 025/511] revert missed value --- src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs index ee30c6b08..729efb390 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifDataTypes.cs @@ -36,6 +36,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return 4; case ExifDataType.DoubleFloat: case ExifDataType.Rational: + case ExifDataType.SignedRational: case ExifDataType.Long8: case ExifDataType.SignedLong8: case ExifDataType.Ifd8: From 1ed2a9becf73022f96f9434800852cf7462de4d0 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 12:06:37 +0300 Subject: [PATCH 026/511] build fixes --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 12 ++++++------ .../Metadata/Profiles/Exif/Values/ExifLong8Array.cs | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index b9628ad73..2eaae2664 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -438,12 +438,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.TileByteCounts: exifValue = new ExifLong8Array(ExifTagValue.TileByteCounts); break; - //case ExifTagValue.SubIFDOffset: - // exifValue = new ExifLong8Array(ExifTagValue.SubIFDOffset); - // break; - //case ExifTagValue.GPSIFDOffset: - // exifValue = new ExifLong8Array(ExifTagValue.GPSIFDOffset); - // break; + ////case ExifTagValue.SubIFDOffset: + //// exifValue = new ExifLong8Array(ExifTagValue.SubIFDOffset); + //// break; + ////case ExifTagValue.GPSIFDOffset: + //// exifValue = new ExifLong8Array(ExifTagValue.GPSIFDOffset); + //// break; default: exifValue = exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); break; diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index 17fe888f7..055a50104 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -77,7 +77,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public override IExifValue DeepClone() => new ExifLong8Array(this); - private bool SetSingle(ulong value) { this.Value = new[] { value }; From 9b042a753634dcad4b5dc92cba49583ef54806fa Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 12 Sep 2021 20:25:01 +0300 Subject: [PATCH 027/511] FormatDetector fixes --- .../Formats/Tiff/TiffImageFormatDetector.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs index 297da2dd8..5755b306a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// Initializes a new instance of the class. /// - /// if set to true [is big tiff]. + /// if set to true is BigTiff. public TiffImageFormatDetector(bool isBigTiff) => this.isBigTiff = isBigTiff; /// @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (header[0] == 0x49 && header[1] == 0x49) { // Little-endian - if (!this.isBigTiff) + if (this.isBigTiff is false) { if (header[2] == 0x2A && header[3] == 0x00) { @@ -48,7 +48,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - if (header[2] == 0x2B && header[3] == 0x00 && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) + if (header[2] == 0x2B && header[3] == 0x00 + && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) { return true; } @@ -57,12 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff else if (header[0] == 0x4D && header[1] == 0x4D) { // Big-endian - if (header[2] == 0x00 && (header[3] == 0x2A || header[3] == 0x2B)) - { - return true; - } - - if (!this.isBigTiff) + if (this.isBigTiff is false) { if (header[2] == 0 && header[3] == 0x2A) { @@ -71,7 +67,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff } else { - if (header[2] == 0 && header[3] == 0x2A && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) + if (header[2] == 0 && header[3] == 0x2B + && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) { return true; } From f370df714101c18cddd67ee6e05304635cf6bd4a Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 17 Sep 2021 07:54:05 +0300 Subject: [PATCH 028/511] Update src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 2eaae2664..9f53bf64c 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -459,7 +459,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif if (size > 8) { ulong newOffset = this.ConvertToUInt64(this.offsetBuffer8); - if (newOffset > ulong.MaxValue || (newOffset + size) > (ulong)this.data.Length) + if (newOffset > ulong.MaxValue || newOffset > (ulong)(this.data.Length - size)) { this.AddInvalidTag(new UnkownExifTag(tag)); return; From 4bb6c7715dddf6ca1ac272890b859ceb0ed42e4e Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 17 Sep 2021 07:54:29 +0300 Subject: [PATCH 029/511] Update src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.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/Tiff/Ifd/DirectoryReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index e8a087123..f6bc33079 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -49,8 +49,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff private static ByteOrder ReadByteOrder(Stream stream) { - byte[] headerBytes = new byte[2]; - stream.Read(headerBytes, 0, 2); + Span headerBytes = stackalloc byte[2]; + stream.Read(headerBytes); if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) { return ByteOrder.LittleEndian; From 789dc78864c27ec6b79b9143cd9765a0bc04ec7b Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 17 Sep 2021 08:03:18 +0300 Subject: [PATCH 030/511] build fix --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 9f53bf64c..a63a40d66 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -459,7 +459,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif if (size > 8) { ulong newOffset = this.ConvertToUInt64(this.offsetBuffer8); - if (newOffset > ulong.MaxValue || newOffset > (ulong)(this.data.Length - size)) + if (newOffset > ulong.MaxValue || newOffset > ((ulong)this.data.Length - size)) { this.AddInvalidTag(new UnkownExifTag(tag)); return; From ebb277a7942e0c18069bb103294661ec6c774d4d Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 17 Sep 2021 10:34:29 +0300 Subject: [PATCH 031/511] try improve reading ext values --- .../Formats/Tiff/Ifd/DirectoryReader.cs | 25 ++++---- .../Formats/Tiff/Ifd/EntryReader.cs | 10 +--- .../Metadata/Profiles/Exif/ExifReader.cs | 57 +++++++++---------- 3 files changed, 41 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index f6bc33079..3c70ee591 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -18,10 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff private ulong nextIfdOffset; - // 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()); - public DirectoryReader(Stream stream) => this.stream = stream; /// @@ -68,23 +64,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff var readers = new List(); while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length) { - var reader = new EntryReader(this.stream, this.ByteOrder, this.lazyLoaders); + var reader = new EntryReader(this.stream, this.ByteOrder); reader.ReadTags(isBigTiff, this.nextIfdOffset); - this.nextIfdOffset = reader.NextIfdOffset; + if (reader.ExtTags.Count > 0) + { + reader.ExtTags.Sort((t1, t2) => t1.offset.CompareTo(t2.offset)); - readers.Add(reader); - } + if (reader.ExtTags[0].offset < reader.NextIfdOffset) + { + // this means that most likely all elements are placed before next IFD + reader.ReadExtValues(); + } + } - // Sequential reading big values. - foreach (Action loader in this.lazyLoaders.Values) - { - loader(); + this.nextIfdOffset = reader.NextIfdOffset; + readers.Add(reader); } var list = new List(readers.Count); foreach (EntryReader reader in readers) { + reader.ReadExtValues(); var profile = new ExifProfile(reader.Values, reader.InvalidTags); list.Add(profile); } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index 7884242ef..5a83febf8 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -12,13 +12,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff { internal class EntryReader : BaseExifReader { - private readonly SortedList lazyLoaders; - - public EntryReader(Stream stream, ByteOrder byteOrder, SortedList lazyLoaders) + public EntryReader(Stream stream, ByteOrder byteOrder) : base(stream) { this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - this.lazyLoaders = lazyLoaders; } public List Values { get; } = new List(); @@ -43,8 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } } - protected override void RegisterExtLoader(ulong offset, Action reader) => - this.lazyLoaders.Add(offset, reader); + public void ReadExtValues() => this.ReadExtValues(this.Values); } internal class HeaderReader : BaseExifReader @@ -81,7 +77,5 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowInvalidHeader(); } - - protected override void RegisterExtLoader(ulong offset, Action reader) => throw new NotSupportedException(); } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index a63a40d66..0cac35197 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal class ExifReader : BaseExifReader { - private readonly List loaders = new List(); - public ExifReader(byte[] exifData) : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData)))) { @@ -41,22 +39,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif uint ifdOffset = this.ReadUInt32(); this.ReadValues(values, ifdOffset); + this.ReadExtValues(values); uint thumbnailOffset = this.ReadUInt32(); this.GetThumbnail(thumbnailOffset); this.ReadSubIfd(values); - foreach (Action loader in this.loaders) - { - loader(); - } - return values; } - protected override void RegisterExtLoader(ulong offset, Action loader) => this.loaders.Add(loader); - private void GetThumbnail(uint offset) { if (offset == 0) @@ -121,7 +113,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public bool IsBigEndian { get; protected set; } - protected abstract void RegisterExtLoader(ulong offset, Action loader); + public List<(ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif)> ExtTags { get; } = new (); + + protected void ReadExtValues(List values) + { + foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.ExtTags) + { + this.ReadExtValue(values, tag); + } + + this.ExtTags.Clear(); + } /// /// Reads the values to the values collection. @@ -173,6 +175,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } + protected void ReadExtValue(IList values, (ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag) + { + ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + byte[] dataBuffer = new byte[size]; + this.Seek(tag.offset); + if (this.TryReadSpan(dataBuffer)) + { + object value = this.ConvertValue(tag.dataType, dataBuffer, tag.numberOfComponents); + this.Add(values, tag.exif, value); + } + } + protected void ReadSubIfd64(List values) { if (this.exifOffset != 0) @@ -375,21 +389,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return; } - this.RegisterExtLoader(newOffset, () => - { - byte[] dataBuffer = new byte[size]; - this.Seek(newOffset); - if (this.TryReadSpan(dataBuffer)) - { - object value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); - this.Add(values, exifValue, value); - } - }); + this.ExtTags.Add((newOffset, dataType, numberOfComponents, exifValue)); } else { object value = this.ConvertValue(dataType, this.offsetBuffer, numberOfComponents); - this.Add(values, exifValue, value); } } @@ -465,16 +469,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return; } - this.RegisterExtLoader(newOffset, () => - { - byte[] dataBuffer = new byte[size]; - this.Seek(newOffset); - if (this.TryReadSpan(dataBuffer)) - { - object value = this.ConvertValue(dataType, dataBuffer, numberOfComponents); - this.Add(values, exifValue, value); - } - }); + this.ExtTags.Add((newOffset, dataType, numberOfComponents, exifValue)); } else { From 7fe80cfd9c229f743649523fa2a297009c28d1c5 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Sep 2021 18:41:32 +0300 Subject: [PATCH 032/511] Implement reading SubIfds (Code 330 (hex 0x014A)), ExifReader improvements --- .../Formats/Tiff/Ifd/DirectoryReader.cs | 2 +- .../Metadata/Profiles/Exif/ExifReader.cs | 110 ++++++++++-------- .../Profiles/Exif/Tags/ExifTag.LongArray.cs | 5 + .../Profiles/Exif/Values/ExifNumberArray.cs | 10 +- .../Profiles/Exif/Values/ExifValues.cs | 1 + .../Formats/Tiff/BigTiffDecoderTests.cs | 28 +++++ .../Profiles/Exif/ExifProfileTests.cs | 6 +- 7 files changed, 105 insertions(+), 57 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 3c70ee591..1e298028d 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -71,9 +71,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff { reader.ExtTags.Sort((t1, t2) => t1.offset.CompareTo(t2.offset)); + // this means that most likely all elements are placed before next IFD if (reader.ExtTags[0].offset < reader.NextIfdOffset) { - // this means that most likely all elements are placed before next IFD reader.ReadExtValues(); } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 0cac35197..eea42af28 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -39,13 +39,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif uint ifdOffset = this.ReadUInt32(); this.ReadValues(values, ifdOffset); - this.ReadExtValues(values); uint thumbnailOffset = this.ReadUInt32(); this.GetThumbnail(thumbnailOffset); this.ReadSubIfd(values); + this.ReadExtValues(values); + return values; } @@ -78,8 +79,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// internal abstract class BaseExifReader { - private readonly byte[] offsetBuffer = new byte[4]; - private readonly byte[] offsetBuffer8 = new byte[8]; private readonly byte[] buf8 = new byte[8]; private readonly byte[] buf4 = new byte[4]; private readonly byte[] buf2 = new byte[2]; @@ -87,9 +86,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private readonly Stream data; private List invalidTags; - private uint exifOffset; - - private uint gpsOffset; + private List subIfds; protected BaseExifReader(Stream stream) => this.data = stream ?? throw new ArgumentNullException(nameof(stream)); @@ -140,22 +137,23 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.Seek(offset); int count = this.ReadUInt16(); + Span offsetBuffer = new byte[4]; for (int i = 0; i < count; i++) { - this.ReadValue(values); + this.ReadValue(values, offsetBuffer); } } protected void ReadSubIfd(List values) { - if (this.exifOffset != 0) + if (this.subIfds is null) { - this.ReadValues(values, this.exifOffset); + return; } - if (this.gpsOffset != 0) + foreach (ulong subIfdOffset in this.subIfds) { - this.ReadValues(values, this.gpsOffset); + this.ReadValues(values, (uint)subIfdOffset); } } @@ -169,9 +167,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.Seek(offset); ulong count = this.ReadUInt64(); + Span offsetBuffer = new byte[8]; for (ulong i = 0; i < count; i++) { - this.ReadValue64(values); + this.ReadValue64(values, offsetBuffer); } } @@ -182,21 +181,21 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.Seek(tag.offset); if (this.TryReadSpan(dataBuffer)) { - object value = this.ConvertValue(tag.dataType, dataBuffer, tag.numberOfComponents); + object value = this.ConvertValue(tag.dataType, dataBuffer, tag.numberOfComponents > 1 || tag.exif.IsArray); this.Add(values, tag.exif, value); } } protected void ReadSubIfd64(List values) { - if (this.exifOffset != 0) + if (this.subIfds is null) { - this.ReadValues64(values, this.exifOffset); + return; } - if (this.gpsOffset != 0) + foreach (ulong subIfdOffset in this.subIfds) { - this.ReadValues64(values, this.gpsOffset); + this.ReadValues64(values, subIfdOffset); } } @@ -231,7 +230,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return Encoding.UTF8.GetString(buffer); } - private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, ulong numberOfComponents) + private object ConvertValue(ExifDataType dataType, ReadOnlySpan buffer, bool isArray) { if (buffer.Length == 0) { @@ -245,102 +244,104 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifDataType.Ascii: return this.ConvertToString(buffer); case ExifDataType.Byte: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToByte(buffer); } return buffer.ToArray(); case ExifDataType.DoubleFloat: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToDouble(buffer); } return ToArray(dataType, buffer, this.ConvertToDouble); case ExifDataType.Long: - if (numberOfComponents == 1) + case ExifDataType.Ifd: + if (!isArray) { return this.ConvertToUInt32(buffer); } return ToArray(dataType, buffer, this.ConvertToUInt32); case ExifDataType.Rational: - if (numberOfComponents == 1) + if (!isArray) { return this.ToRational(buffer); } return ToArray(dataType, buffer, this.ToRational); case ExifDataType.Short: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToShort(buffer); } return ToArray(dataType, buffer, this.ConvertToShort); case ExifDataType.SignedByte: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToSignedByte(buffer); } return ToArray(dataType, buffer, this.ConvertToSignedByte); case ExifDataType.SignedLong: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToInt32(buffer); } return ToArray(dataType, buffer, this.ConvertToInt32); case ExifDataType.SignedRational: - if (numberOfComponents == 1) + if (!isArray) { return this.ToSignedRational(buffer); } return ToArray(dataType, buffer, this.ToSignedRational); case ExifDataType.SignedShort: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToSignedShort(buffer); } return ToArray(dataType, buffer, this.ConvertToSignedShort); case ExifDataType.SingleFloat: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToSingle(buffer); } return ToArray(dataType, buffer, this.ConvertToSingle); case ExifDataType.Long8: - if (numberOfComponents == 1) + case ExifDataType.Ifd8: + if (!isArray) { return this.ConvertToUInt64(buffer); } return ToArray(dataType, buffer, this.ConvertToUInt64); case ExifDataType.SignedLong8: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToInt64(buffer); } return ToArray(dataType, buffer, this.ConvertToUInt64); case ExifDataType.Undefined: - if (numberOfComponents == 1) + if (!isArray) { return this.ConvertToByte(buffer); } return buffer.ToArray(); default: - throw new NotSupportedException(); + throw new NotSupportedException($"Data type {dataType} is not supported."); } } - private void ReadValue(List values) + private void ReadValue(List values, Span offsetBuffer) { // 2 | 2 | 4 | 4 // tag | type | count | value offset @@ -354,7 +355,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif uint numberOfComponents = this.ReadUInt32(); - this.TryReadSpan(this.offsetBuffer); + this.TryReadSpan(offsetBuffer); // Ensure that the data type is valid if (dataType == ExifDataType.Unknown) @@ -380,7 +381,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif uint size = numberOfComponents * ExifDataTypes.GetSize(dataType); if (size > 4) { - uint newOffset = this.ConvertToUInt32(this.offsetBuffer); + uint newOffset = this.ConvertToUInt32(offsetBuffer); // Ensure that the new index does not overrun the data. if (newOffset > int.MaxValue || (newOffset + size) > this.data.Length) @@ -393,12 +394,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } else { - object value = this.ConvertValue(dataType, this.offsetBuffer, numberOfComponents); + object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1 || exifValue.IsArray); this.Add(values, exifValue, value); } } - private void ReadValue64(List values) + private void ReadValue64(List values, Span offsetBuffer) { if ((this.data.Length - this.data.Position) < 20) { @@ -410,7 +411,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ulong numberOfComponents = this.ReadUInt64(); - this.TryReadSpan(this.offsetBuffer8); + this.TryReadSpan(offsetBuffer); if (dataType == ExifDataType.Unknown) { @@ -442,12 +443,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.TileByteCounts: exifValue = new ExifLong8Array(ExifTagValue.TileByteCounts); break; - ////case ExifTagValue.SubIFDOffset: - //// exifValue = new ExifLong8Array(ExifTagValue.SubIFDOffset); - //// break; - ////case ExifTagValue.GPSIFDOffset: - //// exifValue = new ExifLong8Array(ExifTagValue.GPSIFDOffset); - //// break; + case ExifTagValue.SubIFDOffset: + exifValue = new ExifLong8(ExifTagValue.SubIFDOffset); + break; + case ExifTagValue.GPSIFDOffset: + exifValue = new ExifLong8(ExifTagValue.GPSIFDOffset); + break; + case ExifTagValue.SubIFDs: + exifValue = new ExifLong8Array(ExifTagValue.SubIFDs); + break; default: exifValue = exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); break; @@ -462,7 +466,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); if (size > 8) { - ulong newOffset = this.ConvertToUInt64(this.offsetBuffer8); + ulong newOffset = this.ConvertToUInt64(offsetBuffer); if (newOffset > ulong.MaxValue || newOffset > ((ulong)this.data.Length - size)) { this.AddInvalidTag(new UnkownExifTag(tag)); @@ -473,7 +477,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } else { - object value = this.ConvertValue(dataType, ((Span)this.offsetBuffer8).Slice(0, (int)size), numberOfComponents); + object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1 || exifValue.IsArray); this.Add(values, exifValue, value); } } @@ -497,11 +501,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif if (exif.Tag == ExifTag.SubIFDOffset) { - this.exifOffset = (uint)value; + this.AddSubIfd(value); } else if (exif.Tag == ExifTag.GPSIFDOffset) { - this.gpsOffset = (uint)value; + this.AddSubIfd(value); + } + else if (exif.Tag == ExifTag.SubIFDs) + { + foreach (object val in (Array)value) + { + this.AddSubIfd(val); + } } else { @@ -512,6 +523,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private void AddInvalidTag(ExifTag tag) => (this.invalidTags ??= new List()).Add(tag); + private void AddSubIfd(object val) + => (this.subIfds ??= new List()).Add(Convert.ToUInt64(val)); + private void Seek(ulong pos) => this.data.Seek((long)pos, SeekOrigin.Begin); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs index ac4b0a1bf..390599b73 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs @@ -65,5 +65,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif /// Gets the TimeZoneOffset exif tag. /// public static ExifTag TimeZoneOffset { get; } = new ExifTag(ExifTagValue.TimeZoneOffset); + + /// + /// Gets the offset to child IFDs exif tag. + /// + public static ExifTag SubIFDs { get; } = new ExifTag(ExifTagValue.SubIFDs); } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 2d3a93aed..9e9e0e737 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -49,18 +49,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return this.SetSingle(val); case uint val: return this.SetSingle(val); - case short val: - return this.SetSingle(val); case ushort val: return this.SetSingle(val); - case int[] array: - return this.SetArray(array); + case short val: + return this.SetSingle(val); case uint[] array: return this.SetArray(array); - case short[] array: + case int[] array: return this.SetArray(array); case ushort[] array: return this.SetArray(array); + case short[] array: + return this.SetArray(array); } return false; diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs index 7e5b35f49..33fb90cc0 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs @@ -92,6 +92,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.StripRowCounts: return new ExifLongArray(ExifTag.StripRowCounts); case ExifTagValue.IntergraphRegisters: return new ExifLongArray(ExifTag.IntergraphRegisters); case ExifTagValue.TimeZoneOffset: return new ExifLongArray(ExifTag.TimeZoneOffset); + case ExifTagValue.SubIFDs: return new ExifLongArray(ExifTag.SubIFDs); case ExifTagValue.ImageWidth: return new ExifNumber(ExifTag.ImageWidth); case ExifTagValue.ImageLength: return new ExifNumber(ExifTag.ImageLength); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index 7843ba4b9..d59e276cf 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -3,8 +3,10 @@ // ReSharper disable InconsistentNaming using System; +using System.Linq; using System.IO; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -26,6 +28,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(BigTIFFMotorolaLongStrips, PixelTypes.Rgba32)] [WithFile(BigTIFFSubIFD4, PixelTypes.Rgba32)] [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] + [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] + [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] + [WithFile(RLE, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); @@ -42,6 +47,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(BigTIFFMotorolaLongStrips, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(BigTIFFSubIFD4, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Indexed4_Deflate, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) { var testFile = TestFile.Create(imagePath); @@ -82,5 +90,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(expectedByteOrder, img.Metadata.GetTiffMetadata().ByteOrder); } } + + [Theory] + [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] + public void TiffDecoder_SubIfd8(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + + ExifProfile meta = image.Frames.RootFrame.Metadata.ExifProfile; + + Assert.Equal(0, meta.InvalidTags.Count); + Assert.Equal(15, meta.Values.Count); + Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageWidth).Value); + Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageLength).Value); + Assert.Equal(64, (int)meta.GetValue(ExifTag.RowsPerStrip).Value); + + Assert.Equal(2, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.ImageWidth)); + Assert.Equal(2, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripOffsets)); + Assert.Equal(2, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripByteCounts)); + } } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 1f23838ab..4b8551042 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -168,8 +168,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif 2 x due to use of non-standard padding tag 0xEA1C listed in EXIF Tool. We can read those values but adhere strictly to the 2.3.1 specification when writing. (TODO: Support 2.3.2) https://exiftool.org/TagNames/EXIF.html */ - [InlineData(TestImageWriteFormat.Jpeg, 16)] - [InlineData(TestImageWriteFormat.Png, 16)] + [InlineData(TestImageWriteFormat.Jpeg, 18)] + [InlineData(TestImageWriteFormat.Png, 18)] public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); @@ -519,7 +519,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif // todo: duplicate tags Assert.Equal(2, profile.Values.Count(v => (ushort)v.Tag == 59932)); - Assert.Equal(16, profile.Values.Count); + Assert.Equal(18, profile.Values.Count); foreach (IExifValue value in profile.Values) { From 42eab9500e03a4aceb49ce5a72070b4eabe28ace Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Sep 2021 18:42:14 +0300 Subject: [PATCH 033/511] Add test files --- tests/ImageSharp.Tests/TestImages.cs | 22 +++++++++++-------- .../Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif | 3 +++ .../Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif | 3 +++ .../Images/Input/Tiff/BigTiff/BigTIFF_RLE.tif | 3 +++ 4 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_RLE.tif diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 9df6472a1..eccc3b089 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -675,17 +675,21 @@ namespace SixLabors.ImageSharp.Tests public static class BigTiff { - public const string BasePath = "Tiff/BigTiff/"; + public const string Base = "Tiff/BigTiff/"; - public const string BigTIFF = BasePath + "BigTIFF.tif"; - public const string BigTIFFLong = BasePath + "BigTIFFLong.tif"; - public const string BigTIFFLong8 = BasePath + "BigTIFFLong8.tif"; - public const string BigTIFFLong8Tiles = BasePath + "BigTIFFLong8Tiles.tif"; - public const string BigTIFFMotorola = BasePath + "BigTIFFMotorola.tif"; - public const string BigTIFFMotorolaLongStrips = BasePath + "BigTIFFMotorolaLongStrips.tif"; + public const string BigTIFF = Base + "BigTIFF.tif"; + public const string BigTIFFLong = Base + "BigTIFFLong.tif"; + public const string BigTIFFLong8 = Base + "BigTIFFLong8.tif"; + public const string BigTIFFLong8Tiles = Base + "BigTIFFLong8Tiles.tif"; + public const string BigTIFFMotorola = Base + "BigTIFFMotorola.tif"; + public const string BigTIFFMotorolaLongStrips = Base + "BigTIFFMotorolaLongStrips.tif"; - public const string BigTIFFSubIFD4 = BasePath + "BigTIFFSubIFD4.tif"; - public const string BigTIFFSubIFD8 = BasePath + "BigTIFFSubIFD8.tif"; + public const string BigTIFFSubIFD4 = Base + "BigTIFFSubIFD4.tif"; + public const string BigTIFFSubIFD8 = Base + "BigTIFFSubIFD8.tif"; + + public const string Indexed4_Deflate = Base + "BigTIFF_Indexed4_Deflate.tif"; + public const string Indexed8_LZW = Base + "BigTIFF_Indexed8_LZW.tif"; + public const string RLE = Base + "BigTIFF_RLE.tif"; } } } diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif new file mode 100644 index 000000000..bc736790a --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed4_Deflate.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38ba3717b284d7914243609576d0f9b75d732692bf05e2e1ec8b119feb1409fd +size 687 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif new file mode 100644 index 000000000..edff7c40a --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF_Indexed8_LZW.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca404b3ec5560b82169855f0ae69e64c6bc7286117b95fc0e0d505e5e356fa0e +size 2548 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_RLE.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_RLE.tif new file mode 100644 index 000000000..d15188c17 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF_RLE.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:038a298bcace02810054af650f490b6858863c8755e41b786605aa807b43350a +size 509 From 7918ecc5af551cf98d8bc3635125af02b867ee11 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Sep 2021 19:28:40 +0300 Subject: [PATCH 034/511] temporary disable SubIFDs tag reading --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 8 ++++---- .../Formats/Tiff/BigTiffDecoderTests.cs | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index eea42af28..eb15f9a49 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -509,10 +509,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } else if (exif.Tag == ExifTag.SubIFDs) { - foreach (object val in (Array)value) - { - this.AddSubIfd(val); - } + ////foreach (object val in (Array)value) + ////{ + //// this.AddSubIfd(val); + ////} } else { diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index d59e276cf..ae15eac5c 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(BigTIFFMotorolaLongStrips, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(BigTIFFSubIFD4, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(Indexed4_Deflate, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(Indexed4_Deflate, 4, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) @@ -101,14 +101,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff ExifProfile meta = image.Frames.RootFrame.Metadata.ExifProfile; Assert.Equal(0, meta.InvalidTags.Count); - Assert.Equal(15, meta.Values.Count); + Assert.Equal(6, meta.Values.Count); Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageWidth).Value); Assert.Equal(64, (int)meta.GetValue(ExifTag.ImageLength).Value); Assert.Equal(64, (int)meta.GetValue(ExifTag.RowsPerStrip).Value); - Assert.Equal(2, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.ImageWidth)); - Assert.Equal(2, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripOffsets)); - Assert.Equal(2, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripByteCounts)); + Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.ImageWidth)); + Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripOffsets)); + Assert.Equal(1, meta.Values.Count(v => (ushort)v.Tag == (ushort)ExifTagValue.StripByteCounts)); } } } From e55a63ac64cc3d7df14768f80c67c64c2a925549 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Sep 2021 20:28:29 +0300 Subject: [PATCH 035/511] Revert some changes --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 4 ++-- .../Metadata/Profiles/Exif/Values/ExifNumberArray.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index eb15f9a49..2aa2fa27d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -394,7 +394,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } else { - object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1 || exifValue.IsArray); + object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1); this.Add(values, exifValue, value); } } @@ -477,7 +477,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } else { - object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1 || exifValue.IsArray); + object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1); this.Add(values, exifValue, value); } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 9e9e0e737..2d3a93aed 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -49,18 +49,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return this.SetSingle(val); case uint val: return this.SetSingle(val); - case ushort val: - return this.SetSingle(val); case short val: return this.SetSingle(val); - case uint[] array: - return this.SetArray(array); + case ushort val: + return this.SetSingle(val); case int[] array: return this.SetArray(array); - case ushort[] array: + case uint[] array: return this.SetArray(array); case short[] array: return this.SetArray(array); + case ushort[] array: + return this.SetArray(array); } return false; From f170a73d3cb5ae444697031c6cba5f6662223c0b Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Sep 2021 21:54:16 +0300 Subject: [PATCH 036/511] correct reading --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 2aa2fa27d..7a102b3b2 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -365,9 +365,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif // Issue #132: ExifDataType == Undefined is treated like a byte array. // If numberOfComponents == 0 this value can only be handled as an inline value and must fallback to 4 (bytes) - if (dataType == ExifDataType.Undefined && numberOfComponents == 0) + if (numberOfComponents == 0) { - numberOfComponents = 4; + numberOfComponents = 4 / ExifDataTypes.GetSize(dataType); } ExifValue exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); @@ -418,9 +418,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return; } - if (dataType == ExifDataType.Undefined && numberOfComponents == 0) + if (numberOfComponents == 0) { - numberOfComponents = 8; + numberOfComponents = 8 / ExifDataTypes.GetSize(dataType); } // The StripOffsets, StripByteCounts, TileOffsets, and TileByteCounts tags are allowed to have the datatype TIFF_LONG8 in BigTIFF. From 82d9b974cad9d0e627564a9420ece815a5f76bfc Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Sep 2021 22:13:49 +0300 Subject: [PATCH 037/511] memory reduce big values reading --- .../Metadata/Profiles/Exif/ExifReader.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 7a102b3b2..bae2d6c81 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -114,9 +114,22 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif protected void ReadExtValues(List values) { + ulong maxSize = 0; foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.ExtTags) { - this.ReadExtValue(values, tag); + ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + if (size > maxSize) + { + maxSize = size; + } + } + + Span buf = new byte[maxSize]; + foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.ExtTags) + { + ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + + this.ReadExtValue(values, tag, buf.Slice(0, (int)size)); } this.ExtTags.Clear(); @@ -174,14 +187,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - protected void ReadExtValue(IList values, (ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag) + protected void ReadExtValue(IList values, (ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag, Span buffer) { - ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); - byte[] dataBuffer = new byte[size]; this.Seek(tag.offset); - if (this.TryReadSpan(dataBuffer)) + if (this.TryReadSpan(buffer)) { - object value = this.ConvertValue(tag.dataType, dataBuffer, tag.numberOfComponents > 1 || tag.exif.IsArray); + object value = this.ConvertValue(tag.dataType, buffer, tag.numberOfComponents > 1); this.Add(values, tag.exif, value); } } @@ -453,7 +464,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif exifValue = new ExifLong8Array(ExifTagValue.SubIFDs); break; default: - exifValue = exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); + exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); break; } From 8b7aaba6151bc3e794bade8f66146098285e3428 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Sep 2021 22:41:34 +0300 Subject: [PATCH 038/511] report problem files --- .../Formats/Tiff/BigTiffDecoderTests.cs | 11 +++++++++-- tests/ImageSharp.Tests/TestImages.cs | 3 ++- .../{BigTIFF_RLE.tif => BigTIFF_MinInBlack_RLE.tif} | 0 .../Input/Tiff/BigTiff/BigTIFF_MinInWhite_RLE.tif | 3 +++ 4 files changed, 14 insertions(+), 3 deletions(-) rename tests/Images/Input/Tiff/BigTiff/{BigTIFF_RLE.tif => BigTIFF_MinInBlack_RLE.tif} (100%) create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInWhite_RLE.tif diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index ae15eac5c..e95844707 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.PixelFormats; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.BigTiff; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { @@ -30,7 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] - [WithFile(RLE, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); @@ -39,6 +39,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void ThrowsNotSupported(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); + [Theory] + [WithFile(MinInWhite_RLE, PixelTypes.Rgba32)] + [WithFile(MinInBlack_RLE, PixelTypes.Rgba32)] + public void ProblemFiles(TestImageProvider provider) + where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffDecoder(provider)); + [Theory] [InlineData(BigTIFF, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(BigTIFFLong, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] @@ -49,7 +55,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Indexed4_Deflate, 4, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinInWhite_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinInBlack_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) { var testFile = TestFile.Create(imagePath); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index eccc3b089..e6342e660 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -689,7 +689,8 @@ namespace SixLabors.ImageSharp.Tests public const string Indexed4_Deflate = Base + "BigTIFF_Indexed4_Deflate.tif"; public const string Indexed8_LZW = Base + "BigTIFF_Indexed8_LZW.tif"; - public const string RLE = Base + "BigTIFF_RLE.tif"; + public const string MinInWhite_RLE = Base + "BigTIFF_MinInWhite_RLE.tif"; + public const string MinInBlack_RLE = Base + "BigTIFF_MinInBlack_RLE.tif"; } } } diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_RLE.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInBlack_RLE.tif similarity index 100% rename from tests/Images/Input/Tiff/BigTiff/BigTIFF_RLE.tif rename to tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInBlack_RLE.tif diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInWhite_RLE.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInWhite_RLE.tif new file mode 100644 index 000000000..d15188c17 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInWhite_RLE.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:038a298bcace02810054af650f490b6858863c8755e41b786605aa807b43350a +size 509 From d1e778f097175d96716d3021579d41a0325489fa Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 10:43:47 +0300 Subject: [PATCH 039/511] renaming, add fax3 test file --- .../Formats/Tiff/BigTiffDecoderTests.cs | 10 ++++++---- tests/ImageSharp.Tests/TestImages.cs | 5 +++-- .../Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tiff | 3 +++ ...F_MinInBlack_RLE.tif => BigTIFF_MinIsBlack_RLE.tif} | 0 ...F_MinInWhite_RLE.tif => BigTIFF_MinIsWhite_RLE.tif} | 0 5 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tiff rename tests/Images/Input/Tiff/BigTiff/{BigTIFF_MinInBlack_RLE.tif => BigTIFF_MinIsBlack_RLE.tif} (100%) rename tests/Images/Input/Tiff/BigTiff/{BigTIFF_MinInWhite_RLE.tif => BigTIFF_MinIsWhite_RLE.tif} (100%) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index e95844707..a6f08cbc9 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -31,6 +31,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] + [WithFile(MinIsBlack_Fax3, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); @@ -40,8 +41,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); [Theory] - [WithFile(MinInWhite_RLE, PixelTypes.Rgba32)] - [WithFile(MinInBlack_RLE, PixelTypes.Rgba32)] + [WithFile(MinIsWhite_RLE, PixelTypes.Rgba32)] + [WithFile(MinIsBlack_RLE, PixelTypes.Rgba32)] public void ProblemFiles(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffDecoder(provider)); @@ -55,8 +56,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Indexed4_Deflate, 4, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinInWhite_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinInBlack_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinIsWhite_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinIsBlack_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinIsBlack_Fax3, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) { var testFile = TestFile.Create(imagePath); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e6342e660..7292d232d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -689,8 +689,9 @@ namespace SixLabors.ImageSharp.Tests public const string Indexed4_Deflate = Base + "BigTIFF_Indexed4_Deflate.tif"; public const string Indexed8_LZW = Base + "BigTIFF_Indexed8_LZW.tif"; - public const string MinInWhite_RLE = Base + "BigTIFF_MinInWhite_RLE.tif"; - public const string MinInBlack_RLE = Base + "BigTIFF_MinInBlack_RLE.tif"; + public const string MinIsWhite_RLE = Base + "BigTIFF_MinIsWhite_RLE.tif"; + public const string MinIsBlack_RLE = Base + "BigTIFF_MinIsBlack_RLE.tif"; + public const string MinIsBlack_Fax3 = Base + "BigTIFF_MinIsBlack_Fax3.tif"; } } } diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tiff b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tiff new file mode 100644 index 000000000..952f4fab9 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3853a4850a023bce431bc224145f47e095e9910cddce9ba5fcaf496c9d385e04 +size 564 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInBlack_RLE.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_RLE.tif similarity index 100% rename from tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInBlack_RLE.tif rename to tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_RLE.tif diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInWhite_RLE.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite_RLE.tif similarity index 100% rename from tests/Images/Input/Tiff/BigTiff/BigTIFF_MinInWhite_RLE.tif rename to tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite_RLE.tif From e13122cab1997f4aa91cc30844d81694eaf352a6 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 10:54:18 +0300 Subject: [PATCH 040/511] workaround for inconsistent covariance of value-typed arrays --- .../Profiles/Exif/Values/ExifLong8Array.cs | 37 +++++++++++++++---- .../Profiles/Exif/Values/ExifNumberArray.cs | 20 ++++++++-- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index 055a50104..d012f646b 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -51,25 +51,46 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif switch (value) { case int val: - return this.SetSingle((ulong)val); + return this.SetSingle((ulong)Numerics.Clamp(val, 0, int.MaxValue)); + case uint val: return this.SetSingle((ulong)val); + case short val: - return this.SetSingle((ulong)val); + return this.SetSingle((ulong)Numerics.Clamp(val, 0, short.MaxValue)); + case ushort val: return this.SetSingle((ulong)val); + case long[] array: + { + if (value.GetType().Equals(typeof(ulong[]))) + { + return this.SetArray((ulong[])value); + } + return this.SetArray(array); - case ulong[] array: - return this.SetArray(array); + } + case int[] array: + { + if (value.GetType().Equals(typeof(uint[]))) + { + return this.SetArray((uint[])value); + } + return this.SetArray(array); - case uint[] array: - return this.SetArray(array); + } + case short[] array: + { + if (value.GetType().Equals(typeof(ushort[]))) + { + return this.SetArray((ushort[])value); + } + return this.SetArray(array); - case ushort[] array: - return this.SetArray(array); + } } return false; diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 2d3a93aed..4b7433d7d 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -54,13 +54,25 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ushort val: return this.SetSingle(val); case int[] array: + { + // workaround for inconsistent covariance of value-typed arrays + if (value.GetType().Equals(typeof(uint[]))) + { + return this.SetArray((uint[])value); + } + return this.SetArray(array); - case uint[] array: - return this.SetArray(array); + } + case short[] array: + { + if (value.GetType().Equals(typeof(ushort[]))) + { + return this.SetArray((ushort[])value); + } + return this.SetArray(array); - case ushort[] array: - return this.SetArray(array); + } } return false; From 1ff115638229073531e72e95e95f3c28088bd44a Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 11:06:46 +0300 Subject: [PATCH 041/511] renaming --- .../Formats/Tiff/Ifd/DirectoryReader.cs | 10 +++---- .../Formats/Tiff/Ifd/EntryReader.cs | 3 +- .../Metadata/Profiles/Exif/ExifReader.cs | 28 +++++++++---------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 1e298028d..3aed89bc5 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -67,14 +67,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff var reader = new EntryReader(this.stream, this.ByteOrder); reader.ReadTags(isBigTiff, this.nextIfdOffset); - if (reader.ExtTags.Count > 0) + if (reader.BigValues.Count > 0) { - reader.ExtTags.Sort((t1, t2) => t1.offset.CompareTo(t2.offset)); + reader.BigValues.Sort((t1, t2) => t1.offset.CompareTo(t2.offset)); // this means that most likely all elements are placed before next IFD - if (reader.ExtTags[0].offset < reader.NextIfdOffset) + if (reader.BigValues[0].offset < reader.NextIfdOffset) { - reader.ReadExtValues(); + reader.ReadBigValues(); } } @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff var list = new List(readers.Count); foreach (EntryReader reader in readers) { - reader.ReadExtValues(); + reader.ReadBigValues(); var profile = new ExifProfile(reader.Values, reader.InvalidTags); list.Add(profile); } diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index 5a83febf8..98cc1deb3 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -1,7 +1,6 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using System.IO; @@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } } - public void ReadExtValues() => this.ReadExtValues(this.Values); + public void ReadBigValues() => this.ReadBigValues(this.Values); } internal class HeaderReader : BaseExifReader diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index bae2d6c81..feca52871 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.ReadSubIfd(values); - this.ReadExtValues(values); + this.ReadBigValues(values); return values; } @@ -110,14 +110,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public bool IsBigEndian { get; protected set; } - public List<(ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif)> ExtTags { get; } = new (); + public List<(ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif)> BigValues { get; } = new (); - protected void ReadExtValues(List values) + protected void ReadBigValues(List values) { ulong maxSize = 0; - foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.ExtTags) + foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) in this.BigValues) { - ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); if (size > maxSize) { maxSize = size; @@ -125,14 +125,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } Span buf = new byte[maxSize]; - foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.ExtTags) + foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.BigValues) { ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); - this.ReadExtValue(values, tag, buf.Slice(0, (int)size)); + this.ReadBigValue(values, tag, buf.Slice(0, (int)size)); } - this.ExtTags.Clear(); + this.BigValues.Clear(); } /// @@ -187,12 +187,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - protected void ReadExtValue(IList values, (ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag, Span buffer) + protected void ReadBigValue(IList values, (ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag, Span buffer) { this.Seek(tag.offset); if (this.TryReadSpan(buffer)) { - object value = this.ConvertValue(tag.dataType, buffer, tag.numberOfComponents > 1); + object value = this.ConvertValue(tag.dataType, buffer, tag.numberOfComponents > 1 || tag.exif.IsArray); this.Add(values, tag.exif, value); } } @@ -401,11 +401,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return; } - this.ExtTags.Add((newOffset, dataType, numberOfComponents, exifValue)); + this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); } else { - object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1); + object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1 || exifValue.IsArray); this.Add(values, exifValue, value); } } @@ -484,11 +484,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return; } - this.ExtTags.Add((newOffset, dataType, numberOfComponents, exifValue)); + this.BigValues.Add((newOffset, dataType, numberOfComponents, exifValue)); } else { - object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1); + object value = this.ConvertValue(dataType, offsetBuffer.Slice(0, (int)size), numberOfComponents > 1 || exifValue.IsArray); this.Add(values, exifValue, value); } } From 46b6214c8cbe81bc9195762eab81145edd240ed1 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 11:15:03 +0300 Subject: [PATCH 042/511] rename --- .../{BigTIFF_MinIsBlack_Fax3.tiff => BigTIFF_MinIsBlack_Fax3.tif} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/Images/Input/Tiff/BigTiff/{BigTIFF_MinIsBlack_Fax3.tiff => BigTIFF_MinIsBlack_Fax3.tif} (100%) diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tiff b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tif similarity index 100% rename from tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tiff rename to tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tif From 75db00274fe4aba7d91c0fcd62f4321c99672759 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 11:29:53 +0300 Subject: [PATCH 043/511] remove obsolete code --- .../Formats/Tiff/Ifd/DirectoryReader.cs | 16 ---------------- .../Metadata/Profiles/Exif/ExifReader.cs | 5 +---- 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 3aed89bc5..6421db8d8 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -92,21 +92,5 @@ namespace SixLabors.ImageSharp.Formats.Tiff return list; } - - /// - /// used for possibility add a duplicate offsets (but tags don't duplicate). - /// - /// The type of the key. - private class DuplicateKeyComparer : IComparer - where TKey : IComparable - { - public int Compare(TKey x, TKey y) - { - int result = x.CompareTo(y); - - // Handle equality as being greater. - return (result == 0) ? 1 : result; - } - } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index feca52871..9abaf1432 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -172,10 +172,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif protected void ReadValues64(List values, ulong offset) { - if (offset > (ulong)this.data.Length) - { - return; - } + DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .Net Stream.Length can Int64.MaxValue."); this.Seek(offset); ulong count = this.ReadUInt64(); From df0bc4c2227ec019859b42bd655ba7fc4c105693 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 11:30:09 +0300 Subject: [PATCH 044/511] problem test file --- tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index a6f08cbc9..c00336b25 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -31,7 +31,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] - [WithFile(MinIsBlack_Fax3, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); @@ -43,6 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [Theory] [WithFile(MinIsWhite_RLE, PixelTypes.Rgba32)] [WithFile(MinIsBlack_RLE, PixelTypes.Rgba32)] + [WithFile(MinIsBlack_Fax3, PixelTypes.Rgba32)] public void ProblemFiles(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffDecoder(provider)); From 82f64912ea5ffb6e66c075fd8ed67abea7e7a613 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 15:05:49 +0300 Subject: [PATCH 045/511] improve strip offset and strip byte counts parsing --- .../Formats/Tiff/TiffDecoderCore.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 9822cf63b..6692b7991 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Buffers; using System.Collections.Generic; using System.Linq; @@ -218,18 +219,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; - ulong[] stripOffsets; - ulong[] stripByteCounts; - if (this.isBigTiff) - { - stripOffsets = tags.GetValueInternal(ExifTag.StripOffsets)?.GetValue() as ulong[]; - stripByteCounts = tags.GetValueInternal(ExifTag.StripByteCounts)?.GetValue() as ulong[]; - } - else - { - stripOffsets = tags.GetValue(ExifTag.StripOffsets)?.Value.Select(s => (ulong)(uint)s).ToArray(); - stripByteCounts = tags.GetValue(ExifTag.StripByteCounts)?.Value.Select(s => (ulong)(uint)s).ToArray(); - } + var stripOffsets = (Array)tags.GetValueInternal(ExifTag.StripOffsets)?.GetValue(); + var stripByteCounts = (Array)tags.GetValueInternal(ExifTag.StripByteCounts)?.GetValue(); if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) { @@ -293,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// An array of byte offsets to each strip in the image. /// An array of the size of each strip (in bytes). /// The token to monitor cancellation. - private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, ulong[] stripOffsets, ulong[] stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Array stripOffsets, Array stripByteCounts, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int stripsPerPixel = this.BitsPerSample.Channels; @@ -344,10 +335,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff int stripIndex = i; for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) { + ulong offset = stripOffsets.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; + ulong count = stripByteCounts.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; decompressor.Decompress( this.inputStream, - stripOffsets[stripIndex], - stripByteCounts[stripIndex], + offset, + count, stripHeight, stripBuffers[planeIndex].GetSpan()); stripIndex += stripsPerPlane; @@ -374,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The strip offsets. /// The strip byte counts. /// The token to monitor cancellation. - private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, ulong[] stripOffsets, ulong[] stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Array stripOffsets, Array stripByteCounts, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. @@ -387,7 +380,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff int bitsPerPixel = this.BitsPerPixel; using IMemoryOwner stripBuffer = this.memoryAllocator.Allocate(uncompressedStripSize, AllocationOptions.Clean); - System.Span stripBufferSpan = stripBuffer.GetSpan(); + Span stripBufferSpan = stripBuffer.GetSpan(); Buffer2D pixels = frame.PixelBuffer; using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( @@ -430,7 +423,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; } - decompressor.Decompress(this.inputStream, stripOffsets[stripIndex], stripByteCounts[stripIndex], stripHeight, stripBufferSpan); + ulong offset = stripOffsets.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; + ulong count = stripByteCounts.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; + decompressor.Decompress( + this.inputStream, + offset, + count, + stripHeight, + stripBufferSpan); colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); } From 04884d56a99da4bf4b2be84d4ada0e85a01f4915 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 19 Sep 2021 22:13:23 +0300 Subject: [PATCH 046/511] add 1-bit test files, renaming --- .../Formats/Tiff/BigTiffDecoderTests.cs | 25 +++++++++++++------ tests/ImageSharp.Tests/TestImages.cs | 8 +++--- .../Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif | 3 +++ .../Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tif | 3 --- .../Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif | 3 +++ 5 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif delete mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tif create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index c00336b25..131310086 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -31,6 +31,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(BigTIFFSubIFD8, PixelTypes.Rgba32)] [WithFile(Indexed4_Deflate, PixelTypes.Rgba32)] [WithFile(Indexed8_LZW, PixelTypes.Rgba32)] + [WithFile(MinIsBlack, PixelTypes.Rgba32)] + [WithFile(MinIsWhite, PixelTypes.Rgba32)] public void TiffDecoder_CanDecode(TestImageProvider provider) where TPixel : unmanaged, IPixel => TestTiffDecoder(provider); @@ -40,11 +42,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder)); [Theory] - [WithFile(MinIsWhite_RLE, PixelTypes.Rgba32)] - [WithFile(MinIsBlack_RLE, PixelTypes.Rgba32)] - [WithFile(MinIsBlack_Fax3, PixelTypes.Rgba32)] - public void ProblemFiles(TestImageProvider provider) - where TPixel : unmanaged, IPixel => Assert.Throws(() => TestTiffDecoder(provider)); + [WithFile(Damaged_MinIsWhite_RLE, PixelTypes.Rgba32)] + [WithFile(Damaged_MinIsBlack_RLE, PixelTypes.Rgba32)] + public void DamagedFiles(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Assert.Throws(() => TestTiffDecoder(provider)); + + using Image image = provider.GetImage(TiffDecoder); + ExifProfile exif = image.Frames.RootFrame.Metadata.ExifProfile; + + // PhotometricInterpretation is required tag: https://www.awaresystems.be/imaging/tiff/tifftags/photometricinterpretation.html + Assert.Null(exif.GetValueInternal(ExifTag.PhotometricInterpretation)); + } [Theory] [InlineData(BigTIFF, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] @@ -56,9 +66,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [InlineData(BigTIFFSubIFD8, 24, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Indexed4_Deflate, 4, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Indexed8_LZW, 8, 64, 64, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinIsWhite_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinIsBlack_RLE, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] - [InlineData(MinIsBlack_Fax3, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinIsWhite, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] + [InlineData(MinIsBlack, 1, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) { var testFile = TestFile.Create(imagePath); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 7292d232d..b0e67d669 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -689,9 +689,11 @@ namespace SixLabors.ImageSharp.Tests public const string Indexed4_Deflate = Base + "BigTIFF_Indexed4_Deflate.tif"; public const string Indexed8_LZW = Base + "BigTIFF_Indexed8_LZW.tif"; - public const string MinIsWhite_RLE = Base + "BigTIFF_MinIsWhite_RLE.tif"; - public const string MinIsBlack_RLE = Base + "BigTIFF_MinIsBlack_RLE.tif"; - public const string MinIsBlack_Fax3 = Base + "BigTIFF_MinIsBlack_Fax3.tif"; + public const string MinIsBlack = Base + "BigTIFF_MinIsBlack.tif"; + public const string MinIsWhite = Base + "BigTIFF_MinIsWhite.tif"; + + public const string Damaged_MinIsWhite_RLE = Base + "BigTIFF_MinIsWhite_RLE.tif"; + public const string Damaged_MinIsBlack_RLE = Base + "BigTIFF_MinIsBlack_RLE.tif"; } } } diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif new file mode 100644 index 000000000..491b4092e --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac0471c1600f6e5fb47037dab07172aff524abc866a40c9ec54279bd49cbef77 +size 517 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tif deleted file mode 100644 index 952f4fab9..000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsBlack_Fax3.tif +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3853a4850a023bce431bc224145f47e095e9910cddce9ba5fcaf496c9d385e04 -size 564 diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif new file mode 100644 index 000000000..fb26e58a4 --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFF_MinIsWhite.tif @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a95b0b46bf4f75babb86d9ec74694e6d684087504be214df48a6c8a54338834c +size 517 From 7f1376ffff31c6c0d6d4e8add19e2cfd2fb731e5 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 10:29:05 +0300 Subject: [PATCH 047/511] Update src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- .../Metadata/Profiles/Exif/Values/ExifLong8Array.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index d012f646b..d76deba07 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -107,10 +107,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private bool SetArray(long[] values) { var numbers = new ulong[values.Length]; - for (int i = 0; i < values.Length; i++) - { - numbers[i] = (ulong)values[i]; - } + this.Value = Unsafe.As(ref values); this.Value = numbers; return true; From f95102e340e34cfb5f22fec4c436db8b1321fc4f Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 10:30:38 +0300 Subject: [PATCH 048/511] Update src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 4b7433d7d..39d1ff049 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case int[] array: { // workaround for inconsistent covariance of value-typed arrays - if (value.GetType().Equals(typeof(uint[]))) + if (value.GetType() == typeof(uint[])) { return this.SetArray((uint[])value); } From 22d67f9210e74e9a7d762b49c21d3692ce99e427 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 10:30:49 +0300 Subject: [PATCH 049/511] Update src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 39d1ff049..c65e0b460 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case short[] array: { - if (value.GetType().Equals(typeof(ushort[]))) + if (value.GetType() == typeof(ushort[])) { return this.SetArray((ushort[])value); } From f0ef4f9d510c516688a982e94453d87ece5b0f19 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 10:31:46 +0300 Subject: [PATCH 050/511] Update src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 9abaf1432..f36f2cbae 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.Seek(offset); int count = this.ReadUInt16(); - Span offsetBuffer = new byte[4]; + Span offsetBuffer = stackalloc byte[4]; for (int i = 0; i < count; i++) { this.ReadValue(values, offsetBuffer); From 02c891134c946ee2326da3c57ffff221c7b19b11 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 10:31:54 +0300 Subject: [PATCH 051/511] Update src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index f36f2cbae..d9fe29e82 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -177,7 +177,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif this.Seek(offset); ulong count = this.ReadUInt64(); - Span offsetBuffer = new byte[8]; + Span offsetBuffer = stackalloc byte[8]; for (ulong i = 0; i < count; i++) { this.ReadValue64(values, offsetBuffer); From d670edcccf25f1778ed61730d2d888153b45c714 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 10:59:46 +0300 Subject: [PATCH 052/511] build fixes, minor improves --- .../Profiles/Exif/Values/ExifLong8Array.cs | 15 +++++++-------- .../Profiles/Exif/Values/ExifNumberArray.cs | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index d76deba07..618135e92 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -1,6 +1,8 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; + namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal sealed class ExifLong8Array : ExifArrayValue @@ -29,9 +31,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return ExifDataType.Long; } - for (int i = 0; i < this.Value.Length; i++) + foreach (ulong value in this.Value) { - if (this.Value[i] > uint.MaxValue) + if (value > uint.MaxValue) { return ExifDataType.Long8; } @@ -64,7 +66,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case long[] array: { - if (value.GetType().Equals(typeof(ulong[]))) + if (value.GetType() == typeof(ulong[])) { return this.SetArray((ulong[])value); } @@ -74,7 +76,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case int[] array: { - if (value.GetType().Equals(typeof(uint[]))) + if (value.GetType() == typeof(uint[])) { return this.SetArray((uint[])value); } @@ -84,7 +86,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case short[] array: { - if (value.GetType().Equals(typeof(ushort[]))) + if (value.GetType() == typeof(ushort[])) { return this.SetArray((ushort[])value); } @@ -106,10 +108,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private bool SetArray(long[] values) { - var numbers = new ulong[values.Length]; this.Value = Unsafe.As(ref values); - - this.Value = numbers; return true; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index c65e0b460..2d006c538 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -24,9 +24,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return ExifDataType.Short; } - for (int i = 0; i < this.Value.Length; i++) + foreach (Number value in this.Value) { - if (this.Value[i] > ushort.MaxValue) + if (value > ushort.MaxValue) { return ExifDataType.Long; } From eb8b6f25862da4f405f9f0081d07e0ac2cd6f595 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 11:22:57 +0300 Subject: [PATCH 053/511] build fixes --- .../Formats/Tiff/TiffDecoderCore.cs | 32 ++++++++++++++++--- .../Formats/Tiff/TiffThrowHelper.cs | 2 +- .../Profiles/Exif/Values/ExifLong8Array.cs | 2 +- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 6692b7991..9f0a4b514 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -335,8 +335,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff int stripIndex = i; for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) { - ulong offset = stripOffsets.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; - ulong count = stripByteCounts.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; + ulong offset = stripOffsets.GetValue(stripIndex) switch + { + ulong val => val, + Number val => (uint)val, + _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") + }; + + ulong count = stripByteCounts.GetValue(stripIndex) switch + { + ulong val => val, + Number val => (uint)val, + _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") + }; + decompressor.Decompress( this.inputStream, offset, @@ -423,8 +435,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; } - ulong offset = stripOffsets.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; - ulong count = stripByteCounts.GetValue(stripIndex) switch { ulong val => val, Number val => (uint)val }; + ulong offset = stripOffsets.GetValue(stripIndex) switch + { + ulong val => val, + Number val => (uint)val, + _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") + }; + + ulong count = stripByteCounts.GetValue(stripIndex) switch + { + ulong val => val, + Number val => (uint)val, + _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") + }; + decompressor.Decompress( this.inputStream, offset, diff --git a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs index 3c541a786..8223c445a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs +++ b/src/ImageSharp/Formats/Tiff/TiffThrowHelper.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// /// The error message for the exception. [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); + public static Exception ThrowImageFormatException(string errorMessage) => throw new ImageFormatException(errorMessage); [MethodImpl(InliningOptions.ColdPath)] public static Exception NotSupportedDecompressor(string compressionType) => throw new NotSupportedException($"Not supported decoder compression method: {compressionType}"); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index 618135e92..d75e68565 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return ExifDataType.Long; } - foreach (ulong value in this.Value) + foreach (ulong value in this.Value) { if (value > uint.MaxValue) { From dfcb74305b7a04e328ca7241d57105d4ba9f91a0 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 19:51:06 +0300 Subject: [PATCH 054/511] Add long8 tests --- .../Profiles/Exif/Values/ExifLong8.cs | 13 +++ .../Profiles/Exif/Values/ExifLong8Array.cs | 3 + .../Formats/Tiff/BigTiffDecoderTests.cs | 7 +- .../Formats/Tiff/BigTiffMetadataTests.cs | 90 +++++++++++++++++++ 4 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs index 47da962f3..a4a1dd3df 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8.cs @@ -35,6 +35,18 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif switch (value) { + case int intValue: + if (intValue >= uint.MinValue) + { + this.Value = (uint)intValue; + return true; + } + + return false; + case uint uintValue: + this.Value = uintValue; + + return true; case long intValue: if (intValue >= 0) { @@ -44,6 +56,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return false; default: + return false; } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index d75e68565..3d14709a4 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -64,6 +64,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ushort val: return this.SetSingle((ulong)val); + case long val: + return this.SetSingle((ulong)Numerics.Clamp(val, 0, long.MaxValue)); + case long[] array: { if (value.GetType() == typeof(ulong[])) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index 131310086..24fce1d0d 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -3,22 +3,19 @@ // ReSharper disable InconsistentNaming using System; -using System.Linq; using System.IO; +using System.Linq; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; - +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; - using static SixLabors.ImageSharp.Tests.TestImages.BigTiff; -using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [Collection("RunSerial")] [Trait("Format", "Tiff")] - [Trait("Format", "BigTiff")] public class BigTiffDecoderTests : TiffDecoderBaseTester { [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs new file mode 100644 index 000000000..91cfa1efa --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Metadata.Profiles.Exif; + +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.Tiff +{ + [Trait("Format", "Tiff")] + public class BigTiffMetadataTests + { + private static TiffDecoder TiffDecoder => new TiffDecoder(); + + [Fact] + public void ExifLong8() + { + var long8 = new ExifLong8(ExifTagValue.StripByteCounts); + + Assert.True(long8.TrySetValue(0)); + Assert.Equal(0UL, long8.GetValue()); + + Assert.True(long8.TrySetValue(100u)); + Assert.Equal(100UL, long8.GetValue()); + + Assert.True(long8.TrySetValue(ulong.MaxValue)); + Assert.Equal(ulong.MaxValue, long8.GetValue()); + + Assert.False(long8.TrySetValue(-65)); + Assert.Equal(ulong.MaxValue, long8.GetValue()); + } + + [Fact] + public void ExifSignedLong8() + { + var long8 = new ExifSignedLong8(ExifTagValue.ImageID); + + Assert.False(long8.TrySetValue(0)); + + Assert.True(long8.TrySetValue(0L)); + Assert.Equal(0L, long8.GetValue()); + + Assert.True(long8.TrySetValue(-100L)); + Assert.Equal(-100L, long8.GetValue()); + + Assert.True(long8.TrySetValue(100L)); + Assert.Equal(100L, long8.GetValue()); + } + + [Fact] + public void ExifLong8Array() + { + var long8 = new ExifLong8Array(ExifTagValue.StripOffsets); + + Assert.True(long8.TrySetValue((short)-123)); + Assert.Equal(new[] { 0UL }, long8.GetValue()); + + Assert.True(long8.TrySetValue((ushort)123)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); + + Assert.True(long8.TrySetValue((short)123)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); + + Assert.True(long8.TrySetValue(123)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); + + Assert.True(long8.TrySetValue(123L)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); + + Assert.True(long8.TrySetValue(123UL)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); + + Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4 })); + Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL }, long8.GetValue()); + } + + [Fact] + public void ExifSignedLong8Array() + { + var long8 = new ExifSignedLong8Array(ExifTagValue.StripOffsets); + + Assert.True(long8.TrySetValue(new[] { 0L })); + Assert.Equal(new[] { 0L }, long8.GetValue()); + + Assert.True(long8.TrySetValue(new[] { -1L, 2L, -3L, 4L })); + Assert.Equal(new[] { -1L, 2L, -3L, 4L }, long8.GetValue()); + } + } +} From 7185a24baf78ca39ab38be437adfaaf19701bda5 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 20:15:06 +0300 Subject: [PATCH 055/511] minor --- .../Profiles/Exif/Values/ExifLong8Array.cs | 14 ++++++-------- .../Profiles/Exif/Values/ExifNumberArray.cs | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index 3d14709a4..366eb8d12 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -26,16 +26,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { get { - if (this.Value is null) + if (this.Value is not null) { - return ExifDataType.Long; - } - - foreach (ulong value in this.Value) - { - if (value > uint.MaxValue) + foreach (ulong value in this.Value) { - return ExifDataType.Long8; + if (value > uint.MaxValue) + { + return ExifDataType.Long8; + } } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs index 2d006c538..bf9c2cf9a 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifNumberArray.cs @@ -19,16 +19,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { get { - if (this.Value is null) + if (this.Value is not null) { - return ExifDataType.Short; - } - - foreach (Number value in this.Value) - { - if (value > ushort.MaxValue) + foreach (Number value in this.Value) { - return ExifDataType.Long; + if (value > ushort.MaxValue) + { + return ExifDataType.Long; + } } } From 775f43e754f88c337ffbfe690c229d09d83b98a8 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 20:33:14 +0300 Subject: [PATCH 056/511] Add Number tests --- .../Profiles/Exif/Values/ExifValuesTests.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index 3358b1f97..3871ac61c 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -394,6 +394,32 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values Assert.Equal(expected, typed.Value); } + [Fact] + public void NumberTests() + { + Number value1 = ushort.MaxValue; + Number value2 = ushort.MaxValue; + Assert.True(value1 == value2); + + value2 = short.MaxValue; + Assert.True(value1 != value2); + + value2 = -2; + Assert.True(value1 > value2); + + value1 = -6; + Assert.True(value1 <= value2); + + value2 = -2; + Assert.True(value1 >= value2); + + Assert.True(value1.Equals(value2)); + Assert.True(value1.GetHashCode() == value2.GetHashCode()); + + value1 = 1; + Assert.False(value1.Equals(value2)); + } + [Theory] [MemberData(nameof(NumberTags))] public void ExifNumberTests(ExifTag tag) @@ -422,6 +448,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values var typed = (ExifNumberArray)value; Assert.Equal(expected, typed.Value); + + Assert.True(value.TrySetValue(int.MaxValue)); + Assert.Equal(new[] { int.MaxValue }, value.GetValue()); + + Assert.True(value.TrySetValue(new[] { 1u, 2u, 5u })); + Assert.Equal(new[] { 1u, 2u, 5u }, value.GetValue()); + + Assert.True(value.TrySetValue(new[] { (short)1, (short)2, (short)5 })); + Assert.Equal(new[] { (short)1, (short)2, (short)5 }, value.GetValue()); } [Theory] From 02d83c9bd240062d67e94ee3b4d99971d22c2274 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 20:38:53 +0300 Subject: [PATCH 057/511] minor --- .../Metadata/Profiles/Exif/ExifReader.cs | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index d9fe29e82..6107bd766 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -114,6 +114,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif protected void ReadBigValues(List values) { + if (this.BigValues.Count == 0) + { + return; + } + ulong maxSize = 0; foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) in this.BigValues) { @@ -159,14 +164,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif protected void ReadSubIfd(List values) { - if (this.subIfds is null) - { - return; - } - - foreach (ulong subIfdOffset in this.subIfds) + if (this.subIfds is not null) { - this.ReadValues(values, (uint)subIfdOffset); + foreach (ulong subIfdOffset in this.subIfds) + { + this.ReadValues(values, (uint)subIfdOffset); + } } } @@ -196,14 +199,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif protected void ReadSubIfd64(List values) { - if (this.subIfds is null) - { - return; - } - - foreach (ulong subIfdOffset in this.subIfds) + if (this.subIfds is not null) { - this.ReadValues64(values, subIfdOffset); + foreach (ulong subIfdOffset in this.subIfds) + { + this.ReadValues64(values, subIfdOffset); + } } } @@ -517,6 +518,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } else if (exif.Tag == ExifTag.SubIFDs) { + //// didn't find any useful data in SubIFDs ////foreach (object val in (Array)value) ////{ //// this.AddSubIfd(val); From 2c06edc07561ebee678747f6546571624a6f9f99 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 2 Oct 2021 20:51:54 +0300 Subject: [PATCH 058/511] test fixes --- .../Metadata/Profiles/Exif/Values/ExifValuesTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index 3871ac61c..341beca5c 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -404,13 +404,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values value2 = short.MaxValue; Assert.True(value1 != value2); + value1 = -1; value2 = -2; Assert.True(value1 > value2); value1 = -6; Assert.True(value1 <= value2); - value2 = -2; + value1 = 10; + value2 = 10; Assert.True(value1 >= value2); Assert.True(value1.Equals(value2)); @@ -450,13 +452,13 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values Assert.Equal(expected, typed.Value); Assert.True(value.TrySetValue(int.MaxValue)); - Assert.Equal(new[] { int.MaxValue }, value.GetValue()); + Assert.Equal(new[] { (Number)int.MaxValue }, value.GetValue()); Assert.True(value.TrySetValue(new[] { 1u, 2u, 5u })); - Assert.Equal(new[] { 1u, 2u, 5u }, value.GetValue()); + Assert.Equal(new[] { (Number)1u, (Number)2u, (Number)5u }, value.GetValue()); Assert.True(value.TrySetValue(new[] { (short)1, (short)2, (short)5 })); - Assert.Equal(new[] { (short)1, (short)2, (short)5 }, value.GetValue()); + Assert.Equal(new[] { (Number)(short)1, (Number)(short)2, (Number)(short)5 }, value.GetValue()); } [Theory] From e277a0dce68bb0f8212a134e012ac16be286e855 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 15 Oct 2021 10:46:25 +0300 Subject: [PATCH 059/511] Use MemoryAllocator --- .../Formats/Tiff/Ifd/DirectoryReader.cs | 11 +++- .../Formats/Tiff/Ifd/EntryReader.cs | 9 ++-- .../Formats/Tiff/TiffDecoderCore.cs | 4 +- .../Metadata/Profiles/Exif/ExifReader.cs | 52 +++++++++++++++---- 4 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 6421db8d8..121341b46 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Tiff @@ -16,9 +17,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff { private readonly Stream stream; + private readonly MemoryAllocator allocator; + private ulong nextIfdOffset; - public DirectoryReader(Stream stream) => this.stream = stream; + public DirectoryReader(Stream stream, MemoryAllocator allocator) + { + this.stream = stream; + this.allocator = allocator; + } /// /// Gets the byte order. @@ -64,7 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff var readers = new List(); while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length) { - var reader = new EntryReader(this.stream, this.ByteOrder); + var reader = new EntryReader(this.stream, this.ByteOrder, this.allocator); reader.ReadTags(isBigTiff, this.nextIfdOffset); if (reader.BigValues.Count > 0) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index 98cc1deb3..ef614b0e2 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -5,17 +5,16 @@ using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Tiff.Constants; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Exif; namespace SixLabors.ImageSharp.Formats.Tiff { internal class EntryReader : BaseExifReader { - public EntryReader(Stream stream, ByteOrder byteOrder) - : base(stream) - { + public EntryReader(Stream stream, ByteOrder byteOrder, MemoryAllocator allocator) + : base(stream, allocator) => this.IsBigEndian = byteOrder == ByteOrder.BigEndian; - } public List Values { get; } = new List(); @@ -45,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff internal class HeaderReader : BaseExifReader { public HeaderReader(Stream stream, ByteOrder byteOrder) - : base(stream) => + : base(stream, null) => this.IsBigEndian = byteOrder == ByteOrder.BigEndian; public bool IsBigTiff { get; private set; } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 9f0a4b514..98adc5183 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff where TPixel : unmanaged, IPixel { this.inputStream = stream; - var reader = new DirectoryReader(stream); + var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); IEnumerable directories = reader.Read(); this.byteOrder = reader.ByteOrder; @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { this.inputStream = stream; - var reader = new DirectoryReader(stream); + var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator); IEnumerable directories = reader.Read(); ExifProfile rootFrameExifProfile = directories.First(); diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 6107bd766..0633d3715 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -9,13 +10,19 @@ using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Text; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal class ExifReader : BaseExifReader { public ExifReader(byte[] exifData) - : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData)))) + : this(exifData, null) + { + } + + public ExifReader(byte[] exifData, MemoryAllocator allocator) + : base(new MemoryStream(exifData ?? throw new ArgumentNullException(nameof(exifData))), allocator) { } @@ -83,13 +90,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private readonly byte[] buf4 = new byte[4]; private readonly byte[] buf2 = new byte[2]; + private readonly MemoryAllocator allocator; private readonly Stream data; private List invalidTags; private List subIfds; - protected BaseExifReader(Stream stream) => + protected BaseExifReader(Stream stream, MemoryAllocator allocator) + { this.data = stream ?? throw new ArgumentNullException(nameof(stream)); + this.allocator = allocator; + } private delegate TDataType ConverterMethod(ReadOnlySpan data); @@ -110,7 +121,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public bool IsBigEndian { get; protected set; } - public List<(ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif)> BigValues { get; } = new (); + public List<(ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif)> BigValues { get; } = new(); protected void ReadBigValues(List values) { @@ -119,22 +130,41 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return; } - ulong maxSize = 0; + int maxSize = 0; foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) in this.BigValues) { ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); - if (size > maxSize) + if (size > int.MaxValue) + { + ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(size, int.MaxValue, nameof(size)); + } + + if ((int)size > maxSize) { - maxSize = size; + maxSize = (int)size; } } - Span buf = new byte[maxSize]; - foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.BigValues) + if (this.allocator != null) { - ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); - - this.ReadBigValue(values, tag, buf.Slice(0, (int)size)); + // tiff, bigTiff + using IMemoryOwner memory = this.allocator.Allocate(maxSize); + Span buf = memory.GetSpan(); + foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.BigValues) + { + ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + this.ReadBigValue(values, tag, buf.Slice(0, (int)size)); + } + } + else + { + // embedded exif + Span buf = maxSize <= 256 ? stackalloc byte[maxSize] : new byte[maxSize]; + foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.BigValues) + { + ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + this.ReadBigValue(values, tag, buf.Slice(0, (int)size)); + } } this.BigValues.Clear(); From d4784cb683be9bfa072f89b38cd4cfd58ed1d473 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 15 Oct 2021 11:22:13 +0300 Subject: [PATCH 060/511] correct casings --- .../Metadata/Profiles/Exif/ExifReader.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 0633d3715..fbc3b5c8b 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public bool IsBigEndian { get; protected set; } - public List<(ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif)> BigValues { get; } = new(); + public List<(ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif)> BigValues { get; } = new(); protected void ReadBigValues(List values) { @@ -150,9 +150,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif // tiff, bigTiff using IMemoryOwner memory = this.allocator.Allocate(maxSize); Span buf = memory.GetSpan(); - foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.BigValues) + foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) { - ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); this.ReadBigValue(values, tag, buf.Slice(0, (int)size)); } } @@ -160,9 +160,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { // embedded exif Span buf = maxSize <= 256 ? stackalloc byte[maxSize] : new byte[maxSize]; - foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag in this.BigValues) + foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) { - ulong size = tag.numberOfComponents * ExifDataTypes.GetSize(tag.dataType); + ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); this.ReadBigValue(values, tag, buf.Slice(0, (int)size)); } } @@ -217,13 +217,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - protected void ReadBigValue(IList values, (ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) tag, Span buffer) + protected void ReadBigValue(IList values, (ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag, Span buffer) { - this.Seek(tag.offset); + this.Seek(tag.Offset); if (this.TryReadSpan(buffer)) { - object value = this.ConvertValue(tag.dataType, buffer, tag.numberOfComponents > 1 || tag.exif.IsArray); - this.Add(values, tag.exif, value); + object value = this.ConvertValue(tag.DataType, buffer, tag.NumberOfComponents > 1 || tag.Exif.IsArray); + this.Add(values, tag.Exif, value); } } From c6478426a3520fd894bd416f7637e4b51137fe35 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 15 Oct 2021 14:19:52 +0300 Subject: [PATCH 061/511] Update src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs --- src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index fbc3b5c8b..1a39d723a 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -159,7 +159,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif else { // embedded exif - Span buf = maxSize <= 256 ? stackalloc byte[maxSize] : new byte[maxSize]; + Span buf = maxSize <= 256 ? stackalloc byte[256] : new byte[maxSize]; foreach ((ulong Offset, ExifDataType DataType, ulong NumberOfComponents, ExifValue Exif) tag in this.BigValues) { ulong size = tag.NumberOfComponents * ExifDataTypes.GetSize(tag.DataType); From c8974ab4f45096e4ed8c0a63edaa9e1b2bf480bd Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Fri, 15 Oct 2021 14:34:39 +0300 Subject: [PATCH 062/511] build fixes --- src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 121341b46..02035265d 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -76,10 +76,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (reader.BigValues.Count > 0) { - reader.BigValues.Sort((t1, t2) => t1.offset.CompareTo(t2.offset)); + reader.BigValues.Sort((t1, t2) => t1.Offset.CompareTo(t2.Offset)); // this means that most likely all elements are placed before next IFD - if (reader.BigValues[0].offset < reader.NextIfdOffset) + if (reader.BigValues[0].Offset < reader.NextIfdOffset) { reader.ReadBigValues(); } From a67cb01a5f5c396286c0f40216760ef30b2ec0d6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 16 Oct 2021 13:31:32 +0200 Subject: [PATCH 063/511] 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 064/511] 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 4f10036f144b22ca4015c88f7ca028e55726bc98 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Wed, 27 Oct 2021 08:55:49 +0300 Subject: [PATCH 065/511] skipping SubIfd64 (disable SubIfd64 related code) --- .../Formats/Tiff/Ifd/EntryReader.cs | 2 +- .../Metadata/Profiles/Exif/ExifReader.cs | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs index ee7315a7b..58b042e84 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs @@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.ReadValues64(this.Values, ifdOffset); this.NextIfdOffset = this.ReadUInt64(); - this.ReadSubIfd64(this.Values); + //// this.ReadSubIfd64(this.Values); } } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 1a39d723a..bb8e6b9f3 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -227,16 +227,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - protected void ReadSubIfd64(List values) - { - if (this.subIfds is not null) - { - foreach (ulong subIfdOffset in this.subIfds) - { - this.ReadValues64(values, subIfdOffset); - } - } - } + ////protected void ReadSubIfd64(List values) + ////{ + //// if (this.subIfds is not null) + //// { + //// foreach (ulong subIfdOffset in this.subIfds) + //// { + //// this.ReadValues64(values, subIfdOffset); + //// } + //// } + ////} private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) { @@ -482,15 +482,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.TileByteCounts: exifValue = new ExifLong8Array(ExifTagValue.TileByteCounts); break; - case ExifTagValue.SubIFDOffset: - exifValue = new ExifLong8(ExifTagValue.SubIFDOffset); - break; - case ExifTagValue.GPSIFDOffset: - exifValue = new ExifLong8(ExifTagValue.GPSIFDOffset); - break; - case ExifTagValue.SubIFDs: - exifValue = new ExifLong8Array(ExifTagValue.SubIFDs); - break; + ////case ExifTagValue.SubIFDOffset: + //// exifValue = new ExifLong8(ExifTagValue.SubIFDOffset); + //// break; + ////case ExifTagValue.GPSIFDOffset: + //// exifValue = new ExifLong8(ExifTagValue.GPSIFDOffset); + //// break; + ////case ExifTagValue.SubIFDs: + //// exifValue = new ExifLong8Array(ExifTagValue.SubIFDs); + //// break; default: exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); break; From e66145e2d6ea89acd5eb4b6a9431ae0e65789fab Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Wed, 27 Oct 2021 09:59:40 +0300 Subject: [PATCH 066/511] webp test fix --- .../ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index e20ad5136..3ee20cbd1 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -187,7 +187,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif https://exiftool.org/TagNames/EXIF.html */ [InlineData(TestImageWriteFormat.Jpeg, 18)] [InlineData(TestImageWriteFormat.Png, 18)] - [InlineData(TestImageWriteFormat.WebpLossless, 16)] + [InlineData(TestImageWriteFormat.WebpLossless, 18)] public void SetValue(TestImageWriteFormat imageFormat, int expectedProfileValueCount) { Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); From 831501bc3254ed9f4e7018c8aecc41cbcbb187be Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Wed, 27 Oct 2021 10:31:54 +0300 Subject: [PATCH 067/511] comment unused code --- .../Metadata/Profiles/Exif/ExifReader.cs | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index bb8e6b9f3..651ebe422 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -134,10 +134,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif foreach ((ulong offset, ExifDataType dataType, ulong numberOfComponents, ExifValue exif) in this.BigValues) { ulong size = numberOfComponents * ExifDataTypes.GetSize(dataType); - if (size > int.MaxValue) - { - ThrowHelper.ThrowArgumentOutOfRangeExceptionForMustBeLessThanOrEqualTo(size, int.MaxValue, nameof(size)); - } + DebugGuard.MustBeLessThanOrEqualTo(size, int.MaxValue, nameof(size)); if ((int)size > maxSize) { @@ -361,13 +358,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } return ToArray(dataType, buffer, this.ConvertToUInt64); - case ExifDataType.SignedLong8: - if (!isArray) - { - return this.ConvertToInt64(buffer); - } + ////case ExifDataType.SignedLong8: + //// if (!isArray) + //// { + //// return this.ConvertToInt64(buffer); + //// } - return ToArray(dataType, buffer, this.ConvertToUInt64); + //// return ToArray(dataType, buffer, this.ConvertToUInt64); case ExifDataType.Undefined: if (!isArray) { @@ -596,17 +593,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ? this.ConvertToShort(this.buf2) : default; - private long ConvertToInt64(ReadOnlySpan buffer) - { - if (buffer.Length < 8) - { - return default; - } + ////private long ConvertToInt64(ReadOnlySpan buffer) + ////{ + //// if (buffer.Length < 8) + //// { + //// return default; + //// } - return this.IsBigEndian - ? BinaryPrimitives.ReadInt64BigEndian(buffer) - : BinaryPrimitives.ReadInt64LittleEndian(buffer); - } + //// return this.IsBigEndian + //// ? BinaryPrimitives.ReadInt64BigEndian(buffer) + //// : BinaryPrimitives.ReadInt64LittleEndian(buffer); + ////} private ulong ConvertToUInt64(ReadOnlySpan buffer) { From 6e25e2f87e41551e0bb8934fc65cfcfca3e2976e Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Wed, 27 Oct 2021 11:26:56 +0300 Subject: [PATCH 068/511] improve coverage --- .../Tiff/Compression/TiffBaseDecompressor.cs | 6 ++--- .../Formats/Tiff/TiffDecoderCore.cs | 5 +--- .../Profiles/Exif/Values/ExifLong8Array.cs | 17 +++++++------- .../Formats/Tiff/BigTiffMetadataTests.cs | 23 +++++++++++++++---- 4 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs index d5de4937f..986d585d3 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs @@ -36,10 +36,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression /// The output buffer for uncompressed data. public void Decompress(BufferedReadStream stream, ulong stripOffset, ulong stripByteCount, int stripHeight, Span buffer) { - if (stripOffset > long.MaxValue || stripByteCount > long.MaxValue) - { - TiffThrowHelper.ThrowImageFormatException("The StripOffset or StripByteCount value is too big."); - } + DebugGuard.MustBeLessThanOrEqualTo(stripOffset, (ulong)long.MaxValue, nameof(stripOffset)); + DebugGuard.MustBeLessThanOrEqualTo(stripByteCount, (ulong)long.MaxValue, nameof(stripByteCount)); stream.Seek((long)stripOffset, SeekOrigin.Begin); this.Decompress(stream, (int)stripByteCount, stripHeight, buffer); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 98adc5183..419fbc31c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -473,10 +473,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowImageFormatException("The TIFF image frame is missing the ImageWidth"); } - if (((ulong)width.Value) > int.MaxValue) - { - TiffThrowHelper.ThrowImageFormatException("Too big ImageWidth value"); - } + DebugGuard.MustBeLessThanOrEqualTo((ulong)width.Value, (ulong)int.MaxValue, nameof(ExifTag.ImageWidth)); return (int)width.Value; } diff --git a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs index 366eb8d12..eced4e3de 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/Values/ExifLong8Array.cs @@ -7,11 +7,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { internal sealed class ExifLong8Array : ExifArrayValue { - public ExifLong8Array(ExifTag tag) - : base(tag) - { - } - public ExifLong8Array(ExifTagValue tag) : base(tag) { @@ -109,7 +104,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif private bool SetArray(long[] values) { - this.Value = Unsafe.As(ref values); + var numbers = new ulong[values.Length]; + for (int i = 0; i < values.Length; i++) + { + numbers[i] = (ulong)(values[i] < 0 ? 0 : values[i]); + } + + this.Value = numbers; return true; } @@ -124,7 +125,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif var numbers = new ulong[values.Length]; for (int i = 0; i < values.Length; i++) { - numbers[i] = (ulong)values[i]; + numbers[i] = (ulong)Numerics.Clamp(values[i], 0, int.MaxValue); } this.Value = numbers; @@ -148,7 +149,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif var numbers = new ulong[values.Length]; for (int i = 0; i < values.Length; i++) { - numbers[i] = (ulong)values[i]; + numbers[i] = (ulong)Numerics.Clamp(values[i], 0, short.MaxValue); } this.Value = numbers; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index 91cfa1efa..8e90aeea1 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -43,9 +43,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.True(long8.TrySetValue(-100L)); Assert.Equal(-100L, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - Assert.True(long8.TrySetValue(100L)); - Assert.Equal(100L, long8.GetValue()); + Assert.True(long8.TrySetValue(long.MaxValue)); + Assert.Equal(long.MaxValue, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); } [Fact] @@ -65,14 +67,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.True(long8.TrySetValue(123)); Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(123u)); + Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(123L)); Assert.Equal(new[] { 123UL }, long8.GetValue()); Assert.True(long8.TrySetValue(123UL)); Assert.Equal(new[] { 123UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(new short[] { -1, 2, -3, 4 })); + Assert.Equal(new ulong[] { 0, 2UL, 0, 4UL }, long8.GetValue()); + Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4 })); Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL }, long8.GetValue()); + Assert.Equal(ExifDataType.Long, long8.DataType); + + Assert.True(long8.TrySetValue(new[] { 1, 2, 3, 4, long.MaxValue })); + Assert.Equal(new[] { 1UL, 2UL, 3UL, 4UL, (ulong)long.MaxValue }, long8.GetValue()); + Assert.Equal(ExifDataType.Long8, long8.DataType); } [Fact] @@ -82,9 +95,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.True(long8.TrySetValue(new[] { 0L })); Assert.Equal(new[] { 0L }, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); - Assert.True(long8.TrySetValue(new[] { -1L, 2L, -3L, 4L })); - Assert.Equal(new[] { -1L, 2L, -3L, 4L }, long8.GetValue()); + Assert.True(long8.TrySetValue(new[] { -1L, 2L, long.MinValue, 4L })); + Assert.Equal(new[] { -1L, 2L, long.MinValue, 4L }, long8.GetValue()); + Assert.Equal(ExifDataType.SignedLong8, long8.DataType); } } } From 0e3fada06abef8ed1314ba61540d2e4f0211c99c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Wed, 27 Oct 2021 15:55:51 +0300 Subject: [PATCH 069/511] rework number/ulong arrays --- .../Formats/Tiff/TiffDecoderCore.cs | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 419fbc31c..177a93d24 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -219,21 +219,57 @@ namespace SixLabors.ImageSharp.Formats.Tiff int rowsPerStrip = tags.GetValue(ExifTag.RowsPerStrip) != null ? (int)tags.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; - var stripOffsets = (Array)tags.GetValueInternal(ExifTag.StripOffsets)?.GetValue(); - var stripByteCounts = (Array)tags.GetValueInternal(ExifTag.StripByteCounts)?.GetValue(); + var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); + var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); + + IMemoryOwner stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets); + IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span stripByteCounts); if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) { - this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken); + this.DecodeStripsPlanar( + frame, + rowsPerStrip, + stripOffsets, + stripByteCounts, + cancellationToken); } else { - this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken); + this.DecodeStripsChunky( + frame, + rowsPerStrip, + stripOffsets, + stripByteCounts, + cancellationToken); } + stripOffsetsMemory?.Dispose(); + stripByteCountsMemory?.Dispose(); return frame; } + private IMemoryOwner ConvertNumbers(Array array, out Span span) + { + if (array is Number[] numbers) + { + IMemoryOwner memory = this.memoryAllocator.Allocate(numbers.Length); + span = memory.GetSpan(); + for (int i = 0; i < numbers.Length; i++) + { + span[i] = (uint)numbers[i]; + } + + return memory; + } + else + { + DebugGuard.IsTrue(array is ulong[], $"Expected {nameof(UInt64)} array."); + span = (ulong[])array; + return null; + } + } + /// /// Calculates the size (in bytes) for a pixel buffer using the determined color format. /// @@ -284,7 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// An array of byte offsets to each strip in the image. /// An array of the size of each strip (in bytes). /// The token to monitor cancellation. - private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Array stripOffsets, Array stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { int stripsPerPixel = this.BitsPerSample.Channels; @@ -335,26 +371,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff int stripIndex = i; for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) { - ulong offset = stripOffsets.GetValue(stripIndex) switch - { - ulong val => val, - Number val => (uint)val, - _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") - }; - - ulong count = stripByteCounts.GetValue(stripIndex) switch - { - ulong val => val, - Number val => (uint)val, - _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") - }; - decompressor.Decompress( this.inputStream, - offset, - count, + stripOffsets[stripIndex], + stripByteCounts[stripIndex], stripHeight, stripBuffers[planeIndex].GetSpan()); + stripIndex += stripsPerPlane; } @@ -379,7 +402,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The strip offsets. /// The strip byte counts. /// The token to monitor cancellation. - private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Array stripOffsets, Array stripByteCounts, CancellationToken cancellationToken) + private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, Span stripOffsets, Span stripByteCounts, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. @@ -435,24 +458,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff break; } - ulong offset = stripOffsets.GetValue(stripIndex) switch - { - ulong val => val, - Number val => (uint)val, - _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") - }; - - ulong count = stripByteCounts.GetValue(stripIndex) switch - { - ulong val => val, - Number val => (uint)val, - _ => throw TiffThrowHelper.ThrowImageFormatException("Expected Number or Long8 array") - }; - decompressor.Decompress( this.inputStream, - offset, - count, + stripOffsets[stripIndex], + stripByteCounts[stripIndex], stripHeight, stripBufferSpan); From 5839a5a483ded09081a87f8d6ec105ef457176d9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 28 Oct 2021 21:18:06 +0200 Subject: [PATCH 070/511] 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 071/511] 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 072/511] 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 073/511] 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 074/511] 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 075/511] 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 076/511] 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 077/511] 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 078/511] 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 079/511] 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 080/511] 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 081/511] 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 082/511] 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 083/511] 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 084/511] 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 085/511] 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 086/511] 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 087/511] 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 088/511] 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 089/511] 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 090/511] 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 091/511] 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 092/511] 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 093/511] 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 6885d97c8834f3cf29b89380a5cd4cc3d05a9d79 Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Tue, 9 Nov 2021 19:59:36 -0800 Subject: [PATCH 094/511] hoist some calculations out --- .../Quantization/WuQuantizer{TPixel}.cs | 26 +++++++++++++++++++ .../Codecs/EncodeIndexedPng.cs | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index bf4a5ca41..9b8263163 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -418,18 +418,44 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int r = 1; r < IndexCount; r++) { volumeSpan.Clear(); +#if ALTERNATE + int ind1_r = (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + r; +#endif for (int g = 1; g < IndexCount; g++) { areaSpan.Clear(); +#if ALTERNATE + int ind1_g = ind1_r + + (g << (IndexBits + IndexAlphaBits)) + + (g << IndexBits) + + g; + + int r_g = r + g; +#endif + for (int b = 1; b < IndexCount; b++) { +#if ALTERNATE + int ind1_b = ind1_g + + ((r_g + b) << IndexAlphaBits) + + b; +#endif + Moment line = default; for (int a = 1; a < IndexAlphaCount; a++) { +#if ALTERNATE + int ind1 = ind1_b + a; +#else int ind1 = GetPaletteIndex(r, g, b, a); +#endif line += momentSpan[ind1]; areaSpan[a] += line; diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs index de8d41202..b33922262 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs @@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs public void PngCoreWuNoDither() { using var memoryStream = new MemoryStream(); - var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }) }; + var options = new PngEncoder { Quantizer = new WuQuantizer(new QuantizerOptions { Dither = null }), ColorType = PngColorType.Palette }; this.bmpCore.SaveAsPng(memoryStream, options); } } From 6e8def1cc87808965cd0fc5a6f141161cc02de27 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 00:11:44 +0100 Subject: [PATCH 095/511] Add sse2 version of inverse transform --- .../Formats/Webp/Lossy/LossyUtils.cs | 60 ++++-- .../Formats/Webp/Lossy/Vp8Encoding.cs | 204 +++++++++++++++++- 2 files changed, 239 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index 3064ccc03..cfac273c4 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -661,28 +661,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // a20 a21 a22 a23 b20 b21 b22 b23 // a30 a31 a32 a33 b30 b31 b32 b33 // Transpose the two 4x4. - Vector128 transpose00 = Sse2.UnpackLow(b0, b1); - Vector128 transpose01 = Sse2.UnpackLow(b2, b3); - Vector128 transpose02 = Sse2.UnpackHigh(b0, b1); - Vector128 transpose03 = Sse2.UnpackHigh(b2, b3); - - // a00 a10 a01 a11 a02 a12 a03 a13 - // a20 a30 a21 a31 a22 a32 a23 a33 - // b00 b10 b01 b11 b02 b12 b03 b13 - // b20 b30 b21 b31 b22 b32 b23 b33 - Vector128 transpose10 = Sse2.UnpackLow(transpose00.AsInt32(), transpose01.AsInt32()); - Vector128 transpose11 = Sse2.UnpackLow(transpose02.AsInt32(), transpose03.AsInt32()); - Vector128 transpose12 = Sse2.UnpackHigh(transpose00.AsInt32(), transpose01.AsInt32()); - Vector128 transpose13 = Sse2.UnpackHigh(transpose02.AsInt32(), transpose03.AsInt32()); - - // a00 a10 a20 a30 a01 a11 a21 a31 - // b00 b10 b20 b30 b01 b11 b21 b31 - // a02 a12 a22 a32 a03 a13 a23 a33 - // b02 b12 a22 b32 b03 b13 b23 b33 - Vector128 output0 = Sse2.UnpackLow(transpose10.AsInt64(), transpose11.AsInt64()); - Vector128 output1 = Sse2.UnpackHigh(transpose10.AsInt64(), transpose11.AsInt64()); - Vector128 output2 = Sse2.UnpackLow(transpose12.AsInt64(), transpose13.AsInt64()); - Vector128 output3 = Sse2.UnpackHigh(transpose12.AsInt64(), transpose13.AsInt64()); + Vp8Transpose_2_4x4_16b(b0, b1, b2, b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3); // a00 a10 a20 a30 b00 b10 b20 b30 // a01 a11 a21 a31 b01 b11 b21 b31 @@ -728,6 +707,43 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Unsafe.As>(ref outputRef) = result.AsInt32(); return sum[3] + sum[2] + sum[1] + sum[0]; } + + // Transpose two 4x4 16b matrices horizontally stored in registers. + public static void Vp8Transpose_2_4x4_16b(Vector128 b0, Vector128 b1, Vector128 b2, Vector128 b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3) + { + // Transpose the two 4x4. + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + Vector128 transpose00 = Sse2.UnpackLow(b0, b1); + Vector128 transpose01 = Sse2.UnpackLow(b2, b3); + Vector128 transpose02 = Sse2.UnpackHigh(b0, b1); + Vector128 transpose03 = Sse2.UnpackHigh(b2, b3); + + // a00 a10 a01 a11 a02 a12 a03 a13 + // a20 a30 a21 a31 a22 a32 a23 a33 + // b00 b10 b01 b11 b02 b12 b03 b13 + // b20 b30 b21 b31 b22 b32 b23 b33 + Vector128 transpose10 = Sse2.UnpackLow(transpose00.AsInt32(), transpose01.AsInt32()); + Vector128 transpose11 = Sse2.UnpackLow(transpose02.AsInt32(), transpose03.AsInt32()); + Vector128 transpose12 = Sse2.UnpackHigh(transpose00.AsInt32(), transpose01.AsInt32()); + Vector128 transpose13 = Sse2.UnpackHigh(transpose02.AsInt32(), transpose03.AsInt32()); + + // a00 a10 a20 a30 a01 a11 a21 a31 + // b00 b10 b20 b30 b01 b11 b21 b31 + // a02 a12 a22 a32 a03 a13 a23 a33 + // b02 b12 a22 b32 b03 b13 b23 b33 + output0 = Sse2.UnpackLow(transpose10.AsInt64(), transpose11.AsInt64()); + output1 = Sse2.UnpackHigh(transpose10.AsInt64(), transpose11.AsInt64()); + output2 = Sse2.UnpackLow(transpose12.AsInt64(), transpose13.AsInt64()); + output3 = Sse2.UnpackHigh(transpose12.AsInt64(), transpose13.AsInt64()); + + // 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 + } #endif public static void TransformTwo(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 0567a0f27..cb149bec7 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -4,6 +4,11 @@ using System; using System.Buffers.Binary; 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 { @@ -60,6 +65,12 @@ 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(); + + public static readonly Vector128 K2 = Vector128.Create((short)-30068).AsInt16(); +#endif + static Vp8Encoding() { for (int i = -255; i <= 255 + 255; i++) @@ -68,12 +79,199 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } + // Transforms (Paragraph 14.4) + // Does one or two inverse transforms. public static void ITransform(Span reference, Span input, Span dst, bool doTwo, Span scratch) { - ITransformOne(reference, input, dst, scratch); - if (doTwo) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4), scratch); + // 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 inverse + // transforms in parallel). In the case of only one inverse transform, the + // second half of the vectors will just contain random value we'll never + // use nor store. + ref short inputRef = ref MemoryMarshal.GetReference(input); + var in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 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 + if (doTwo) + { + var inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 16)), 0); + var inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 20)), 0); + var inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 24)), 0); + var inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 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.AsInt16()); + Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1.AsInt16()); + Vector128 c3 = Sse2.Subtract(in1, in3); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3.AsInt16(), c4.AsInt16()); + + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1.AsInt16()); + Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2.AsInt16()); + Vector128 d3 = Sse2.Add(in1, in3); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3.AsInt16(), d4.AsInt16()); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a, d); + Vector128 tmp1 = Sse2.Add(b, c); + Vector128 tmp2 = Sse2.Subtract(b, c); + Vector128 tmp3 = Sse2.Subtract(a, d); + + // Transpose the two 4x4. + LossyUtils.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. + var four = Vector128.Create((short)4); + 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, t3); + c4 = Sse2.Subtract(c1, c2); + c = Sse2.Add(c3.AsInt16(), 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, t3); + d4 = Sse2.Add(d1, d2); + d = Sse2.Add(d3.AsInt16(), 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. + LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'ref' and store. + // Load the reference(s). + Vector128 ref0 = Vector128.Zero; + Vector128 ref1 = Vector128.Zero; + Vector128 ref2 = Vector128.Zero; + Vector128 ref3 = Vector128.Zero; + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + if (doTwo) + { + // Load eight bytes/pixels per line. + ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0).AsByte(); + ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0).AsByte(); + ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0).AsByte(); + ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0).AsByte(); + } + else + { + // Load four bytes/pixels per line. + ref0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref referenceRef)).AsByte(); + ref1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps))).AsByte(); + ref2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2))).AsByte(); + ref3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3))).AsByte(); + } + + // Convert to 16b. + ref0 = Sse2.UnpackLow(ref0, Vector128.Zero); + ref1 = Sse2.UnpackLow(ref1, Vector128.Zero); + ref2 = Sse2.UnpackLow(ref2, Vector128.Zero); + ref3 = Sse2.UnpackLow(ref3, Vector128.Zero); + + // Add the inverse transform(s). + Vector128 ref0InvAdded = Sse2.Add(ref0.AsUInt16(), t0.AsUInt16()); + Vector128 ref1InvAdded = Sse2.Add(ref1.AsUInt16(), t1.AsUInt16()); + Vector128 ref2InvAdded = Sse2.Add(ref2.AsUInt16(), t2.AsUInt16()); + Vector128 ref3InvAdded = Sse2.Add(ref3.AsUInt16(), t3.AsUInt16()); + + // Unsigned saturate to 8b. + ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded.AsInt16(), ref0InvAdded.AsInt16()); + ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded.AsInt16(), ref1InvAdded.AsInt16()); + ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded.AsInt16(), ref2InvAdded.AsInt16()); + ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded.AsInt16(), ref3InvAdded.AsInt16()); + + // Unsigned saturate to 8b. + if (doTwo) + { + // Store eight bytes/pixels per line. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + Unsafe.As>(ref outputRef) = ref0; + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1; + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2; + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3; + } + else + { + // Store four bytes/pixels per line. + int output0 = Sse2.ConvertToInt32(ref0.AsInt32()); + int output1 = Sse2.ConvertToInt32(ref1.AsInt32()); + int output2 = Sse2.ConvertToInt32(ref2.AsInt32()); + int output3 = Sse2.ConvertToInt32(ref3.AsInt32()); + + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + 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 + { + ITransformOne(reference, input, dst, scratch); + if (doTwo) + { + ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4), scratch); + } } } From c0d5dfbac59d1ce6937f3be61ccb0841f59e702d Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Wed, 10 Nov 2021 23:55:01 -0800 Subject: [PATCH 096/511] Better variable names --- .../Quantization/WuQuantizer{TPixel}.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 9b8263163..2824f53ee 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -417,45 +417,40 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int r = 1; r < IndexCount; r++) { - volumeSpan.Clear(); -#if ALTERNATE - int ind1_r = (r << ((IndexBits * 2) + IndexAlphaBits)) + + // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the + // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 + // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations + // in the form of ind1* manually. + int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + (r << (IndexBits + IndexAlphaBits + 1)) + (r << (IndexBits * 2)) + (r << (IndexBits + 1)) + r; -#endif + + volumeSpan.Clear(); for (int g = 1; g < IndexCount; g++) { - areaSpan.Clear(); - -#if ALTERNATE - int ind1_g = ind1_r + + int ind1G = ind1R + (g << (IndexBits + IndexAlphaBits)) + (g << IndexBits) + g; - int r_g = r + g; -#endif + + areaSpan.Clear(); for (int b = 1; b < IndexCount; b++) { -#if ALTERNATE - int ind1_b = ind1_g + + int ind1B = ind1G + ((r_g + b) << IndexAlphaBits) + b; -#endif Moment line = default; for (int a = 1; a < IndexAlphaCount; a++) { -#if ALTERNATE - int ind1 = ind1_b + a; -#else - int ind1 = GetPaletteIndex(r, g, b, a); -#endif + int ind1 = ind1B + a; + line += momentSpan[ind1]; areaSpan[a] += line; From 835ecead49cd0e98b223d4e4cb9b32d11190b8b2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 13:58:02 +0100 Subject: [PATCH 097/511] Store only eight bytes per line --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index c0f81b49f..55fa2593c 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); @@ -69,6 +69,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public static readonly Vector128 K1 = Vector128.Create((short)20091).AsInt16(); public static readonly Vector128 K2 = Vector128.Create((short)-30068).AsInt16(); + + public static readonly Vector128 Four = Vector128.Create((short)4); #endif static Vp8Encoding() @@ -85,6 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) + //if (false) { // This implementation makes use of 16-bit fixed point versions of two // multiply constants: @@ -165,8 +168,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. - var four = Vector128.Create((short)4); - Vector128 dc = Sse2.Add(t0.AsInt16(), four); + Vector128 dc = Sse2.Add(t0.AsInt16(), Four); a = Sse2.Add(dc, t2.AsInt16()); b = Sse2.Subtract(dc, t2.AsInt16()); @@ -243,11 +245,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (doTwo) { // Store eight bytes/pixels per line. - ref byte outputRef = ref MemoryMarshal.GetReference(dst); - Unsafe.As>(ref outputRef) = ref0; - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps)) = ref1; - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 2)) = ref2; - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3; + // TODO: avoid pinning, if possible. + fixed (byte* dstPtr = dst) + { + Sse2.StoreScalar((long*)dstPtr, ref0.AsInt64()); + Sse2.StoreScalar((long*)(dstPtr + WebpConstants.Bps), ref0.AsInt64()); + Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 2)), ref0.AsInt64()); + Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 3)), ref0.AsInt64()); + } } else { From 5968de8f779c21d46facd7b088ec8ae05ecb4a7b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 13:58:37 +0100 Subject: [PATCH 098/511] Add sse tests for inverse transform --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 1 - .../Formats/WebP/Vp8EncodingTests.cs | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 55fa2593c..8f8cf7643 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -87,7 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) - //if (false) { // This implementation makes use of 16-bit fixed point versions of two // multiply constants: diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs new file mode 100644 index 000000000..cd5a24d8c --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -0,0 +1,57 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Linq; +using SixLabors.ImageSharp.Formats.Webp.Lossy; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + [Trait("Format", "Webp")] + public class Vp8EncodingTests + { + private static void RunInverseTransformTest() + { + // arrange + 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[] input = { 177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 177, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = new byte[128]; + byte[] expected = + { + 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int[] scratch = new int[16]; + + // act + Vp8Encoding.ITransform(reference, input, dst, true, scratch); + + // assert + Assert.True(dst.SequenceEqual(expected)); + } + + [Fact] + public void InverseTransform_Works() => RunInverseTransformTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void InverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunInverseTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void InverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); +#endif + } +} From 5c0b598ece1dd8ca63664c93c01591310a98a16c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 15:03:37 +0100 Subject: [PATCH 099/511] Fix copy paste mistake --- src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 8f8cf7643..dab466b9a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -248,9 +248,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy fixed (byte* dstPtr = dst) { Sse2.StoreScalar((long*)dstPtr, ref0.AsInt64()); - Sse2.StoreScalar((long*)(dstPtr + WebpConstants.Bps), ref0.AsInt64()); - Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 2)), ref0.AsInt64()); - Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 3)), ref0.AsInt64()); + Sse2.StoreScalar((long*)(dstPtr + WebpConstants.Bps), ref1.AsInt64()); + Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 2)), ref2.AsInt64()); + Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 3)), ref3.AsInt64()); } } else From 6039d2a8719a263d9a71a4cffb8b1325d6384947 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 16:57:40 +0100 Subject: [PATCH 100/511] Better test case --- .../Formats/WebP/Vp8EncodingTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index cd5a24d8c..053496389 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -24,15 +24,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP 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[] input = { 177, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 177, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + short[] input = { 1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; byte[] dst = new byte[128]; byte[] expected = { - 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150, 150, 150, 150, 146, 149, 152, 154, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + 161, 160, 149, 105, 78, 127, 156, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 160, 160, 133, 85, 81, 129, 155, 167, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 156, 147, 109, 76, 85, 130, 153, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 128, 87, 83, 88, 132, 152, 159, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int[] scratch = new int[16]; From abcbc4c48d6bce5543a45003742f98ccd0b7ef9d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 17:01:56 +0100 Subject: [PATCH 101/511] Fix issue: vectors need to be short type --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index dab466b9a..6ec191baa 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -3,6 +3,7 @@ using System; using System.Buffers.Binary; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS @@ -145,14 +146,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 Vector128 c1 = Sse2.MultiplyHigh(in1.AsInt16(), K2.AsInt16()); Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1.AsInt16()); - Vector128 c3 = Sse2.Subtract(in1, in3); + Vector128 c3 = Sse2.Subtract(in1.AsInt16(), in3.AsInt16()); Vector128 c4 = Sse2.Subtract(c1, c2); Vector128 c = Sse2.Add(c3.AsInt16(), c4.AsInt16()); // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1.AsInt16()); Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2.AsInt16()); - Vector128 d3 = Sse2.Add(in1, in3); + Vector128 d3 = Sse2.Add(in1.AsInt16(), in3.AsInt16()); Vector128 d4 = Sse2.Add(d1, d2); Vector128 d = Sse2.Add(d3.AsInt16(), d4.AsInt16()); @@ -174,14 +175,14 @@ 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(t3.AsInt16(), K1); - c3 = Sse2.Subtract(t1, t3); + c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); c4 = Sse2.Subtract(c1, c2); c = Sse2.Add(c3.AsInt16(), 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, t3); + d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); d4 = Sse2.Add(d1, d2); d = Sse2.Add(d3.AsInt16(), d4); @@ -229,10 +230,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ref3 = Sse2.UnpackLow(ref3, Vector128.Zero); // Add the inverse transform(s). - Vector128 ref0InvAdded = Sse2.Add(ref0.AsUInt16(), t0.AsUInt16()); - Vector128 ref1InvAdded = Sse2.Add(ref1.AsUInt16(), t1.AsUInt16()); - Vector128 ref2InvAdded = Sse2.Add(ref2.AsUInt16(), t2.AsUInt16()); - Vector128 ref3InvAdded = Sse2.Add(ref3.AsUInt16(), t3.AsUInt16()); + Vector128 ref0InvAdded = Sse2.Add(ref0.AsInt16(), t0.AsInt16()); + Vector128 ref1InvAdded = Sse2.Add(ref1.AsInt16(), t1.AsInt16()); + Vector128 ref2InvAdded = Sse2.Add(ref2.AsInt16(), t2.AsInt16()); + Vector128 ref3InvAdded = Sse2.Add(ref3.AsInt16(), t3.AsInt16()); // Unsigned saturate to 8b. ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded.AsInt16(), ref0InvAdded.AsInt16()); From 18ecb065a313601b5d81329f99677bc1357ce8d2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 17:13:23 +0100 Subject: [PATCH 102/511] Add tests for executing only one transform --- .../Formats/WebP/Vp8EncodingTests.cs | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index 053496389..c4f8601b1 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -11,7 +11,39 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP [Trait("Format", "Webp")] public class Vp8EncodingTests { - private static void RunInverseTransformTest() + private static void RunOneInverseTransformTest() + { + // arrange + 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[] input = { 1, 216, -48, 0, 96, -24, -48, 24, 0, -24, 24, 0, 0, 0, 0, 0, 38, -240, -72, -24, 0, -24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + byte[] dst = new byte[128]; + byte[] expected = + { + 161, 160, 149, 105, 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, 160, 160, 133, 85, 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, 156, 147, 109, 76, 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, 152, 128, 87, 83, 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 + }; + int[] scratch = new int[16]; + + // act + Vp8Encoding.ITransform(reference, input, dst, false, scratch); + + // assert + Assert.True(dst.SequenceEqual(expected)); + } + + private static void RunTwoInverseTransformTest() { // arrange byte[] reference = @@ -44,14 +76,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP } [Fact] - public void InverseTransform_Works() => RunInverseTransformTest(); + public void OneInverseTransform_Works() => RunOneInverseTransformTest(); + + [Fact] + public void TwoInverseTransform_Works() => RunTwoInverseTransformTest(); #if SUPPORTS_RUNTIME_INTRINSICS [Fact] - public void InverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunInverseTransformTest, HwIntrinsics.AllowAll); + public void OneInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.AllowAll); + + [Fact] + public void OneInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunOneInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void TwoInverseTransform_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.AllowAll); [Fact] - public void InverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); + public void TwoInverseTransform_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTwoInverseTransformTest, HwIntrinsics.DisableHWIntrinsic); #endif } } From 6e548b5e5bace5fa4c58529d616ff14438fc89bf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 17:25:00 +0100 Subject: [PATCH 103/511] Remove unnecessary casts --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 6ec191baa..70500566f 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -144,18 +144,18 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy 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.AsInt16()); - Vector128 c2 = Sse2.MultiplyHigh(in3.AsInt16(), K1.AsInt16()); + 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.AsInt16()); + Vector128 c = Sse2.Add(c3, c4); // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 - Vector128 d1 = Sse2.MultiplyHigh(in1.AsInt16(), K1.AsInt16()); - Vector128 d2 = Sse2.MultiplyHigh(in3.AsInt16(), K2.AsInt16()); + 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.AsInt16(), d4.AsInt16()); + Vector128 d = Sse2.Add(d3, d4); // Second pass. Vector128 tmp0 = Sse2.Add(a, d); @@ -177,14 +177,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); c4 = Sse2.Subtract(c1, c2); - c = Sse2.Add(c3.AsInt16(), c4); + 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.AsInt16(), d4); + d = Sse2.Add(d3, d4); // Second pass. tmp0 = Sse2.Add(a, d); @@ -236,10 +236,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 ref3InvAdded = Sse2.Add(ref3.AsInt16(), t3.AsInt16()); // Unsigned saturate to 8b. - ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded.AsInt16(), ref0InvAdded.AsInt16()); - ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded.AsInt16(), ref1InvAdded.AsInt16()); - ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded.AsInt16(), ref2InvAdded.AsInt16()); - ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded.AsInt16(), ref3InvAdded.AsInt16()); + ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); + ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); + ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); + ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); // Unsigned saturate to 8b. if (doTwo) From a201e8a1427976addf71974adfffc742cf8ab888 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 11 Nov 2021 18:09:38 +0100 Subject: [PATCH 104/511] Avoid pinning --- src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index 70500566f..34a3a5f17 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -242,17 +242,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); // Unsigned saturate to 8b. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); if (doTwo) { // Store eight bytes/pixels per line. - // TODO: avoid pinning, if possible. - fixed (byte* dstPtr = dst) - { - Sse2.StoreScalar((long*)dstPtr, ref0.AsInt64()); - Sse2.StoreScalar((long*)(dstPtr + WebpConstants.Bps), ref1.AsInt64()); - Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 2)), ref2.AsInt64()); - Sse2.StoreScalar((long*)(dstPtr + (WebpConstants.Bps * 3)), ref3.AsInt64()); - } + 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(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3.GetLower(); } else { @@ -262,7 +259,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int output2 = Sse2.ConvertToInt32(ref2.AsInt32()); int output3 = Sse2.ConvertToInt32(ref3.AsInt32()); - ref byte outputRef = ref MemoryMarshal.GetReference(dst); 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; From a2c0900f61fe854ce729a65814a33ec350a7bf5d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Nov 2021 09:12:30 +0100 Subject: [PATCH 105/511] Avx version of CollectColorBlueTransforms --- .../Formats/Webp/Lossless/LosslessUtils.cs | 2 + .../Formats/Webp/Lossless/PredictorEncoder.cs | 60 ++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index f9b97c6c4..defa65b4b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -744,6 +744,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return (float)retVal; } + [MethodImpl(InliningOptions.ShortMethod)] public static byte TransformColorRed(sbyte greenToRed, uint argb) { sbyte green = U32ToS8(argb >> 8); @@ -752,6 +753,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return (byte)(newRed & 0xff); } + [MethodImpl(InliningOptions.ShortMethod)] public static byte TransformColorBlue(sbyte greenToBlue, sbyte redToBlue, uint argb) { sbyte green = U32ToS8(argb >> 8); diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 99504dd48..3d4696d8d 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -48,6 +48,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static readonly Vector128 CollectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); private static readonly Vector128 CollectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); + + private static readonly Vector256 CollectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255); + + private static readonly Vector256 CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 18, 255, 22, 255, 26, 255, 30, 255); + + private static readonly Vector256 CollectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); + + private static readonly Vector256 CollectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + + private static readonly Vector256 CollectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + #endif // This uses C#'s compiler optimization to refer to assembly's static data directly. @@ -1128,7 +1139,54 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse41.IsSupported) + if (Avx2.IsSupported && tileWidth > 16) + { + const int span = 16; + Span values = stackalloc ushort[span]; + var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); + var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); + for (int y = 0; y < tileHeight; y++) + { + Span srcSpan = bgra.Slice(y * stride); +#pragma warning disable SA1503 // Braces should not be omitted + fixed (uint* src = srcSpan) + fixed (ushort* dst = values) + { + for (int x = 0; x + span <= tileWidth; x += span) + { + uint* input0Idx = src + x; + uint* input1Idx = src + x + (span / 2); + Vector256 input0 = Avx.LoadVector256(input0Idx).AsByte(); + Vector256 input1 = Avx.LoadVector256(input1Idx).AsByte(); + Vector256 r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); + Vector256 r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256); + Vector256 r = Avx2.Or(r0, r1); + Vector256 gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256); + Vector256 gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256); + Vector256 gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector256 g = Avx2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask256); + Vector256 a = Avx2.MultiplyHigh(r.AsInt16(), multsr); + Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); + Vector256 c = Avx2.Subtract(gb.AsByte(), b.AsByte()); + Vector256 d = Avx2.Subtract(c, a.AsByte()); + Vector256 e = Avx2.And(d, CollectColorBlueTransformsBlueMask256); + Avx.Store(dst, e.AsUInt16()); + for (int i = 0; i < span; i++) + { + ++histo[values[i]]; + } + } + } +#pragma warning restore SA1503 // Braces should not be omitted + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); + } + } + } + else if (Sse41.IsSupported) { const int span = 8; Span values = stackalloc ushort[span]; From c15e62ce5056cd9081bee9a3e9549ab1d3fe261b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Nov 2021 09:42:34 +0100 Subject: [PATCH 106/511] Avoid pinning --- .../Formats/Webp/Lossless/PredictorEncoder.cs | 144 +++++++++--------- 1 file changed, 69 insertions(+), 75 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 3d4696d8d..d11102c40 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -1079,34 +1079,32 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int y = 0; y < tileHeight; y++) { Span srcSpan = bgra.Slice(y * stride); -#pragma warning disable SA1503 // Braces should not be omitted - fixed (uint* src = srcSpan) - fixed (ushort* dst = values) + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) { - for (int x = 0; x + span <= tileWidth; x += span) + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector128 g0 = Sse2.And(input0, CollectColorRedTransformsGreenMask); // 0 0 | g 0 + Vector128 g1 = Sse2.And(input1, CollectColorRedTransformsGreenMask); + Vector128 g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 + Vector128 a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r + Vector128 a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); + Vector128 a = Sse41.PackUnsignedSaturate(a0, a1); // x r + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr + Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' + Vector128 d = Sse2.And(c, CollectColorRedTransformsAndMask); // 0 r' + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = d.AsUInt16(); + + for (int i = 0; i < span; i++) { - uint* input0Idx = src + x; - uint* input1Idx = src + x + (span / 2); - Vector128 input0 = Sse2.LoadVector128((ushort*)input0Idx).AsByte(); - Vector128 input1 = Sse2.LoadVector128((ushort*)input1Idx).AsByte(); - Vector128 g0 = Sse2.And(input0, CollectColorRedTransformsGreenMask); // 0 0 | g 0 - Vector128 g1 = Sse2.And(input1, CollectColorRedTransformsGreenMask); - Vector128 g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 - Vector128 a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r - Vector128 a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); - Vector128 a = Sse41.PackUnsignedSaturate(a0, a1); // x r - Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr - Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' - Vector128 d = Sse2.And(c, CollectColorRedTransformsAndMask); // 0 r' - Sse2.Store(dst, d.AsUInt16()); - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } + ++histo[values[i]]; } } } -#pragma warning restore SA1503 // Braces should not be omitted int leftOver = tileWidth & (span - 1); if (leftOver > 0) @@ -1148,36 +1146,34 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int y = 0; y < tileHeight; y++) { Span srcSpan = bgra.Slice(y * stride); -#pragma warning disable SA1503 // Braces should not be omitted - fixed (uint* src = srcSpan) - fixed (ushort* dst = values) + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) { - for (int x = 0; x + span <= tileWidth; x += span) + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector256 r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); + Vector256 r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256); + Vector256 r = Avx2.Or(r0, r1); + Vector256 gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256); + Vector256 gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256); + Vector256 gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector256 g = Avx2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask256); + Vector256 a = Avx2.MultiplyHigh(r.AsInt16(), multsr); + Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); + Vector256 c = Avx2.Subtract(gb.AsByte(), b.AsByte()); + Vector256 d = Avx2.Subtract(c, a.AsByte()); + Vector256 e = Avx2.And(d, CollectColorBlueTransformsBlueMask256); + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = e.AsUInt16(); + + for (int i = 0; i < span; i++) { - uint* input0Idx = src + x; - uint* input1Idx = src + x + (span / 2); - Vector256 input0 = Avx.LoadVector256(input0Idx).AsByte(); - Vector256 input1 = Avx.LoadVector256(input1Idx).AsByte(); - Vector256 r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); - Vector256 r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256); - Vector256 r = Avx2.Or(r0, r1); - Vector256 gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256); - Vector256 gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256); - Vector256 gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector256 g = Avx2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask256); - Vector256 a = Avx2.MultiplyHigh(r.AsInt16(), multsr); - Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); - Vector256 c = Avx2.Subtract(gb.AsByte(), b.AsByte()); - Vector256 d = Avx2.Subtract(c, a.AsByte()); - Vector256 e = Avx2.And(d, CollectColorBlueTransformsBlueMask256); - Avx.Store(dst, e.AsUInt16()); - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } + ++histo[values[i]]; } } -#pragma warning restore SA1503 // Braces should not be omitted int leftOver = tileWidth & (span - 1); if (leftOver > 0) @@ -1195,37 +1191,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless for (int y = 0; y < tileHeight; y++) { Span srcSpan = bgra.Slice(y * stride); -#pragma warning disable SA1503 // Braces should not be omitted - fixed (uint* src = srcSpan) - fixed (ushort* dst = values) + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) { - for (int x = 0; x + span <= tileWidth; x += span) + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector128 r0 = Ssse3.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask); + Vector128 r1 = Ssse3.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask); + Vector128 r = Sse2.Or(r0, r1); + Vector128 gb0 = Sse2.And(input0, CollectColorBlueTransformsGreenBlueMask); + Vector128 gb1 = Sse2.And(input1, CollectColorBlueTransformsGreenBlueMask); + Vector128 gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector128 g = Sse2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask); + Vector128 a = Sse2.MultiplyHigh(r.AsInt16(), multsr); + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); + Vector128 c = Sse2.Subtract(gb.AsByte(), b.AsByte()); + Vector128 d = Sse2.Subtract(c, a.AsByte()); + Vector128 e = Sse2.And(d, CollectColorBlueTransformsBlueMask); + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = e.AsUInt16(); + + for (int i = 0; i < span; i++) { - uint* input0Idx = src + x; - uint* input1Idx = src + x + (span / 2); - Vector128 input0 = Sse2.LoadVector128((ushort*)input0Idx).AsByte(); - Vector128 input1 = Sse2.LoadVector128((ushort*)input1Idx).AsByte(); - Vector128 r0 = Ssse3.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask); - Vector128 r1 = Ssse3.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask); - Vector128 r = Sse2.Or(r0, r1); - Vector128 gb0 = Sse2.And(input0, CollectColorBlueTransformsGreenBlueMask); - Vector128 gb1 = Sse2.And(input1, CollectColorBlueTransformsGreenBlueMask); - Vector128 gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector128 g = Sse2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask); - Vector128 a = Sse2.MultiplyHigh(r.AsInt16(), multsr); - Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); - Vector128 c = Sse2.Subtract(gb.AsByte(), b.AsByte()); - Vector128 d = Sse2.Subtract(c, a.AsByte()); - Vector128 e = Sse2.And(d, CollectColorBlueTransformsBlueMask); - Sse2.Store(dst, e.AsUInt16()); - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } + ++histo[values[i]]; } } } -#pragma warning restore SA1503 // Braces should not be omitted int leftOver = tileWidth & (span - 1); if (leftOver > 0) From 8806d6bd24fe58910b68b22d925a3e92c5385e5a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Nov 2021 10:14:34 +0100 Subject: [PATCH 107/511] Add Avx version of CollectColorRedTransforms --- .../Formats/Webp/Lossless/PredictorEncoder.cs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index d11102c40..48c02f0d3 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -39,6 +39,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static readonly Vector128 CollectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte(); + private static readonly Vector256 CollectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte(); + + private static readonly Vector256 CollectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte(); + private static readonly Vector128 CollectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); private static readonly Vector128 CollectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); @@ -1071,7 +1075,48 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse41.IsSupported) + if (Avx2.IsSupported && tileWidth > 16) + { + var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed)); + const int span = 16; + Span values = stackalloc ushort[span]; + for (int y = 0; y < tileHeight; y++) + { + Span srcSpan = bgra.Slice(y * stride); + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) + { + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector256 g0 = Avx2.And(input0, CollectColorRedTransformsGreenMask256); // 0 0 | g 0 + Vector256 g1 = Avx2.And(input1, CollectColorRedTransformsGreenMask256); + Vector256 g = Avx2.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 + Vector256 a0 = Avx2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r + Vector256 a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16); + Vector256 a = Avx2.PackUnsignedSaturate(a0, a1); // x r + Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr + Vector256 c = Avx2.Subtract(a.AsByte(), b.AsByte()); // x r' + Vector256 d = Avx2.And(c, CollectColorRedTransformsAndMask256); // 0 r' + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = d.AsUInt16(); + + for (int i = 0; i < span; i++) + { + ++histo[values[i]]; + } + } + } + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorRedTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToRed, histo); + } + } + else if (Sse41.IsSupported) { var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed)); const int span = 8; From b7059ae23a72f62ae760f453b21d64fbd63057b0 Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:58:58 +0100 Subject: [PATCH 108/511] Add [MethodImpl(InliningOptions.ShortMethod)] Co-authored-by: Anton Firszov --- src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs index c80fd5817..b8986f66f 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/LossyUtils.cs @@ -750,6 +750,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Transpose two 4x4 16b matrices horizontally stored in registers. + [MethodImpl(InliningOptions.ShortMethod)] public static void Vp8Transpose_2_4x4_16b(Vector128 b0, Vector128 b1, Vector128 b2, Vector128 b3, out Vector128 output0, out Vector128 output1, out Vector128 output2, out Vector128 output3) { // Transpose the two 4x4. From 544319e9ea8689e6f257c03e7990136bbfaad53e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Nov 2021 13:18:41 +0100 Subject: [PATCH 109/511] ITransform now always does two transforms --- src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs | 6 +- .../Formats/Webp/Lossy/Vp8Encoding.cs | 277 ++++++++++++------ .../Formats/WebP/Vp8EncodingTests.cs | 4 +- 3 files changed, 192 insertions(+), 95 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs b/src/ImageSharp/Formats/Webp/Lossy/QuantEnc.cs index 38ed80590..2fcea8cee 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]), true, scratch); + Vp8Encoding.ITransform(reference.Slice(WebpLookupTables.Vp8Scan[n]), tmp.Slice(n * 16, 32), yuvOut.Slice(WebpLookupTables.Vp8Scan[n]), scratch); } return nz; @@ -342,7 +342,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span scratch = it.Scratch3.AsSpan(0, 16); Vp8Encoding.FTransform(src, reference, tmp, scratch); int nz = QuantizeBlock(tmp, levels, ref dqm.Y1); - Vp8Encoding.ITransform(reference, tmp, yuvOut, false, scratch); + Vp8Encoding.ITransformOne(reference, tmp, yuvOut, 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]), true, scratch); + Vp8Encoding.ITransform(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 34a3a5f17..bcecdcd75 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -3,7 +3,6 @@ using System; using System.Buffers.Binary; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS @@ -16,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// /// Methods for encoding a VP8 frame. /// - internal static unsafe class Vp8Encoding + internal static class Vp8Encoding { private const int KC1 = 20091 + (1 << 16); @@ -83,8 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Transforms (Paragraph 14.4) - // Does one or two inverse transforms. - public static void ITransform(Span reference, Span input, Span dst, bool doTwo, Span scratch) + // Does two inverse transforms. + public static void ITransform(Span reference, Span input, Span dst, Span scratch) { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) @@ -120,23 +119,20 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // a01 a11 a21 a31 x x x x // a02 a12 a22 a32 x x x x // a03 a13 a23 a33 x x x x - if (doTwo) - { - var inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 16)), 0); - var inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 20)), 0); - var inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 24)), 0); - var inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 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 - } + var inb0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 16)), 0); + var inb1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 20)), 0); + var inb2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 24)), 0); + var inb3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 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. @@ -206,22 +202,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 ref2 = Vector128.Zero; Vector128 ref3 = Vector128.Zero; ref byte referenceRef = ref MemoryMarshal.GetReference(reference); - if (doTwo) - { - // Load eight bytes/pixels per line. - ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0).AsByte(); - ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0).AsByte(); - ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0).AsByte(); - ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0).AsByte(); - } - else - { - // Load four bytes/pixels per line. - ref0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref referenceRef)).AsByte(); - ref1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps))).AsByte(); - ref2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2))).AsByte(); - ref3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3))).AsByte(); - } + + // Load eight bytes/pixels per line. + ref0 = Vector128.Create(Unsafe.As(ref referenceRef), 0).AsByte(); + ref1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps)), 0).AsByte(); + ref2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2)), 0).AsByte(); + ref3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3)), 0).AsByte(); // Convert to 16b. ref0 = Sse2.UnpackLow(ref0, Vector128.Zero); @@ -243,72 +229,183 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Unsigned saturate to 8b. ref byte outputRef = ref MemoryMarshal.GetReference(dst); - if (doTwo) - { - // Store eight bytes/pixels per line. - 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(); - Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3.GetLower(); - } - else - { - // Store four bytes/pixels per line. - int output0 = Sse2.ConvertToInt32(ref0.AsInt32()); - int output1 = Sse2.ConvertToInt32(ref1.AsInt32()); - int output2 = Sse2.ConvertToInt32(ref2.AsInt32()); - int output3 = Sse2.ConvertToInt32(ref3.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; - } + + // Store eight bytes/pixels per line. + 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(); + Unsafe.As>(ref Unsafe.Add(ref outputRef, WebpConstants.Bps * 3)) = ref3.GetLower(); } else #endif { ITransformOne(reference, input, dst, scratch); - if (doTwo) - { - ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4), scratch); - } + ITransformOne(reference.Slice(4), input.Slice(16), dst.Slice(4), scratch); } } public static void ITransformOne(Span reference, Span input, Span dst, Span scratch) { - int i; - Span tmp = scratch.Slice(0, 16); - for (i = 0; i < 4; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - // vertical pass. - int a = input[0] + input[8]; - int b = input[0] - input[8]; - int c = Mul(input[4], KC2) - Mul(input[12], KC1); - int d = Mul(input[4], KC1) + Mul(input[12], KC2); - tmp[0] = a + d; - tmp[1] = b + c; - tmp[2] = b - c; - tmp[3] = a - d; - tmp = tmp.Slice(4); - input = input.Slice(1); - } + // Load and concatenate the transform coefficients (we'll do two inverse + // transforms in parallel). In the case of only one inverse transform, the + // second half of the vectors will just contain random value we'll never + // use nor store. + ref short inputRef = ref MemoryMarshal.GetReference(input); + var in0 = Vector128.Create(Unsafe.As(ref inputRef), 0); + var in1 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 4)), 0); + var in2 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 8)), 0); + var in3 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref inputRef, 12)), 0); - tmp = scratch; - for (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, 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, d); + Vector128 tmp1 = Sse2.Add(b, c); + Vector128 tmp2 = Sse2.Subtract(b, c); + Vector128 tmp3 = Sse2.Subtract(a, d); + + // Transpose the two 4x4. + LossyUtils.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. + LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); + + // Add inverse transform to 'ref' and store. + // Load the reference(s). + Vector128 ref0 = Vector128.Zero; + Vector128 ref1 = Vector128.Zero; + Vector128 ref2 = Vector128.Zero; + Vector128 ref3 = Vector128.Zero; + ref byte referenceRef = ref MemoryMarshal.GetReference(reference); + + // Load four bytes/pixels per line. + ref0 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref referenceRef)).AsByte(); + ref1 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps))).AsByte(); + ref2 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 2))).AsByte(); + ref3 = Sse2.ConvertScalarToVector128Int32(Unsafe.As(ref Unsafe.Add(ref referenceRef, WebpConstants.Bps * 3))).AsByte(); + + // Convert to 16b. + ref0 = Sse2.UnpackLow(ref0, Vector128.Zero); + ref1 = Sse2.UnpackLow(ref1, Vector128.Zero); + ref2 = Sse2.UnpackLow(ref2, Vector128.Zero); + ref3 = Sse2.UnpackLow(ref3, Vector128.Zero); + + // Add the inverse transform(s). + Vector128 ref0InvAdded = Sse2.Add(ref0.AsInt16(), t0.AsInt16()); + Vector128 ref1InvAdded = Sse2.Add(ref1.AsInt16(), t1.AsInt16()); + Vector128 ref2InvAdded = Sse2.Add(ref2.AsInt16(), t2.AsInt16()); + Vector128 ref3InvAdded = Sse2.Add(ref3.AsInt16(), t3.AsInt16()); + + // Unsigned saturate to 8b. + ref0 = Sse2.PackUnsignedSaturate(ref0InvAdded, ref0InvAdded); + ref1 = Sse2.PackUnsignedSaturate(ref1InvAdded, ref1InvAdded); + ref2 = Sse2.PackUnsignedSaturate(ref2InvAdded, ref2InvAdded); + ref3 = Sse2.PackUnsignedSaturate(ref3InvAdded, ref3InvAdded); + + // Unsigned saturate to 8b. + ref byte outputRef = ref MemoryMarshal.GetReference(dst); + + // Store four bytes/pixels per line. + int output0 = Sse2.ConvertToInt32(ref0.AsInt32()); + int output1 = Sse2.ConvertToInt32(ref1.AsInt32()); + int output2 = Sse2.ConvertToInt32(ref2.AsInt32()); + int output3 = Sse2.ConvertToInt32(ref3.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 dc = tmp[0] + 4; - int a = dc + tmp[8]; - int b = dc - tmp[8]; - int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); - int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); - Store(dst, reference, 0, i, a + d); - Store(dst, reference, 1, i, b + c); - Store(dst, reference, 2, i, b - c); - Store(dst, reference, 3, i, a - d); - tmp = tmp.Slice(1); + int i; + Span tmp = scratch.Slice(0, 16); + for (i = 0; i < 4; i++) + { + // vertical pass. + int a = input[0] + input[8]; + int b = input[0] - input[8]; + int c = Mul(input[4], KC2) - Mul(input[12], KC1); + int d = Mul(input[4], KC1) + Mul(input[12], KC2); + tmp[0] = a + d; + tmp[1] = b + c; + tmp[2] = b - c; + tmp[3] = a - d; + tmp = tmp.Slice(4); + input = input.Slice(1); + } + + tmp = scratch; + for (i = 0; i < 4; i++) + { + // horizontal pass. + int dc = tmp[0] + 4; + int a = dc + tmp[8]; + int b = dc - tmp[8]; + int c = Mul(tmp[4], KC2) - Mul(tmp[12], KC1); + int d = Mul(tmp[4], KC1) + Mul(tmp[12], KC2); + Store(dst, reference, 0, i, a + d); + Store(dst, reference, 1, i, b + c); + Store(dst, reference, 2, i, b - c); + Store(dst, reference, 3, i, a - d); + tmp = tmp.Slice(1); + } } } diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index c4f8601b1..17c9beb9b 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP int[] scratch = new int[16]; // act - Vp8Encoding.ITransform(reference, input, dst, false, scratch); + Vp8Encoding.ITransformOne(reference, input, dst, scratch); // assert Assert.True(dst.SequenceEqual(expected)); @@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP int[] scratch = new int[16]; // act - Vp8Encoding.ITransform(reference, input, dst, true, scratch); + Vp8Encoding.ITransform(reference, input, dst, scratch); // assert Assert.True(dst.SequenceEqual(expected)); From 5074ee6204f7c33875ee40988f1dc9bb20211a3b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Nov 2021 13:33:30 +0100 Subject: [PATCH 110/511] Refactor: extract horizontal and vertical pass into methods --- .../Formats/Webp/Lossy/Vp8Encoding.cs | 161 +++++++----------- 1 file changed, 63 insertions(+), 98 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs index bcecdcd75..aa4ab5767 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoding.cs @@ -136,61 +136,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // 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, 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, d); - Vector128 tmp1 = Sse2.Add(b, c); - Vector128 tmp2 = Sse2.Subtract(b, c); - Vector128 tmp3 = Sse2.Subtract(a, d); + InverseTransformVerticalPass(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); // Transpose the two 4x4. LossyUtils.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); + InverseTransformHorizontalPass(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); // Transpose the two 4x4. LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); @@ -266,61 +219,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // 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, 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, d); - Vector128 tmp1 = Sse2.Add(b, c); - Vector128 tmp2 = Sse2.Subtract(b, c); - Vector128 tmp3 = Sse2.Subtract(a, d); + InverseTransformVerticalPass(in0, in2, in1, in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3); // Transpose the two 4x4. LossyUtils.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); + InverseTransformHorizontalPass(t0, t2, t1, t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3); // Transpose the two 4x4. LossyUtils.Vp8Transpose_2_4x4_16b(shifted0, shifted1, shifted2, shifted3, out t0, out t1, out t2, out t3); @@ -409,6 +315,65 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } +#if SUPPORTS_RUNTIME_INTRINSICS + private static void InverseTransformVerticalPass(Vector128 in0, Vector128 in2, Vector128 in1, Vector128 in3, out Vector128 tmp0, out Vector128 tmp1, out Vector128 tmp2, out Vector128 tmp3) + { + 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, 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. + tmp0 = Sse2.Add(a, d); + tmp1 = Sse2.Add(b, c); + tmp2 = Sse2.Subtract(b, c); + tmp3 = Sse2.Subtract(a, d); + } + + private static void InverseTransformHorizontalPass(Vector128 t0, Vector128 t2, Vector128 t1, Vector128 t3, out Vector128 shifted0, out Vector128 shifted1, out Vector128 shifted2, out Vector128 shifted3) + { + Vector128 dc = Sse2.Add(t0.AsInt16(), Four); + Vector128 a = Sse2.Add(dc, t2.AsInt16()); + Vector128 b = Sse2.Subtract(dc, t2.AsInt16()); + + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + Vector128 c1 = Sse2.MultiplyHigh(t1.AsInt16(), K2); + Vector128 c2 = Sse2.MultiplyHigh(t3.AsInt16(), K1); + Vector128 c3 = Sse2.Subtract(t1.AsInt16(), t3.AsInt16()); + Vector128 c4 = Sse2.Subtract(c1, c2); + Vector128 c = Sse2.Add(c3, c4); + + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + Vector128 d1 = Sse2.MultiplyHigh(t1.AsInt16(), K1); + Vector128 d2 = Sse2.MultiplyHigh(t3.AsInt16(), K2); + Vector128 d3 = Sse2.Add(t1.AsInt16(), t3.AsInt16()); + Vector128 d4 = Sse2.Add(d1, d2); + Vector128 d = Sse2.Add(d3, d4); + + // Second pass. + Vector128 tmp0 = Sse2.Add(a, d); + Vector128 tmp1 = Sse2.Add(b, c); + Vector128 tmp2 = Sse2.Subtract(b, c); + Vector128 tmp3 = Sse2.Subtract(a, d); + shifted0 = Sse2.ShiftRightArithmetic(tmp0, 3); + shifted1 = Sse2.ShiftRightArithmetic(tmp1, 3); + shifted2 = Sse2.ShiftRightArithmetic(tmp2, 3); + shifted3 = Sse2.ShiftRightArithmetic(tmp3, 3); + } +#endif + public static void FTransform2(Span src, Span reference, Span output, Span output2, Span scratch) { FTransform(src, reference, output, scratch); From 0e3eda9840bae44b07eeb3b22f4e8696f01c3f98 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Nov 2021 15:33:02 +0100 Subject: [PATCH 111/511] Add tests with and without avx --- .../Formats/Webp/Lossless/PredictorEncoder.cs | 34 +++++++++---------- .../Formats/WebP/PredictorEncoderTests.cs | 6 ++++ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 48c02f0d3..e40045c46 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -587,19 +587,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return (byte)lower; } - else - { - // upper is closer to residual than lower. - if (residual <= boundaryResidual && upper > boundaryResidual) - { - // Halve quantization step to avoid crossing boundary. This midpoint is - // on the same side of boundary as residual because midpoint <= residual - // (since upper is closer than lower) and residual is below the boundary. - return (byte)(lower + (quantization >> 1)); - } - return (byte)(upper & 0xff); + // upper is closer to residual than lower. + if (residual <= boundaryResidual && upper > boundaryResidual) + { + // Halve quantization step to avoid crossing boundary. This midpoint is + // on the same side of boundary as residual because midpoint <= residual + // (since upper is closer than lower) and residual is below the boundary. + return (byte)(lower + (quantization >> 1)); } + + return (byte)(upper & 0xff); } /// @@ -1075,7 +1073,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported && tileWidth > 16) + if (Avx2.IsSupported && tileWidth >= 16) { var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed)); const int span = 16; @@ -1182,7 +1180,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported && tileWidth > 16) + if (Avx2.IsSupported && tileWidth >= 16) { const int span = 16; Span values = stackalloc ushort[span]; @@ -1219,12 +1217,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless ++histo[values[i]]; } } + } - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); - } + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); } } else if (Sse41.IsSupported) diff --git a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs index d78f7e2f2..98c144a90 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/PredictorEncoderTests.cs @@ -40,8 +40,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void ColorSpaceTransform_WithBikeImage_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableSSE41); + + [Fact] + public void ColorSpaceTransform_WithBikeImage_WithoutAvx2_Works() + => FeatureTestRunner.RunWithHwIntrinsicsFeature(ColorSpaceTransform_WithBikeImage_ProducesExpectedData, HwIntrinsics.DisableAVX2); #endif + // Test image: Input\Webp\peak.png private static void RunColorSpaceTransformTestWithPeakImage() { // arrange @@ -99,6 +104,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp Assert.Equal(expectedData, transformData); } + // Test image: Input\Png\Bike.png private static void RunColorSpaceTransformTestWithBikeImage() { // arrange From 57357b076a939011b3a80f5f83700e90e0899c9a Mon Sep 17 00:00:00 2001 From: Kunal Pathak Date: Fri, 12 Nov 2021 07:41:08 -0800 Subject: [PATCH 112/511] Optimize Mark --- .../Quantization/WuQuantizer{TPixel}.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs index 2824f53ee..4218810d7 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs @@ -649,13 +649,35 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization for (int r = cube.RMin + 1; r <= cube.RMax; r++) { + // Currently, RyuJIT hoists the invariants of multi-level nested loop only to the + // immediate outer loop. See https://github.com/dotnet/runtime/issues/61420 + // To ensure the calculation doesn't happen repeatedly, hoist some of the calculations + // in the form of ind1* manually. + int ind1R = (r << ((IndexBits * 2) + IndexAlphaBits)) + + (r << (IndexBits + IndexAlphaBits + 1)) + + (r << (IndexBits * 2)) + + (r << (IndexBits + 1)) + + r; + for (int g = cube.GMin + 1; g <= cube.GMax; g++) { + int ind1G = ind1R + + (g << (IndexBits + IndexAlphaBits)) + + (g << IndexBits) + + g; + int r_g = r + g; + for (int b = cube.BMin + 1; b <= cube.BMax; b++) { + int ind1B = ind1G + + ((r_g + b) << IndexAlphaBits) + + b; + for (int a = cube.AMin + 1; a <= cube.AMax; a++) { - tagSpan[GetPaletteIndex(r, g, b, a)] = label; + int index = ind1B + a; + + tagSpan[index] = label; } } } From 03c2c229bc5805e91da649bef6670f6bb2fe8a68 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 12 Nov 2021 17:02:11 +0100 Subject: [PATCH 113/511] Fix shuffle high mask --- src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index e40045c46..cbde586b7 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static readonly Vector256 CollectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255); - private static readonly Vector256 CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 18, 255, 22, 255, 26, 255, 30, 255); + private static readonly Vector256 CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 18, 255, 22, 255, 26, 255, 30); private static readonly Vector256 CollectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); From 7818ae000460b06034c4fdf5b8aab971ae0eead4 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 13 Nov 2021 15:51:08 +0100 Subject: [PATCH 114/511] 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 115/511] (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 116/511] 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 117/511] 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 118/511] 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 bdd728e4d331aa89124d113051e4483d040d6ca2 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Sun, 14 Nov 2021 10:32:59 +0100 Subject: [PATCH 119/511] Revert a de-optimization from #1734 and add a comment --- src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index d9d42e061..abe59516f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -288,8 +288,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The number of components to write. private void WriteDefineHuffmanTables(int componentCount) { + // This uses a C#'s compiler optimization that refers to the static data segment of the assembly, + // and doesn't incur any allocation at all. // Table identifiers. - ReadOnlySpan headers = stackalloc byte[] + ReadOnlySpan headers = new byte[] { 0x00, 0x10, From c22919d55eb02ee1a553e0dd3d8bb7b67b701d21 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Sun, 14 Nov 2021 10:58:46 +0100 Subject: [PATCH 120/511] Replace Span.Fill(default) calls with Span.Clear() Span.Clear() is more optimized than Span.Fill(default) --- .../Tiff/Compression/Compressors/TiffLzwEncoder.cs | 4 ++-- .../Compression/Decompressors/T6TiffCompression.cs | 12 ++++++++++-- src/ImageSharp/Formats/Webp/Lossless/CostModel.cs | 2 +- .../Formats/Webp/Lossless/HistogramEncoder.cs | 2 +- src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs | 2 +- .../Formats/Webp/Lossless/Vp8LHistogram.cs | 10 +++++----- src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs | 8 ++++---- src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs | 2 +- 8 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs index baeabdbb2..d4d1d1cb6 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs @@ -256,8 +256,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors private void ResetTables() { - this.children.GetSpan().Fill(0); - this.siblings.GetSpan().Fill(0); + this.children.GetSpan().Clear(); + this.siblings.GetSpan().Clear(); this.bitsPerCode = MinBits; this.maxCode = MaxValue(this.bitsPerCode); this.nextValidCode = EoiCode + 1; diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs index e86418741..972f4d8ff 100644 --- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs @@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors uint bitsWritten = 0; for (int y = 0; y < height; y++) { - scanLine.Fill(0); + scanLine.Clear(); Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine); bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten); @@ -116,7 +116,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors { // If a TIFF reader encounters EOFB before the expected number of lines has been extracted, // it is appropriate to assume that the missing rows consist entirely of white pixels. - scanline.Fill(whiteIsZero ? (byte)0 : (byte)255); + if (whiteIsZero) + { + scanline.Clear(); + } + else + { + scanline.Fill((byte)255); + } + break; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs index 7f4d0307b..bdaf30dc9 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostModel.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (nonzeros <= 1) { - output.AsSpan(0, numSymbols).Fill(0); + output.AsSpan(0, numSymbols).Clear(); } else { diff --git a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs index 5d407d73c..b52f8eb5d 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HistogramEncoder.cs @@ -287,7 +287,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Create a mapping from a cluster id to its minimal version. int clusterMax = 0; - clusterMappingsTmp.AsSpan().Fill(0); + clusterMappingsTmp.AsSpan().Clear(); // Re-map the ids. for (int i = 0; i < symbols.Length; i++) diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 3c81f1a22..5db01ca1c 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs @@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static void CreateHuffmanTree(uint[] histogram, int treeDepthLimit, bool[] bufRle, HuffmanTree[] huffTree, HuffmanTreeCode huffCode) { int numSymbols = huffCode.NumSymbols; - bufRle.AsSpan().Fill(false); + bufRle.AsSpan().Clear(); OptimizeHuffmanForRle(numSymbols, bufRle, histogram); GenerateOptimalTree(huffTree, histogram, numSymbols, treeDepthLimit, huffCode.CodeLengths); diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index 8b0201568..bdb53f5c6 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -320,7 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - output.Literal.AsSpan(0, literalSize).Fill(0); + output.Literal.AsSpan(0, literalSize).Clear(); } } @@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - output.Red.AsSpan(0, size).Fill(0); + output.Red.AsSpan(0, size).Clear(); } } @@ -366,7 +366,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - output.Blue.AsSpan(0, size).Fill(0); + output.Blue.AsSpan(0, size).Clear(); } } @@ -389,7 +389,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - output.Alpha.AsSpan(0, size).Fill(0); + output.Alpha.AsSpan(0, size).Clear(); } } @@ -412,7 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } else { - output.Distance.AsSpan(0, size).Fill(0); + output.Distance.AsSpan(0, size).Clear(); } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs index 6279aef65..fcd61f2c0 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8EncIterator.cs @@ -911,7 +911,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.LeftNz[8] = 0; - this.LeftDerr.AsSpan().Fill(0); + this.LeftDerr.AsSpan().Clear(); } private void InitTop() @@ -919,14 +919,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int topSize = this.mbw * 16; this.YTop.AsSpan(0, topSize).Fill(127); this.UvTop.AsSpan().Fill(127); - this.Nz.AsSpan().Fill(0); + this.Nz.AsSpan().Clear(); int predsW = (4 * this.mbw) + 1; int predsH = (4 * this.mbh) + 1; int predsSize = predsW * predsH; - this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Fill(0); + this.Preds.AsSpan(predsSize + this.predsWidth, this.mbw).Clear(); - this.TopDerr.AsSpan().Fill(0); + this.TopDerr.AsSpan().Clear(); } private int Bit(uint nz, int n) => (nz & (1 << n)) != 0 ? 1 : 0; diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 8a4115d21..37e09d080 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -546,7 +546,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int predsW = (4 * this.Mbw) + 1; int predsH = (4 * this.Mbh) + 1; int predsSize = predsW * predsH; - this.Preds.AsSpan(predsSize + this.PredsWidth - 4, 4).Fill(0); + this.Preds.AsSpan(predsSize + this.PredsWidth - 4, 4).Clear(); this.Nz[0] = 0; // constant } From 55f04f6323181dd2e96fde512c60c628563b4834 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 15 Nov 2021 11:40:22 +1100 Subject: [PATCH 121/511] Update PredictorEncoder.cs --- src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index cbde586b7..95c9065b3 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -53,9 +53,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static readonly Vector128 CollectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); - private static readonly Vector256 CollectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255); + private static readonly Vector256 CollectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255); - private static readonly Vector256 CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 18, 255, 22, 255, 26, 255, 30); + private static readonly Vector256 CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30); private static readonly Vector256 CollectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); From 345e7c640d36dd2e7bfc85a0551664f51353fb67 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Nov 2021 11:34:12 +0100 Subject: [PATCH 122/511] Move color space transform methods into own class --- .../Webp/Lossless/ColorSpaceTransformUtils.cs | 268 ++++++++++++++++++ .../Formats/Webp/Lossless/PredictorEncoder.cs | 262 +---------------- 2 files changed, 270 insertions(+), 260 deletions(-) create mode 100644 src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs new file mode 100644 index 000000000..4a8488f1b --- /dev/null +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs @@ -0,0 +1,268 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +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.Lossless +{ + internal static class ColorSpaceTransformUtils + { +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector128 CollectColorRedTransformsGreenMask = Vector128.Create(0x00ff00).AsByte(); + + private static readonly Vector128 CollectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte(); + + private static readonly Vector256 CollectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte(); + + private static readonly Vector256 CollectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte(); + + private static readonly Vector128 CollectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + + private static readonly Vector128 CollectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); + + private static readonly Vector128 CollectColorBlueTransformsBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + + private static readonly Vector128 CollectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); + + private static readonly Vector128 CollectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); + + private static readonly Vector256 CollectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255); + + private static readonly Vector256 CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30); + + private static readonly Vector256 CollectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); + + private static readonly Vector256 CollectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + + private static readonly Vector256 CollectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); +#endif + + public static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported && tileWidth >= 16) + { + const int span = 16; + Span values = stackalloc ushort[span]; + var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); + var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); + for (int y = 0; y < tileHeight; y++) + { + Span srcSpan = bgra.Slice(y * stride); + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) + { + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector256 r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); + Vector256 r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256); + Vector256 r = Avx2.Or(r0, r1); + Vector256 gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256); + Vector256 gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256); + Vector256 gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector256 g = Avx2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask256); + Vector256 a = Avx2.MultiplyHigh(r.AsInt16(), multsr); + Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); + Vector256 c = Avx2.Subtract(gb.AsByte(), b.AsByte()); + Vector256 d = Avx2.Subtract(c, a.AsByte()); + Vector256 e = Avx2.And(d, CollectColorBlueTransformsBlueMask256); + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = e.AsUInt16(); + + for (int i = 0; i < span; i++) + { + ++histo[values[i]]; + } + } + } + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); + } + } + else if (Sse41.IsSupported) + { + const int span = 8; + Span values = stackalloc ushort[span]; + var multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue)); + var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue)); + for (int y = 0; y < tileHeight; y++) + { + Span srcSpan = bgra.Slice(y * stride); + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) + { + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector128 r0 = Ssse3.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask); + Vector128 r1 = Ssse3.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask); + Vector128 r = Sse2.Or(r0, r1); + Vector128 gb0 = Sse2.And(input0, CollectColorBlueTransformsGreenBlueMask); + Vector128 gb1 = Sse2.And(input1, CollectColorBlueTransformsGreenBlueMask); + Vector128 gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); + Vector128 g = Sse2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask); + Vector128 a = Sse2.MultiplyHigh(r.AsInt16(), multsr); + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); + Vector128 c = Sse2.Subtract(gb.AsByte(), b.AsByte()); + Vector128 d = Sse2.Subtract(c, a.AsByte()); + Vector128 e = Sse2.And(d, CollectColorBlueTransformsBlueMask); + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = e.AsUInt16(); + + for (int i = 0; i < span; i++) + { + ++histo[values[i]]; + } + } + } + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); + } + } + else +#endif + { + CollectColorBlueTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); + } + } + + private static void CollectColorBlueTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) + { + int pos = 0; + while (tileHeight-- > 0) + { + for (int x = 0; x < tileWidth; x++) + { + int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, bgra[pos + x]); + ++histo[idx]; + } + + pos += stride; + } + } + + public static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported && tileWidth >= 16) + { + var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed)); + const int span = 16; + Span values = stackalloc ushort[span]; + for (int y = 0; y < tileHeight; y++) + { + Span srcSpan = bgra.Slice(y * stride); + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) + { + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector256 g0 = Avx2.And(input0, CollectColorRedTransformsGreenMask256); // 0 0 | g 0 + Vector256 g1 = Avx2.And(input1, CollectColorRedTransformsGreenMask256); + Vector256 g = Avx2.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 + Vector256 a0 = Avx2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r + Vector256 a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16); + Vector256 a = Avx2.PackUnsignedSaturate(a0, a1); // x r + Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr + Vector256 c = Avx2.Subtract(a.AsByte(), b.AsByte()); // x r' + Vector256 d = Avx2.And(c, CollectColorRedTransformsAndMask256); // 0 r' + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = d.AsUInt16(); + + for (int i = 0; i < span; i++) + { + ++histo[values[i]]; + } + } + } + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorRedTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToRed, histo); + } + } + else if (Sse41.IsSupported) + { + var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed)); + const int span = 8; + Span values = stackalloc ushort[span]; + for (int y = 0; y < tileHeight; y++) + { + Span srcSpan = bgra.Slice(y * stride); + ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); + for (int x = 0; x + span <= tileWidth; x += span) + { + int input0Idx = x; + int input1Idx = x + (span / 2); + Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); + Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); + Vector128 g0 = Sse2.And(input0, CollectColorRedTransformsGreenMask); // 0 0 | g 0 + Vector128 g1 = Sse2.And(input1, CollectColorRedTransformsGreenMask); + Vector128 g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 + Vector128 a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r + Vector128 a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); + Vector128 a = Sse41.PackUnsignedSaturate(a0, a1); // x r + Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr + Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' + Vector128 d = Sse2.And(c, CollectColorRedTransformsAndMask); // 0 r' + + ref ushort outputRef = ref MemoryMarshal.GetReference(values); + Unsafe.As>(ref outputRef) = d.AsUInt16(); + + for (int i = 0; i < span; i++) + { + ++histo[values[i]]; + } + } + } + + int leftOver = tileWidth & (span - 1); + if (leftOver > 0) + { + CollectColorRedTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToRed, histo); + } + } + else +#endif + { + CollectColorRedTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToRed, histo); + } + } + + private static void CollectColorRedTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) + { + int pos = 0; + while (tileHeight-- > 0) + { + for (int x = 0; x < tileWidth; x++) + { + int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, bgra[pos + x]); + ++histo[idx]; + } + + pos += stride; + } + } + } +} diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 95c9065b3..1f7b284e9 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -5,11 +5,6 @@ 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.Lossless { /// @@ -34,37 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private const int PredLowEffort = 11; -#if SUPPORTS_RUNTIME_INTRINSICS - private static readonly Vector128 CollectColorRedTransformsGreenMask = Vector128.Create(0x00ff00).AsByte(); - - private static readonly Vector128 CollectColorRedTransformsAndMask = Vector128.Create((short)0xff).AsByte(); - - private static readonly Vector256 CollectColorRedTransformsGreenMask256 = Vector256.Create(0x00ff00).AsByte(); - - private static readonly Vector256 CollectColorRedTransformsAndMask256 = Vector256.Create((short)0xff).AsByte(); - - private static readonly Vector128 CollectColorBlueTransformsGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - - private static readonly Vector128 CollectColorBlueTransformsGreenBlueMask = Vector128.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); - - private static readonly Vector128 CollectColorBlueTransformsBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - - private static readonly Vector128 CollectColorBlueTransformsShuffleLowMask = Vector128.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255); - - private static readonly Vector128 CollectColorBlueTransformsShuffleHighMask = Vector128.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14); - - private static readonly Vector256 CollectColorBlueTransformsShuffleLowMask256 = Vector256.Create(255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30, 255, 255, 255, 255, 255, 255, 255, 255); - - private static readonly Vector256 CollectColorBlueTransformsShuffleHighMask256 = Vector256.Create(255, 255, 255, 255, 255, 255, 255, 255, 255, 2, 255, 6, 255, 10, 255, 14, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 22, 255, 26, 255, 30); - - private static readonly Vector256 CollectColorBlueTransformsGreenBlueMask256 = Vector256.Create(255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0); - - private static readonly Vector256 CollectColorBlueTransformsBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); - - private static readonly Vector256 CollectColorBlueTransformsGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); - -#endif - // This uses C#'s compiler optimization to refer to assembly's static data directly. private static ReadOnlySpan DeltaLut => new sbyte[] { 16, 16, 8, 4, 2, 2, 2 }; @@ -993,7 +957,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span histo = scratch.Slice(0, 256); histo.Clear(); - CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); + ColorSpaceTransformUtils.CollectColorRedTransforms(argb, stride, tileWidth, tileHeight, greenToRed, histo); double curDiff = PredictionCostCrossColor(accumulatedRedHisto, histo); if ((byte)greenToRed == prevX.GreenToRed) @@ -1031,7 +995,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Span histo = scratch.Slice(0, 256); histo.Clear(); - CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); + ColorSpaceTransformUtils.CollectColorBlueTransforms(argb, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); double curDiff = PredictionCostCrossColor(accumulatedBlueHisto, histo); if ((byte)greenToBlue == prevX.GreenToBlue) { @@ -1070,228 +1034,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return curDiff; } - private static void CollectColorRedTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported && tileWidth >= 16) - { - var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToRed)); - const int span = 16; - Span values = stackalloc ushort[span]; - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra.Slice(y * stride); - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) - { - int input0Idx = x; - int input1Idx = x + (span / 2); - Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector256 g0 = Avx2.And(input0, CollectColorRedTransformsGreenMask256); // 0 0 | g 0 - Vector256 g1 = Avx2.And(input1, CollectColorRedTransformsGreenMask256); - Vector256 g = Avx2.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 - Vector256 a0 = Avx2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r - Vector256 a1 = Avx2.ShiftRightLogical(input1.AsInt32(), 16); - Vector256 a = Avx2.PackUnsignedSaturate(a0, a1); // x r - Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); // x dr - Vector256 c = Avx2.Subtract(a.AsByte(), b.AsByte()); // x r' - Vector256 d = Avx2.And(c, CollectColorRedTransformsAndMask256); // 0 r' - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = d.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorRedTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToRed, histo); - } - } - else if (Sse41.IsSupported) - { - var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToRed)); - const int span = 8; - Span values = stackalloc ushort[span]; - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra.Slice(y * stride); - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) - { - int input0Idx = x; - int input1Idx = x + (span / 2); - Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector128 g0 = Sse2.And(input0, CollectColorRedTransformsGreenMask); // 0 0 | g 0 - Vector128 g1 = Sse2.And(input1, CollectColorRedTransformsGreenMask); - Vector128 g = Sse41.PackUnsignedSaturate(g0.AsInt32(), g1.AsInt32()); // g 0 - Vector128 a0 = Sse2.ShiftRightLogical(input0.AsInt32(), 16); // 0 0 | x r - Vector128 a1 = Sse2.ShiftRightLogical(input1.AsInt32(), 16); - Vector128 a = Sse41.PackUnsignedSaturate(a0, a1); // x r - Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); // x dr - Vector128 c = Sse2.Subtract(a.AsByte(), b.AsByte()); // x r' - Vector128 d = Sse2.And(c, CollectColorRedTransformsAndMask); // 0 r' - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = d.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorRedTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToRed, histo); - } - } - else -#endif - { - CollectColorRedTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToRed, histo); - } - } - - private static void CollectColorRedTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToRed, Span histo) - { - int pos = 0; - while (tileHeight-- > 0) - { - for (int x = 0; x < tileWidth; x++) - { - int idx = LosslessUtils.TransformColorRed((sbyte)greenToRed, bgra[pos + x]); - ++histo[idx]; - } - - pos += stride; - } - } - - private static void CollectColorBlueTransforms(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (Avx2.IsSupported && tileWidth >= 16) - { - const int span = 16; - Span values = stackalloc ushort[span]; - var multsr = Vector256.Create(LosslessUtils.Cst5b(redToBlue)); - var multsg = Vector256.Create(LosslessUtils.Cst5b(greenToBlue)); - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra.Slice(y * stride); - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) - { - int input0Idx = x; - int input1Idx = x + (span / 2); - Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector256 r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); - Vector256 r1 = Avx2.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask256); - Vector256 r = Avx2.Or(r0, r1); - Vector256 gb0 = Avx2.And(input0, CollectColorBlueTransformsGreenBlueMask256); - Vector256 gb1 = Avx2.And(input1, CollectColorBlueTransformsGreenBlueMask256); - Vector256 gb = Avx2.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector256 g = Avx2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask256); - Vector256 a = Avx2.MultiplyHigh(r.AsInt16(), multsr); - Vector256 b = Avx2.MultiplyHigh(g.AsInt16(), multsg); - Vector256 c = Avx2.Subtract(gb.AsByte(), b.AsByte()); - Vector256 d = Avx2.Subtract(c, a.AsByte()); - Vector256 e = Avx2.And(d, CollectColorBlueTransformsBlueMask256); - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = e.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); - } - } - else if (Sse41.IsSupported) - { - const int span = 8; - Span values = stackalloc ushort[span]; - var multsr = Vector128.Create(LosslessUtils.Cst5b(redToBlue)); - var multsg = Vector128.Create(LosslessUtils.Cst5b(greenToBlue)); - for (int y = 0; y < tileHeight; y++) - { - Span srcSpan = bgra.Slice(y * stride); - ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) - { - int input0Idx = x; - int input1Idx = x + (span / 2); - Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); - Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); - Vector128 r0 = Ssse3.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask); - Vector128 r1 = Ssse3.Shuffle(input1, CollectColorBlueTransformsShuffleHighMask); - Vector128 r = Sse2.Or(r0, r1); - Vector128 gb0 = Sse2.And(input0, CollectColorBlueTransformsGreenBlueMask); - Vector128 gb1 = Sse2.And(input1, CollectColorBlueTransformsGreenBlueMask); - Vector128 gb = Sse41.PackUnsignedSaturate(gb0.AsInt32(), gb1.AsInt32()); - Vector128 g = Sse2.And(gb.AsByte(), CollectColorBlueTransformsGreenMask); - Vector128 a = Sse2.MultiplyHigh(r.AsInt16(), multsr); - Vector128 b = Sse2.MultiplyHigh(g.AsInt16(), multsg); - Vector128 c = Sse2.Subtract(gb.AsByte(), b.AsByte()); - Vector128 d = Sse2.Subtract(c, a.AsByte()); - Vector128 e = Sse2.And(d, CollectColorBlueTransformsBlueMask); - - ref ushort outputRef = ref MemoryMarshal.GetReference(values); - Unsafe.As>(ref outputRef) = e.AsUInt16(); - - for (int i = 0; i < span; i++) - { - ++histo[values[i]]; - } - } - } - - int leftOver = tileWidth & (span - 1); - if (leftOver > 0) - { - CollectColorBlueTransformsNoneVectorized(bgra.Slice(tileWidth - leftOver), stride, leftOver, tileHeight, greenToBlue, redToBlue, histo); - } - } - else -#endif - { - CollectColorBlueTransformsNoneVectorized(bgra, stride, tileWidth, tileHeight, greenToBlue, redToBlue, histo); - } - } - - private static void CollectColorBlueTransformsNoneVectorized(Span bgra, int stride, int tileWidth, int tileHeight, int greenToBlue, int redToBlue, Span histo) - { - int pos = 0; - while (tileHeight-- > 0) - { - for (int x = 0; x < tileWidth; x++) - { - int idx = LosslessUtils.TransformColorBlue((sbyte)greenToBlue, (sbyte)redToBlue, bgra[pos + x]); - ++histo[idx]; - } - - pos += stride; - } - } - private static float PredictionCostSpatialHistogram(int[][] accumulated, int[][] tile) { double retVal = 0.0d; From 4d5af9c4a9f83a365859d7b49200c696facbc9a7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Nov 2021 13:44:12 +0100 Subject: [PATCH 123/511] Additional tests for color transforms --- .../WebP/ColorSpaceTransformUtilsTests.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs diff --git a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs new file mode 100644 index 000000000..5306a8c78 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Webp.Lossless; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + [Trait("Format", "Webp")] + public class ColorSpaceTransformUtilsTests + { + private static void RunCollectColorBlueTransformsTest() + { + uint[] pixelData = + { + 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, + 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 + }; + + int[] expectedOutput = + { + 31, 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, 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, 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, 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, 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, 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, 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, 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, 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, 0 + }; + + int[] histo = new int[256]; + ColorSpaceTransformUtils.CollectColorBlueTransforms(pixelData, 0, 32, 1, 0, 0, histo); + + Assert.Equal(expectedOutput, histo); + } + + private static void RunCollectColorRedTransformsTest() + { + uint[] pixelData = + { + 3074, 256, 256, 256, 0, 65280, 65280, 65280, 256, 256, 0, 256, 0, 65280, 0, 65280, 16711680, 256, + 256, 0, 65024, 0, 256, 256, 0, 65280, 0, 65280, 0, 256, 0, 256 + }; + + int[] expectedOutput = + { + 31, 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, 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, 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, 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, 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, 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, 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, + 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, 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, 0, 0, 1 + }; + + int[] histo = new int[256]; + ColorSpaceTransformUtils.CollectColorRedTransforms(pixelData, 0, 32, 1, 0, histo); + + Assert.Equal(expectedOutput, histo); + } + + [Fact] + public void CollectColorBlueTransforms_Works() => RunCollectColorBlueTransformsTest(); + + [Fact] + public void CollectColorRedTransforms_Works() => RunCollectColorRedTransformsTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void CollectColorBlueTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.AllowAll); + + [Fact] + public void CollectColorBlueTransforms_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableSSE41); + + [Fact] + public void CollectColorBlueTransforms_WithoutAvx2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorBlueTransformsTest, HwIntrinsics.DisableAVX2); + + [Fact] + public void CollectColorRedTransforms_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.AllowAll); + + [Fact] + public void CollectColorRedTransforms_WithoutSSE41_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableSSE41); + + [Fact] + public void CollectColorRedTransforms_WithoutAvx2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCollectColorRedTransformsTest, HwIntrinsics.DisableAVX2); +#endif + + } +} From c76518b114673cc1e8ff6d6f574978ad2a970b21 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Nov 2021 15:02:51 +0100 Subject: [PATCH 124/511] Add AVX version of TransformColor --- .../Formats/Webp/Lossless/LosslessUtils.cs | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index f9b97c6c4..9a6d974bd 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -42,8 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static readonly Vector128 TransformColorAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + private static readonly Vector256 TransformColorAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + private static readonly Vector128 TransformColorRedBlueMask = Vector128.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + private static readonly Vector256 TransformColorRedBlueMask256 = Vector256.Create(255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0); + private static readonly byte TransformColorShuffleMask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); private static readonly Vector128 TransformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); @@ -408,7 +412,37 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static void TransformColor(Vp8LMultipliers m, Span data, int numPixels) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) + if (Avx2.IsSupported && numPixels >= 8) + { + Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); + fixed (uint* src = data) + { + int idx; + for (idx = 0; idx + 8 <= numPixels; idx += 8) + { + uint* pos = src + idx; + Vector256 input = Avx.LoadVector256(pos); + Vector256 a = Avx2.And(input.AsByte(), TransformColorAlphaGreenMask256); + Vector256 b = Avx2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask); + Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask); + Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector256 e = Avx2.ShiftLeftLogical(input.AsInt16(), 8); + Vector256 f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); + Vector256 g = Avx2.ShiftRightLogical(f.AsInt32(), 16); + Vector256 h = Avx2.Add(g.AsByte(), d.AsByte()); + Vector256 i = Avx2.And(h, TransformColorRedBlueMask256); + Vector256 output = Avx2.Subtract(input.AsByte(), i); + Avx.Store((byte*)pos, output); + } + + if (idx != numPixels) + { + TransformColorNoneVectorized(m, data.Slice(idx), numPixels - idx); + } + } + } + else if (Sse2.IsSupported) { Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); @@ -1288,6 +1322,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless #if SUPPORTS_RUNTIME_INTRINSICS [MethodImpl(InliningOptions.ShortMethod)] private static Vector128 MkCst16(int hi, int lo) => Vector128.Create((hi << 16) | (lo & 0xffff)); + + [MethodImpl(InliningOptions.ShortMethod)] + private static Vector256 MkCst32(int hi, int lo) => Vector256.Create((hi << 16) | (lo & 0xffff)); #endif private static uint Select(uint a, uint b, uint c, Span scratch) From e67ad60e8d2a42d246d785f0ebe91d92b2183aff Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Nov 2021 15:13:56 +0100 Subject: [PATCH 125/511] Add AVX version of TransformColorInverse --- .../Formats/Webp/Lossless/LosslessUtils.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 9a6d974bd..94ad343c8 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -52,6 +52,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static readonly Vector128 TransformColorInverseAlphaGreenMask = Vector128.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + private static readonly Vector256 TransformColorInverseAlphaGreenMask256 = Vector256.Create(0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255); + private static readonly byte TransformColorInverseShuffleMask = SimdUtils.Shuffle.MmShuffle(2, 2, 0, 0); #endif @@ -505,7 +507,38 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static void TransformColorInverse(Vp8LMultipliers m, Span pixelData) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) + if (Avx2.IsSupported && pixelData.Length >= 8) + { + Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); + Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); + fixed (uint* src = pixelData) + { + int idx; + for (idx = 0; idx + 8 <= pixelData.Length; idx += 8) + { + uint* pos = src + idx; + Vector256 input = Avx.LoadVector256(pos); + Vector256 a = Avx2.And(input.AsByte(), TransformColorInverseAlphaGreenMask256); + Vector256 b = Avx2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask); + Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask); + Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector256 e = Avx2.Add(input.AsByte(), d.AsByte()); + Vector256 f = Avx2.ShiftLeftLogical(e.AsInt16(), 8); + Vector256 g = Avx2.MultiplyHigh(f, multsb2.AsInt16()); + Vector256 h = Avx2.ShiftRightLogical(g.AsInt32(), 8); + Vector256 i = Avx2.Add(h.AsByte(), f.AsByte()); + Vector256 j = Avx2.ShiftRightLogical(i.AsInt16(), 8); + Vector256 output = Avx2.Or(j.AsByte(), a); + Avx.Store((byte*)pos, output); + } + + if (idx != pixelData.Length) + { + TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); + } + } + } + else if (Sse2.IsSupported) { Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); From 8e5645912cd0b711e865fa8c47ffdbe5be4f83de Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Nov 2021 15:19:07 +0100 Subject: [PATCH 126/511] Add AVX tests --- tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index c70f332ef..97567ba21 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -257,11 +257,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Fact] public void TransformColor_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableSSE2); + [Fact] + public void TransformColor_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.DisableAVX2); + [Fact] public void TransformColorInverse_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.AllowAll); [Fact] public void TransformColorInverse_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableSSE2); + + [Fact] + public void TransformColorInverse_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorInverseTest, HwIntrinsics.DisableAVX2); #endif } } From b15a021fac71d9643855e4c52e19d955bfd54daa Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Nov 2021 16:14:51 +0100 Subject: [PATCH 127/511] Avoid pinning --- .../Formats/Webp/Lossless/LosslessUtils.cs | 341 ++++++++---------- 1 file changed, 156 insertions(+), 185 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 94ad343c8..c202ad4a8 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -128,66 +128,57 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (Avx2.IsSupported) { int numPixels = pixelData.Length; - fixed (uint* p = pixelData) + int i; + for (i = 0; i + 8 <= numPixels; i += 8) { - int i; - for (i = 0; i + 8 <= numPixels; i += 8) - { - uint* idx = p + i; - Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); - Vector256 in0g0g = Avx2.Shuffle(input, AddGreenToBlueAndRedMaskAvx2); - Vector256 output = Avx2.Add(input, in0g0g); - Avx.Store((byte*)idx, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector256 input = Unsafe.As>(ref pos).AsByte(); + Vector256 in0g0g = Avx2.Shuffle(input, AddGreenToBlueAndRedMaskAvx2); + Vector256 output = Avx2.Add(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (i != numPixels) - { - AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); - } + if (i != numPixels) + { + AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); } } else if (Ssse3.IsSupported) { int numPixels = pixelData.Length; - fixed (uint* p = pixelData) + int i; + for (i = 0; i + 4 <= numPixels; i += 4) { - int i; - for (i = 0; i + 4 <= numPixels; i += 4) - { - uint* idx = p + i; - Vector128 input = Sse2.LoadVector128((ushort*)idx).AsByte(); - Vector128 in0g0g = Ssse3.Shuffle(input, AddGreenToBlueAndRedMaskSsse3); - Vector128 output = Sse2.Add(input, in0g0g); - Sse2.Store((byte*)idx, output.AsByte()); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 in0g0g = Ssse3.Shuffle(input, AddGreenToBlueAndRedMaskSsse3); + Vector128 output = Sse2.Add(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (i != numPixels) - { - AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); - } + if (i != numPixels) + { + AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); } } else if (Sse2.IsSupported) { int numPixels = pixelData.Length; - fixed (uint* p = pixelData) + int i; + for (i = 0; i + 4 <= numPixels; i += 4) { - int i; - for (i = 0; i + 4 <= numPixels; i += 4) - { - uint* idx = p + i; - Vector128 input = Sse2.LoadVector128((ushort*)idx); - Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g - Vector128 b = Sse2.ShuffleLow(a, AddGreenToBlueAndRedShuffleMask); - Vector128 c = Sse2.ShuffleHigh(b, AddGreenToBlueAndRedShuffleMask); // 0g0g - Vector128 output = Sse2.Add(input.AsByte(), c.AsByte()); - Sse2.Store((byte*)idx, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g + Vector128 b = Sse2.ShuffleLow(a, AddGreenToBlueAndRedShuffleMask); + Vector128 c = Sse2.ShuffleHigh(b, AddGreenToBlueAndRedShuffleMask); // 0g0g + Vector128 output = Sse2.Add(input.AsByte(), c.AsByte()); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (i != numPixels) - { - AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); - } + if (i != numPixels) + { + AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); } } else @@ -217,66 +208,57 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (Avx2.IsSupported) { int numPixels = pixelData.Length; - fixed (uint* p = pixelData) + int i; + for (i = 0; i + 8 <= numPixels; i += 8) { - int i; - for (i = 0; i + 8 <= numPixels; i += 8) - { - uint* idx = p + i; - Vector256 input = Avx.LoadVector256((ushort*)idx).AsByte(); - Vector256 in0g0g = Avx2.Shuffle(input, SubtractGreenFromBlueAndRedMaskAvx2); - Vector256 output = Avx2.Subtract(input, in0g0g); - Avx.Store((byte*)idx, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector256 input = Unsafe.As>(ref pos).AsByte(); + Vector256 in0g0g = Avx2.Shuffle(input, SubtractGreenFromBlueAndRedMaskAvx2); + Vector256 output = Avx2.Subtract(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (i != numPixels) - { - SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); - } + if (i != numPixels) + { + SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); } } else if (Ssse3.IsSupported) { int numPixels = pixelData.Length; - fixed (uint* p = pixelData) + int i; + for (i = 0; i + 4 <= numPixels; i += 4) { - int i; - for (i = 0; i + 4 <= numPixels; i += 4) - { - uint* idx = p + i; - Vector128 input = Sse2.LoadVector128((ushort*)idx).AsByte(); - Vector128 in0g0g = Ssse3.Shuffle(input, SubtractGreenFromBlueAndRedMaskSsse3); - Vector128 output = Sse2.Subtract(input, in0g0g); - Sse2.Store((byte*)idx, output.AsByte()); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 in0g0g = Ssse3.Shuffle(input, SubtractGreenFromBlueAndRedMaskSsse3); + Vector128 output = Sse2.Subtract(input, in0g0g); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (i != numPixels) - { - SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); - } + if (i != numPixels) + { + SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); } } else if (Sse2.IsSupported) { int numPixels = pixelData.Length; - fixed (uint* p = pixelData) + int i; + for (i = 0; i + 4 <= numPixels; i += 4) { - int i; - for (i = 0; i + 4 <= numPixels; i += 4) - { - uint* idx = p + i; - Vector128 input = Sse2.LoadVector128((ushort*)idx); - Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g - Vector128 b = Sse2.ShuffleLow(a, SubtractGreenFromBlueAndRedShuffleMask); - Vector128 c = Sse2.ShuffleHigh(b, SubtractGreenFromBlueAndRedShuffleMask); // 0g0g - Vector128 output = Sse2.Subtract(input.AsByte(), c.AsByte()); - Sse2.Store((byte*)idx, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); + Vector128 input = Unsafe.As>(ref pos).AsByte(); + Vector128 a = Sse2.ShiftRightLogical(input.AsUInt16(), 8); // 0 a 0 g + Vector128 b = Sse2.ShuffleLow(a, SubtractGreenFromBlueAndRedShuffleMask); + Vector128 c = Sse2.ShuffleHigh(b, SubtractGreenFromBlueAndRedShuffleMask); // 0g0g + Vector128 output = Sse2.Subtract(input.AsByte(), c.AsByte()); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (i != numPixels) - { - SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); - } + if (i != numPixels) + { + SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); } } else @@ -409,75 +391,70 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Color transform keeps the green (G) value as it is, transforms red (R) based on green and transforms blue (B) based on green and then based on red. /// /// The Vp8LMultipliers. - /// The pixel data to transform. + /// The pixel data to transform. /// The number of pixels to process. - public static void TransformColor(Vp8LMultipliers m, Span data, int numPixels) + public static void TransformColor(Vp8LMultipliers m, Span pixelData, int numPixels) { #if SUPPORTS_RUNTIME_INTRINSICS if (Avx2.IsSupported && numPixels >= 8) { Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); - fixed (uint* src = data) + + int idx; + for (idx = 0; idx + 8 <= numPixels; idx += 8) { - int idx; - for (idx = 0; idx + 8 <= numPixels; idx += 8) - { - uint* pos = src + idx; - Vector256 input = Avx.LoadVector256(pos); - Vector256 a = Avx2.And(input.AsByte(), TransformColorAlphaGreenMask256); - Vector256 b = Avx2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask); - Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask); - Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector256 e = Avx2.ShiftLeftLogical(input.AsInt16(), 8); - Vector256 f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); - Vector256 g = Avx2.ShiftRightLogical(f.AsInt32(), 16); - Vector256 h = Avx2.Add(g.AsByte(), d.AsByte()); - Vector256 i = Avx2.And(h, TransformColorRedBlueMask256); - Vector256 output = Avx2.Subtract(input.AsByte(), i); - Avx.Store((byte*)pos, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector256 input = Unsafe.As>(ref pos); + Vector256 a = Avx2.And(input.AsByte(), TransformColorAlphaGreenMask256); + Vector256 b = Avx2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask); + Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask); + Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector256 e = Avx2.ShiftLeftLogical(input.AsInt16(), 8); + Vector256 f = Avx2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); + Vector256 g = Avx2.ShiftRightLogical(f.AsInt32(), 16); + Vector256 h = Avx2.Add(g.AsByte(), d.AsByte()); + Vector256 i = Avx2.And(h, TransformColorRedBlueMask256); + Vector256 output = Avx2.Subtract(input.AsByte(), i); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (idx != numPixels) - { - TransformColorNoneVectorized(m, data.Slice(idx), numPixels - idx); - } + if (idx != numPixels) + { + TransformColorNoneVectorized(m, pixelData.Slice(idx), numPixels - idx); } } else if (Sse2.IsSupported) { Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - fixed (uint* src = data) + int idx; + for (idx = 0; idx + 4 <= numPixels; idx += 4) { - int idx; - for (idx = 0; idx + 4 <= numPixels; idx += 4) - { - uint* pos = src + idx; - Vector128 input = Sse2.LoadVector128(pos); - Vector128 a = Sse2.And(input.AsByte(), TransformColorAlphaGreenMask); - Vector128 b = Sse2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask); - Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask); - Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector128 e = Sse2.ShiftLeftLogical(input.AsInt16(), 8); - Vector128 f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); - Vector128 g = Sse2.ShiftRightLogical(f.AsInt32(), 16); - Vector128 h = Sse2.Add(g.AsByte(), d.AsByte()); - Vector128 i = Sse2.And(h, TransformColorRedBlueMask); - Vector128 output = Sse2.Subtract(input.AsByte(), i); - Sse2.Store((byte*)pos, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector128 input = Unsafe.As>(ref pos); + Vector128 a = Sse2.And(input.AsByte(), TransformColorAlphaGreenMask); + Vector128 b = Sse2.ShuffleLow(a.AsInt16(), TransformColorShuffleMask); + Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorShuffleMask); + Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector128 e = Sse2.ShiftLeftLogical(input.AsInt16(), 8); + Vector128 f = Sse2.MultiplyHigh(e.AsInt16(), multsb2.AsInt16()); + Vector128 g = Sse2.ShiftRightLogical(f.AsInt32(), 16); + Vector128 h = Sse2.Add(g.AsByte(), d.AsByte()); + Vector128 i = Sse2.And(h, TransformColorRedBlueMask); + Vector128 output = Sse2.Subtract(input.AsByte(), i); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (idx != numPixels) - { - TransformColorNoneVectorized(m, data.Slice(idx), numPixels - idx); - } + if (idx != numPixels) + { + TransformColorNoneVectorized(m, pixelData.Slice(idx), numPixels - idx); } } else #endif { - TransformColorNoneVectorized(m, data, numPixels); + TransformColorNoneVectorized(m, pixelData, numPixels); } } @@ -511,62 +488,57 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); - fixed (uint* src = pixelData) + int idx; + for (idx = 0; idx + 8 <= pixelData.Length; idx += 8) { - int idx; - for (idx = 0; idx + 8 <= pixelData.Length; idx += 8) - { - uint* pos = src + idx; - Vector256 input = Avx.LoadVector256(pos); - Vector256 a = Avx2.And(input.AsByte(), TransformColorInverseAlphaGreenMask256); - Vector256 b = Avx2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask); - Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask); - Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector256 e = Avx2.Add(input.AsByte(), d.AsByte()); - Vector256 f = Avx2.ShiftLeftLogical(e.AsInt16(), 8); - Vector256 g = Avx2.MultiplyHigh(f, multsb2.AsInt16()); - Vector256 h = Avx2.ShiftRightLogical(g.AsInt32(), 8); - Vector256 i = Avx2.Add(h.AsByte(), f.AsByte()); - Vector256 j = Avx2.ShiftRightLogical(i.AsInt16(), 8); - Vector256 output = Avx2.Or(j.AsByte(), a); - Avx.Store((byte*)pos, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector256 input = Unsafe.As>(ref pos); + Vector256 a = Avx2.And(input.AsByte(), TransformColorInverseAlphaGreenMask256); + Vector256 b = Avx2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask); + Vector256 c = Avx2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask); + Vector256 d = Avx2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector256 e = Avx2.Add(input.AsByte(), d.AsByte()); + Vector256 f = Avx2.ShiftLeftLogical(e.AsInt16(), 8); + Vector256 g = Avx2.MultiplyHigh(f, multsb2.AsInt16()); + Vector256 h = Avx2.ShiftRightLogical(g.AsInt32(), 8); + Vector256 i = Avx2.Add(h.AsByte(), f.AsByte()); + Vector256 j = Avx2.ShiftRightLogical(i.AsInt16(), 8); + Vector256 output = Avx2.Or(j.AsByte(), a); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (idx != pixelData.Length) - { - TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); - } + if (idx != pixelData.Length) + { + TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); } } else if (Sse2.IsSupported) { Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - fixed (uint* src = pixelData) + + int idx; + for (idx = 0; idx + 4 <= pixelData.Length; idx += 4) { - int idx; - for (idx = 0; idx + 4 <= pixelData.Length; idx += 4) - { - uint* pos = src + idx; - Vector128 input = Sse2.LoadVector128(pos); - Vector128 a = Sse2.And(input.AsByte(), TransformColorInverseAlphaGreenMask); - Vector128 b = Sse2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask); - Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask); - Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); - Vector128 e = Sse2.Add(input.AsByte(), d.AsByte()); - Vector128 f = Sse2.ShiftLeftLogical(e.AsInt16(), 8); - Vector128 g = Sse2.MultiplyHigh(f, multsb2.AsInt16()); - Vector128 h = Sse2.ShiftRightLogical(g.AsInt32(), 8); - Vector128 i = Sse2.Add(h.AsByte(), f.AsByte()); - Vector128 j = Sse2.ShiftRightLogical(i.AsInt16(), 8); - Vector128 output = Sse2.Or(j.AsByte(), a); - Sse2.Store((byte*)pos, output); - } + ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); + Vector128 input = Unsafe.As>(ref pos); + Vector128 a = Sse2.And(input.AsByte(), TransformColorInverseAlphaGreenMask); + Vector128 b = Sse2.ShuffleLow(a.AsInt16(), TransformColorInverseShuffleMask); + Vector128 c = Sse2.ShuffleHigh(b.AsInt16(), TransformColorInverseShuffleMask); + Vector128 d = Sse2.MultiplyHigh(c.AsInt16(), multsrb.AsInt16()); + Vector128 e = Sse2.Add(input.AsByte(), d.AsByte()); + Vector128 f = Sse2.ShiftLeftLogical(e.AsInt16(), 8); + Vector128 g = Sse2.MultiplyHigh(f, multsb2.AsInt16()); + Vector128 h = Sse2.ShiftRightLogical(g.AsInt32(), 8); + Vector128 i = Sse2.Add(h.AsByte(), f.AsByte()); + Vector128 j = Sse2.ShiftRightLogical(i.AsInt16(), 8); + Vector128 output = Sse2.Or(j.AsByte(), a); + Unsafe.As>(ref pos) = output.AsUInt32(); + } - if (idx != pixelData.Length) - { - TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); - } + if (idx != pixelData.Length) + { + TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); } } else @@ -885,15 +857,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int correction = (int)((23 * (origV & (y - 1))) >> 4); return (vF * (WebpLookupTables.Log2Table[v] + logCnt)) + correction; } - else - { - return (float)(Log2Reciprocal * v * Math.Log(v)); - } + + return (float)(Log2Reciprocal * v * Math.Log(v)); } private static float FastLog2Slow(uint v) { Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + if (v < ApproxLogWithCorrectionMax) { int logCnt = 0; From 7959d0bd8b6742e24451ca58f1f6febc7b445476 Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Mon, 15 Nov 2021 19:21:33 +0100 Subject: [PATCH 128/511] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- .../Formats/Webp/Lossless/ColorSpaceTransformUtils.cs | 8 ++++---- src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs index 4a8488f1b..87b9afa54 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) + for (int x = 0; x <= tileWidth - span; x += span) { int input0Idx = x; int input1Idx = x + (span / 2); @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) + for (int x = 0; x <= tileWidth - span; x += span) { int input0Idx = x; int input1Idx = x + (span / 2); @@ -170,7 +170,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) + for (int x = 0; x <= tileWidth - span; x += span) { int input0Idx = x; int input1Idx = x + (span / 2); @@ -211,7 +211,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x + span <= tileWidth; x += span) + for (int x = 0; x <= tileWidth - span; x += span) { int input0Idx = x; int input1Idx = x + (span / 2); diff --git a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs index 1f7b284e9..a1e04c66a 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PredictorEncoder.cs @@ -561,7 +561,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return (byte)(lower + (quantization >> 1)); } - return (byte)(upper & 0xff); + return (byte)upper; } /// From c491cbba36907734da66fd49333571d739ca0bc1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 15 Nov 2021 19:55:45 +0100 Subject: [PATCH 129/511] Use nint for inner loop x variable --- .../Webp/Lossless/ColorSpaceTransformUtils.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs index 87b9afa54..71f3c5ca9 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/ColorSpaceTransformUtils.cs @@ -56,10 +56,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x <= tileWidth - span; x += span) + for (nint x = 0; x <= tileWidth - span; x += span) { - int input0Idx = x; - int input1Idx = x + (span / 2); + nint input0Idx = x; + nint input1Idx = x + (span / 2); Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); Vector256 r0 = Avx2.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask256); @@ -101,10 +101,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x <= tileWidth - span; x += span) + for (nint x = 0; x <= tileWidth - span; x += span) { - int input0Idx = x; - int input1Idx = x + (span / 2); + nint input0Idx = x; + nint input1Idx = x + (span / 2); Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); Vector128 r0 = Ssse3.Shuffle(input0, CollectColorBlueTransformsShuffleLowMask); @@ -170,10 +170,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x <= tileWidth - span; x += span) + for (nint x = 0; x <= tileWidth - span; x += span) { - int input0Idx = x; - int input1Idx = x + (span / 2); + nint input0Idx = x; + nint input1Idx = x + (span / 2); Vector256 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); Vector256 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); Vector256 g0 = Avx2.And(input0, CollectColorRedTransformsGreenMask256); // 0 0 | g 0 @@ -211,10 +211,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Span srcSpan = bgra.Slice(y * stride); ref uint inputRef = ref MemoryMarshal.GetReference(srcSpan); - for (int x = 0; x <= tileWidth - span; x += span) + for (nint x = 0; x <= tileWidth - span; x += span) { - int input0Idx = x; - int input1Idx = x + (span / 2); + nint input0Idx = x; + nint input1Idx = x + (span / 2); Vector128 input0 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input0Idx)).AsByte(); Vector128 input1 = Unsafe.As>(ref Unsafe.Add(ref inputRef, input1Idx)).AsByte(); Vector128 g0 = Sse2.And(input0, CollectColorRedTransformsGreenMask); // 0 0 | g 0 From ff77361e7c8277c5eddd71614dcbd808e22360cf Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Mon, 15 Nov 2021 20:00:25 +0100 Subject: [PATCH 130/511] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- .../Formats/Webp/Lossless/LosslessUtils.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index c202ad4a8..5903ba9a2 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int numPixels = pixelData.Length; int i; - for (i = 0; i + 4 <= numPixels; i += 4) + for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); Vector128 input = Unsafe.As>(ref pos).AsByte(); @@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int numPixels = pixelData.Length; int i; - for (i = 0; i + 4 <= numPixels; i += 4) + for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); Vector128 input = Unsafe.As>(ref pos).AsByte(); @@ -209,7 +209,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int numPixels = pixelData.Length; int i; - for (i = 0; i + 8 <= numPixels; i += 8) + for (i = 0; i <= numPixels - 8; i += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); Vector256 input = Unsafe.As>(ref pos).AsByte(); @@ -227,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int numPixels = pixelData.Length; int i; - for (i = 0; i + 4 <= numPixels; i += 4) + for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); Vector128 input = Unsafe.As>(ref pos).AsByte(); @@ -245,7 +245,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int numPixels = pixelData.Length; int i; - for (i = 0; i + 4 <= numPixels; i += 4) + for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); Vector128 input = Unsafe.As>(ref pos).AsByte(); @@ -402,7 +402,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); int idx; - for (idx = 0; idx + 8 <= numPixels; idx += 8) + for (idx = 0; idx <= numPixels - 8; idx += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); Vector256 input = Unsafe.As>(ref pos); @@ -429,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); int idx; - for (idx = 0; idx + 4 <= numPixels; idx += 4) + for (idx = 0; idx <= numPixels - 4; idx += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); Vector128 input = Unsafe.As>(ref pos); @@ -489,7 +489,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); int idx; - for (idx = 0; idx + 8 <= pixelData.Length; idx += 8) + for (idx = 0; idx <= pixelData.Length - 8; idx += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); Vector256 input = Unsafe.As>(ref pos); @@ -518,7 +518,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); int idx; - for (idx = 0; idx + 4 <= pixelData.Length; idx += 4) + for (idx = 0; idx <= pixelData.Length - 4; idx += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); Vector128 input = Unsafe.As>(ref pos); From b53aab44b36a1d9d6c90457c724d3e53d39d90ba Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Mon, 15 Nov 2021 20:03:57 +0100 Subject: [PATCH 131/511] Change loop condition to i <= numPixels - 8 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/Lossless/LosslessUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 5903ba9a2..ca021ba9d 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -129,7 +129,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int numPixels = pixelData.Length; int i; - for (i = 0; i + 8 <= numPixels; i += 8) + for (i = 0; i <= numPixels - 8; i += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); Vector256 input = Unsafe.As>(ref pos).AsByte(); From 00d20b8ee55b0eb01297b36cbb9dee1f697df27c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 16 Nov 2021 22:06:47 +1100 Subject: [PATCH 132/511] Use nint and rename scalar fallback --- .../Formats/Webp/Lossless/LosslessUtils.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index ca021ba9d..84b01846b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (Avx2.IsSupported) { int numPixels = pixelData.Length; - int i; + nint i; for (i = 0; i <= numPixels - 8; i += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); @@ -140,13 +140,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (i != numPixels) { - AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); + AddGreenToBlueAndRedScalar(pixelData.Slice((int)i)); } } else if (Ssse3.IsSupported) { int numPixels = pixelData.Length; - int i; + nint i; for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); @@ -158,13 +158,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (i != numPixels) { - AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); + AddGreenToBlueAndRedScalar(pixelData.Slice((int)i)); } } else if (Sse2.IsSupported) { int numPixels = pixelData.Length; - int i; + nint i; for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); @@ -178,17 +178,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (i != numPixels) { - AddGreenToBlueAndRedNoneVectorized(pixelData.Slice(i)); + AddGreenToBlueAndRedScalar(pixelData.Slice((int)i)); } } else #endif { - AddGreenToBlueAndRedNoneVectorized(pixelData); + AddGreenToBlueAndRedScalar(pixelData); } } - private static void AddGreenToBlueAndRedNoneVectorized(Span pixelData) + private static void AddGreenToBlueAndRedScalar(Span pixelData) { int numPixels = pixelData.Length; for (int i = 0; i < numPixels; i++) @@ -208,7 +208,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (Avx2.IsSupported) { int numPixels = pixelData.Length; - int i; + nint i; for (i = 0; i <= numPixels - 8; i += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); @@ -220,13 +220,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (i != numPixels) { - SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); + SubtractGreenFromBlueAndRedScalar(pixelData.Slice((int)i)); } } else if (Ssse3.IsSupported) { int numPixels = pixelData.Length; - int i; + nint i; for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); @@ -238,13 +238,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (i != numPixels) { - SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); + SubtractGreenFromBlueAndRedScalar(pixelData.Slice((int)i)); } } else if (Sse2.IsSupported) { int numPixels = pixelData.Length; - int i; + nint i; for (i = 0; i <= numPixels - 4; i += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), i); @@ -258,17 +258,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (i != numPixels) { - SubtractGreenFromBlueAndRedNoneVectorized(pixelData.Slice(i)); + SubtractGreenFromBlueAndRedScalar(pixelData.Slice((int)i)); } } else #endif { - SubtractGreenFromBlueAndRedNoneVectorized(pixelData); + SubtractGreenFromBlueAndRedScalar(pixelData); } } - private static void SubtractGreenFromBlueAndRedNoneVectorized(Span pixelData) + private static void SubtractGreenFromBlueAndRedScalar(Span pixelData) { int numPixels = pixelData.Length; for (int i = 0; i < numPixels; i++) @@ -401,7 +401,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); - int idx; + nint idx; for (idx = 0; idx <= numPixels - 8; idx += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); @@ -421,14 +421,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (idx != numPixels) { - TransformColorNoneVectorized(m, pixelData.Slice(idx), numPixels - idx); + TransformColorScalar(m, pixelData.Slice((int)idx), numPixels - (int)idx); } } else if (Sse2.IsSupported) { Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - int idx; + nint idx; for (idx = 0; idx <= numPixels - 4; idx += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); @@ -448,17 +448,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (idx != numPixels) { - TransformColorNoneVectorized(m, pixelData.Slice(idx), numPixels - idx); + TransformColorScalar(m, pixelData.Slice((int)idx), numPixels - (int)idx); } } else #endif { - TransformColorNoneVectorized(m, pixelData, numPixels); + TransformColorScalar(m, pixelData, numPixels); } } - private static void TransformColorNoneVectorized(Vp8LMultipliers m, Span data, int numPixels) + private static void TransformColorScalar(Vp8LMultipliers m, Span data, int numPixels) { for (int i = 0; i < numPixels; i++) { @@ -488,7 +488,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { Vector256 multsrb = MkCst32(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector256 multsb2 = MkCst32(Cst5b(m.RedToBlue), 0); - int idx; + nint idx; for (idx = 0; idx <= pixelData.Length - 8; idx += 8) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); @@ -509,7 +509,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (idx != pixelData.Length) { - TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); + TransformColorInverseScalar(m, pixelData.Slice((int)idx)); } } else if (Sse2.IsSupported) @@ -517,7 +517,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector128 multsrb = MkCst16(Cst5b(m.GreenToRed), Cst5b(m.GreenToBlue)); Vector128 multsb2 = MkCst16(Cst5b(m.RedToBlue), 0); - int idx; + nint idx; for (idx = 0; idx <= pixelData.Length - 4; idx += 4) { ref uint pos = ref Unsafe.Add(ref MemoryMarshal.GetReference(pixelData), idx); @@ -538,17 +538,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (idx != pixelData.Length) { - TransformColorInverseNoneVectorized(m, pixelData.Slice(idx)); + TransformColorInverseScalar(m, pixelData.Slice((int)idx)); } } else #endif { - TransformColorInverseNoneVectorized(m, pixelData); + TransformColorInverseScalar(m, pixelData); } } - private static void TransformColorInverseNoneVectorized(Vp8LMultipliers m, Span pixelData) + private static void TransformColorInverseScalar(Vp8LMultipliers m, Span pixelData) { for (int i = 0; i < pixelData.Length; i++) { From 31e6230801a959060900f7fb04e556a8d9c85a68 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Tue, 16 Nov 2021 18:57:35 +0100 Subject: [PATCH 133/511] Delete benchmark.sh --- tests/ImageSharp.Benchmarks/benchmark.sh | 7 ------- 1 file changed, 7 deletions(-) delete mode 100755 tests/ImageSharp.Benchmarks/benchmark.sh diff --git a/tests/ImageSharp.Benchmarks/benchmark.sh b/tests/ImageSharp.Benchmarks/benchmark.sh deleted file mode 100755 index f51a9833a..000000000 --- a/tests/ImageSharp.Benchmarks/benchmark.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -# Build in release mode -dotnet build -c Release -f netcoreapp2.0 - -# Run benchmarks -dotnet bin/Release/netcoreapp2.0/ImageSharp.Benchmarks.dll \ No newline at end of file From 7191acaf34f535bc883b99114eb811708ddb5064 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 17 Nov 2021 10:58:52 +0100 Subject: [PATCH 134/511] Move UpSample to YuvConversion class --- .../Formats/Webp/Lossy/WebpLossyDecoder.cs | 62 ++----------------- .../Formats/Webp/Lossy/YuvConversion.cs | 54 ++++++++++++++++ 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index 2f78842c6..b27ef88fb 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -696,12 +696,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (y == 0) { // First line is special cased. We mirror the u/v samples at boundary. - this.UpSample(curY, null, curU, curV, curU, curV, dst, null, mbw); + YuvConversion.UpSample(curY, null, curU, curV, curU, curV, dst, null, mbw); } else { // We can finish the left-over line from previous call. - this.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf.Slice(dstStartIdx - bufferStride), dst, mbw); + YuvConversion.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf.Slice(dstStartIdx - bufferStride), dst, mbw); numLinesOut++; } @@ -714,7 +714,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy topV = curV; curU = curU.Slice(io.UvStride); curV = curV.Slice(io.UvStride); - this.UpSample(curY.Slice(io.YStride), curY.Slice(ioStride2), topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(bufferStride2), mbw); + YuvConversion.UpSample(curY.Slice(io.YStride), curY.Slice(ioStride2), topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(bufferStride2), mbw); curY = curY.Slice(ioStride2); dst = dst.Slice(bufferStride2); } @@ -736,67 +736,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Process the very last row of even-sized picture. if ((yEnd & 1) == 0) { - this.UpSample(curY, null, curU, curV, curU, curV, dst.Slice(bufferStride), null, mbw); + YuvConversion.UpSample(curY, null, curU, curV, curU, curV, dst.Slice(bufferStride), null, mbw); } } return numLinesOut; } - private void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) - { - int xStep = 3; - int lastPixelPair = (len - 1) >> 1; - uint tluv = YuvConversion.LoadUv(topU[0], topV[0]); // top-left sample - uint luv = YuvConversion.LoadUv(curU[0], curV[0]); // left-sample - uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - YuvConversion.YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); - - if (bottomY != null) - { - uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - YuvConversion.YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); - } - - for (int x = 1; x <= lastPixelPair; x++) - { - uint tuv = YuvConversion.LoadUv(topU[x], topV[x]); // top sample - uint uv = YuvConversion.LoadUv(curU[x], curV[x]); // sample - - // Precompute invariant values associated with first and second diagonals. - uint avg = tluv + tuv + luv + uv + 0x00080008u; - uint diag12 = (avg + (2 * (tuv + luv))) >> 3; - uint diag03 = (avg + (2 * (tluv + uv))) >> 3; - uv0 = (diag12 + tluv) >> 1; - uint uv1 = (diag03 + tuv) >> 1; - int xMul2 = x * 2; - YuvConversion.YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep)); - YuvConversion.YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep)); - - if (bottomY != null) - { - uv0 = (diag03 + luv) >> 1; - uv1 = (diag12 + uv) >> 1; - YuvConversion.YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((xMul2 - 1) * xStep)); - YuvConversion.YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice((xMul2 + 0) * xStep)); - } - - tluv = tuv; - luv = uv; - } - - if ((len & 1) == 0) - { - uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; - YuvConversion.YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); - if (bottomY != null) - { - uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; - YuvConversion.YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); - } - } - } - private void DoTransform(uint bits, Span src, Span dst, Span scratch) { switch (bits >> 30) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index a9cf876c8..182437e54 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -18,6 +18,60 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int YuvHalf = 1 << (YuvFix - 1); + public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + { + int xStep = 3; + int lastPixelPair = (len - 1) >> 1; + uint tluv = LoadUv(topU[0], topV[0]); // top-left sample + uint luv = LoadUv(curU[0], curV[0]); // left-sample + uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; + YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); + + if (bottomY != null) + { + uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; + YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); + } + + for (int x = 1; x <= lastPixelPair; x++) + { + uint tuv = LoadUv(topU[x], topV[x]); // top sample + uint uv = LoadUv(curU[x], curV[x]); // sample + + // Precompute invariant values associated with first and second diagonals. + uint avg = tluv + tuv + luv + uv + 0x00080008u; + uint diag12 = (avg + (2 * (tuv + luv))) >> 3; + uint diag03 = (avg + (2 * (tluv + uv))) >> 3; + uv0 = (diag12 + tluv) >> 1; + uint uv1 = (diag03 + tuv) >> 1; + int xMul2 = x * 2; + YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep)); + YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep)); + + if (bottomY != null) + { + uv0 = (diag03 + luv) >> 1; + uv1 = (diag12 + uv) >> 1; + YuvToBgr(bottomY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((xMul2 - 1) * xStep)); + YuvToBgr(bottomY[xMul2 + 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), bottomDst.Slice((xMul2 + 0) * xStep)); + } + + tluv = tuv; + luv = uv; + } + + if ((len & 1) == 0) + { + uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; + YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); + if (bottomY != null) + { + uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; + YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); + } + } + } + /// /// Converts the RGB values of the image to YUV. /// From 56890ba07666c74f9ad6b057353a8a776445fed2 Mon Sep 17 00:00:00 2001 From: Turnerj Date: Wed, 17 Nov 2021 23:20:18 +1030 Subject: [PATCH 135/511] Add missing premultiply --- .../Processors/Convolution/Convolution2PassProcessor{TPixel}.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs index 365b2e2df..3f4809c11 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs @@ -396,6 +396,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution PixelOperations.Instance.ToVector4(this.configuration, sourceRow, sourceBuffer); + Numerics.Premultiply(sourceBuffer); + ref Vector4 sourceBase = ref MemoryMarshal.GetReference(sourceBuffer); ref Vector4 sourceEnd = ref Unsafe.Add(ref sourceBase, sourceBuffer.Length); ref Vector4 targetStart = ref targetBase; From 088e92cfcf94ee073e1131660b70fa9077aced7a Mon Sep 17 00:00:00 2001 From: Turnerj Date: Thu, 18 Nov 2021 01:03:32 +1030 Subject: [PATCH 136/511] Add reference image for gaussian blur issue --- .../Convolution/Basic1ParameterConvolutionTests.cs | 9 +++++---- .../Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png | 3 +++ .../Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png | 3 +++ .../BoxBlurTest/OnFullImage_Rgba32_blur_3.png | 3 +++ .../BoxBlurTest/OnFullImage_Rgba32_blur_5.png | 3 +++ .../Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png | 3 +++ .../Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png | 3 +++ .../GaussianBlurTest/OnFullImage_Rgba32_blur_3.png | 3 +++ .../GaussianBlurTest/OnFullImage_Rgba32_blur_5.png | 3 +++ .../GaussianSharpenTest/InBox_Rgba32_blur_3.png | 3 +++ .../GaussianSharpenTest/InBox_Rgba32_blur_5.png | 3 +++ .../GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png | 3 +++ .../GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png | 3 +++ 13 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_3.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_5.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_3.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_5.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_3.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_5.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png create mode 100644 tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png diff --git a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs index 7e0676aab..8eed3683e 100644 --- a/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs +++ b/tests/ImageSharp.Tests/Processing/Processors/Convolution/Basic1ParameterConvolutionTests.cs @@ -17,10 +17,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution public static readonly TheoryData Values = new TheoryData { 3, 5 }; public static readonly string[] InputImages = - { - TestImages.Bmp.Car, - TestImages.Png.CalliphoraPartial - }; + { + TestImages.Bmp.Car, + TestImages.Png.CalliphoraPartial, + TestImages.Png.Blur + }; [Theory] [WithFileCollection(nameof(InputImages), nameof(Values), PixelTypes.Rgba32)] diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png new file mode 100644 index 000000000..dc28cf4f7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c6dd45683953e7cecbbaaa339b78db1303f9583b8d0988fe1948c6b1b4ba297a +size 121550 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png new file mode 100644 index 000000000..11d0b6e05 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/InBox_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3867cbbc1d425ceba20dd392de0728ce4de652860491e87434cd33675f56d8e +size 117863 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_3.png new file mode 100644 index 000000000..df6e2f204 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:186c35bc159c7125f59b47866021051ff74368b9021dd09ad3c6386b39be3546 +size 80992 diff --git a/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_5.png new file mode 100644 index 000000000..4bbc0604c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/BoxBlurTest/OnFullImage_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d7413d1d7ac69feb1d1f0a61d0d4a8228d3276337446d2c761ce58b0813cf66 +size 67243 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png new file mode 100644 index 000000000..1e75f1723 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ace61fd7330b5e52b7aa09af937259d200b71fa152bf1ffdc6b891e5b61abfd5 +size 117133 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png new file mode 100644 index 000000000..8a9442449 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/InBox_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc2f26bda2dec8354d8b77887806012f28f54b8a8f7e39e7e4bcb4d872d29042 +size 114247 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_3.png new file mode 100644 index 000000000..eb371ba5f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3aac58316fa795c2683f7cfac34f69ba71501abd78e0d72076cc36c439a8fa7a +size 63680 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_5.png new file mode 100644 index 000000000..52f4f2bda --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianBlurTest/OnFullImage_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7bf28351fa51e0e9b0c2fd4b3fc7a30b0b3a8c1ca2dc9dd62ec5fab56e22c10 +size 50451 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_3.png new file mode 100644 index 000000000..956facf68 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6eecdf3bf90a2dd9430ce8501ab98f7a25f4f06674673fd6b9ca6a44435d303 +size 239962 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_5.png new file mode 100644 index 000000000..98c0096af --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/InBox_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3cc3a46595d648a4551f499e1246ccdb63a80f424487fb7306fd3cfd772f5f1e +size 238816 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png new file mode 100644 index 000000000..936b774f7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59ca62ae017d8f5a19dbd0f61ded29d936c325553eb3e08fe39f2440d4c941eb +size 356290 diff --git a/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png new file mode 100644 index 000000000..4b6642dae --- /dev/null +++ b/tests/Images/External/ReferenceOutput/Convolution/GaussianSharpenTest/OnFullImage_Rgba32_blur_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:427d325ace605fe9a22702dcd8bff20dff888293def6569c4dc635b56c732565 +size 351992 From 065cb1ca459362fe1aaa6bb8e1afad4491c8515f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 17 Nov 2021 20:34:26 +0100 Subject: [PATCH 137/511] 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 59a11bf9011729ad5cf8f30f3ef21adf616bf0b9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 17 Nov 2021 21:45:32 +0100 Subject: [PATCH 138/511] Add SSE41 version of UpSample --- .../Formats/Webp/Lossy/YuvConversion.cs | 332 ++++++++++++++++++ 1 file changed, 332 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 182437e54..0f5c56c74 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -4,6 +4,11 @@ using System; using System.Buffers; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -18,8 +23,66 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private const int YuvHalf = 1 << (YuvFix - 1); +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector128 One = Vector128.Create((byte)1); + + // These constants are 14b fixed-point version of ITU-R BT.601 constants. + // R = (19077 * y + 26149 * v - 14234) >> 6 + // G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 + // B = (19077 * y + 33050 * u - 17685) >> 6 + private static readonly Vector128 K19077 = Vector128.Create((short)19077).AsByte(); + + private static readonly Vector128 K26149 = Vector128.Create((short)26149).AsByte(); + + private static readonly Vector128 K14234 = Vector128.Create((short)14234).AsByte(); + + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + private static readonly Vector128 K33050 = Vector128.Create(26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129, 26, 129); + + private static readonly Vector128 K17685 = Vector128.Create((short)17685).AsByte(); + + private static readonly Vector128 K6419 = Vector128.Create((short)6419).AsByte(); + + private static readonly Vector128 K13320 = Vector128.Create((short)13320).AsByte(); + + private static readonly Vector128 K8708 = Vector128.Create((short)8708).AsByte(); + + private static readonly Vector128 PlanarTo24Shuffle0 = Vector128.Create(0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255, 5); + + private static readonly Vector128 PlanarTo24Shuffle1 = Vector128.Create(255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10, 255); + + private static readonly Vector128 PlanarTo24Shuffle2 = Vector128.Create(255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255, 255); + + private static readonly Vector128 PlanarTo24Shuffle3 = Vector128.Create(255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255, 255); + + private static readonly Vector128 PlanarTo24Shuffle4 = Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10); + + private static readonly Vector128 PlanarTo24Shuffle5 = Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 24, 255, 255, 15, 255); + + private static readonly Vector128 PlanarTo24Shuffle6 = Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255); + + private static readonly Vector128 PlanarTo24Shuffle7 = Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255); + + private static readonly Vector128 PlanarTo24Shuffle8 = Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 24, 255, 255, 15); +#endif + + // UpSample from YUV to RGB. public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported) + { + UpSampleSse41(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len); + } + else +#endif + { + UpSampleScalar(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len); + } + } + + public static void UpSampleScalar(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + { int xStep = 3; int lastPixelPair = (len - 1) >> 1; uint tluv = LoadUv(topU[0], topV[0]); // top-left sample @@ -72,6 +135,106 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } +#if SUPPORTS_RUNTIME_INTRINSICS + // We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows + // u = (9*a + 3*b + 3*c + d + 8) / 16 + // = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 + // = (a + m + 1) / 2 + // where m = (a + 3*b + 3*c + d) / 8 + // = ((a + b + c + d) / 2 + b + c) / 4 + // + // Let's say k = (a + b + c + d) / 4. + // We can compute k as + // k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 + // where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 + // + // Then m can be written as + // m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + public static void UpSampleSse41(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + { + const int xStep = 3; + byte[] uvBuffer = new byte[(14 * 32) + 15]; + Span ru = uvBuffer.AsSpan(15); + Span rv = ru.Slice(32); + + // Treat the first pixel in regular way. + int uDiag = ((topU[0] + curU[0]) >> 1) + 1; + int vDiag = ((topV[0] + curV[0]) >> 1) + 1; + int u0t = (topU[0] + uDiag) >> 1; + int v0t = (topV[0] + vDiag) >> 1; + YuvToBgr(topY[0], u0t, v0t, topDst); + if (bottomY != null) + { + int u0b = (curU[0] + uDiag) >> 1; + int v0b = (curV[0] + vDiag) >> 1; + YuvToBgr(bottomY[0], u0b, v0b, bottomDst); + } + + // For UpSample32Pixels, 17 u/v values must be read-able for each block. + for (int pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) + { + UpSample32Pixels(topU.Slice(uvPos), curU.Slice(uvPos), ru); + UpSample32Pixels(topV.Slice(uvPos), curV.Slice(uvPos), rv); + ConvertToBgrSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); + } + } + + // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. + public static void UpSample32Pixels(Span r1, Span r2, Span output) + { + // Load inputs. + Vector128 a = Unsafe.As>(ref MemoryMarshal.GetReference(r1)); + Vector128 b = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(r1), 1)); + Vector128 c = Unsafe.As>(ref MemoryMarshal.GetReference(r2)); + Vector128 d = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(r2), 1)); + + Vector128 s = Sse2.Average(a, d); // s = (a + d + 1) / 2 + Vector128 t = Sse2.Average(b, c); // t = (b + c + 1) / 2 + Vector128 st = Sse2.Xor(s, t); // st = s^t + + Vector128 ad = Sse2.Xor(a, d); // ad = a^d + Vector128 bc = Sse2.Xor(b, c); // bc = b^c + + Vector128 t1 = Sse2.Or(ad, bc); // (a^d) | (b^c) + Vector128 t2 = Sse2.Or(t1, st); // (a^d) | (b^c) | (s^t) + Vector128 t3 = Sse2.And(t2, One); // (a^d) | (b^c) | (s^t) & 1 + Vector128 t4 = Sse2.Average(s, t); + Vector128 k = Sse2.Subtract(t4, t3); // k = (a + b + c + d) / 4 + + Vector128 diag1 = GetM(k, st, bc, t); + Vector128 diag2 = GetM(k, st, ad, s); + + // Pack the alternate pixels. + PackAndStore(a, b, diag1, diag2, output); // store top. + PackAndStore(c, d, diag2, diag1, output.Slice(2 * 32)); + } + + // Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 + private static Vector128 GetM(Vector128 k, Vector128 st, Vector128 ij, Vector128 input) + { + Vector128 tmp0 = Sse2.Average(k, input); // (k + in + 1) / 2 + Vector128 tmp1 = Sse2.And(ij, st); // (ij) & (s^t) + Vector128 tmp2 = Sse2.Xor(k, input); // (k^in) + Vector128 tmp3 = Sse2.Or(tmp1, tmp2); // ((ij) & (s^t)) | (k^in) + Vector128 tmp4 = Sse2.And(tmp3, One); // & 1 -> lsb_correction + + return Sse2.Subtract(tmp0, tmp4); // (k + in + 1) / 2 - lsb_correction + } + + private static void PackAndStore(Vector128 a, Vector128 b, Vector128 da, Vector128 db, Span output) + { + Vector128 ta = Sse2.Average(a, da); // (9a + 3b + 3c + d + 8) / 16 + Vector128 tb = Sse2.Average(b, db); // (3a + 9b + c + 3d + 8) / 16 + Vector128 t1 = Sse2.UnpackLow(ta, tb); + Vector128 t2 = Sse2.UnpackHigh(ta, tb); + + ref byte output0Ref = ref MemoryMarshal.GetReference(output); + ref byte output1Ref = ref Unsafe.Add(ref output0Ref, 16); + Unsafe.As>(ref output0Ref) = t1; + Unsafe.As>(ref output1Ref) = t2; + } +#endif + /// /// Converts the RGB values of the image to YUV. /// @@ -366,6 +529,175 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy bgr[0] = (byte)YuvToB(y, u); } +#if SUPPORTS_RUNTIME_INTRINSICS + + private static void ConvertToBgrSse41(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) + { + YuvToBgrSse41(topY.Slice(curX), ru, rv, topDst.Slice(curX * step)); + + if (bottomY != null) + { + YuvToBgrSse41(bottomY.Slice(curX), ru.Slice(64), rv.Slice(64), bottomDst.Slice(curX * step)); + } + } + + public static void YuvToBgrSse41(Span y, Span u, Span v, Span dst) + { + ConvertYuv444ToRgbSse41(y, u, v, out Vector128 r0, out Vector128 g0, out Vector128 b0); + ConvertYuv444ToRgbSse41(y.Slice(8), u.Slice(8), v.Slice(8), out Vector128 r1, out Vector128 g1, out Vector128 b1); + ConvertYuv444ToRgbSse41(y.Slice(16), u.Slice(16), v.Slice(16), out Vector128 r2, out Vector128 g2, out Vector128 b2); + ConvertYuv444ToRgbSse41(y.Slice(24), u.Slice(24), v.Slice(24), out Vector128 r3, out Vector128 g3, out Vector128 b3); + + // Cast to 8b and store as BBBBGGGGRRRR. + Vector128 bgr0 = Sse2.PackUnsignedSaturate(b0, b1); + Vector128 bgr1 = Sse2.PackUnsignedSaturate(b2, b3); + Vector128 bgr2 = Sse2.PackUnsignedSaturate(g0, g1); + Vector128 bgr3 = Sse2.PackUnsignedSaturate(g2, g3); + Vector128 bgr4 = Sse2.PackUnsignedSaturate(r0, r1); + Vector128 bgr5 = Sse2.PackUnsignedSaturate(r2, r3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24bSse41(bgr0, bgr1, bgr2, bgr3, bgr4, bgr5, dst); + } + + // Pack the planar buffers + // rrrr... rrrr... gggg... gggg... bbbb... bbbb.... + // triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... + private static void PlanarTo24bSse41(Vector128 input0, Vector128 input1, Vector128 input2, Vector128 input3, Vector128 input4, Vector128 input5, Span rgb) + { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + + // Process R. + ChannelMixing( + input0, + input1, + PlanarTo24Shuffle0, + PlanarTo24Shuffle1, + PlanarTo24Shuffle2, + out Vector128 r0, + out Vector128 r1, + out Vector128 r2, + out Vector128 r3, + out Vector128 r4, + out Vector128 r5); + + // Process G. + // Same as before, just shifted to the left by one and including the right padding. + ChannelMixing( + input2, + input3, + PlanarTo24Shuffle3, + PlanarTo24Shuffle4, + PlanarTo24Shuffle5, + out Vector128 g0, + out Vector128 g1, + out Vector128 g2, + out Vector128 g3, + out Vector128 g4, + out Vector128 g5); + + // Process B. + ChannelMixing( + input4, + input5, + PlanarTo24Shuffle6, + PlanarTo24Shuffle7, + PlanarTo24Shuffle8, + out Vector128 b0, + out Vector128 b1, + out Vector128 b2, + out Vector128 b3, + out Vector128 b4, + out Vector128 b5); + + // OR the different channels. + Vector128 rg0 = Sse2.Or(r0, g0); + Vector128 rg1 = Sse2.Or(r1, g1); + Vector128 rg2 = Sse2.Or(r2, g2); + Vector128 rg3 = Sse2.Or(r3, g3); + Vector128 rg4 = Sse2.Or(r4, g4); + Vector128 rg5 = Sse2.Or(r5, g5); + + ref byte outputRef = ref MemoryMarshal.GetReference(rgb); + Unsafe.As>(ref outputRef) = Sse2.Or(rg0, b0); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 16)) = Sse2.Or(rg1, b1); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 32)) = Sse2.Or(rg2, b2); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 48)) = Sse2.Or(rg3, b3); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 64)) = Sse2.Or(rg4, b4); + Unsafe.As>(ref Unsafe.Add(ref outputRef, 80)) = Sse2.Or(rg5, b5); + } + + // Shuffles the input buffer as A0 0 0 A1 0 0 A2 + private static void ChannelMixing( + Vector128 input0, + Vector128 input1, + Vector128 shuffle0, + Vector128 shuffle1, + Vector128 shuffle2, + out Vector128 output0, + out Vector128 output1, + out Vector128 output2, + out Vector128 output3, + out Vector128 output4, + out Vector128 output5) + { + output0 = Ssse3.Shuffle(input0, shuffle0); + output1 = Ssse3.Shuffle(input0, shuffle1); + output2 = Ssse3.Shuffle(input0, shuffle2); + output3 = Ssse3.Shuffle(input1, shuffle0); + output4 = Ssse3.Shuffle(input1, shuffle1); + output5 = Ssse3.Shuffle(input1, shuffle2); + } + + // Convert 32 samples of YUV444 to R/G/B + private static void ConvertYuv444ToRgbSse41(Span y, Span u, Span v, out Vector128 r, out Vector128 g, out Vector128 b) + { + Vector128 y0 = LoadHigh(y); + Vector128 u0 = LoadHigh(u); + Vector128 v0 = LoadHigh(v); + + Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16()); + + Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); + Vector128 r1 = Sse2.Subtract(y1.AsUInt16(), K14234.AsUInt16()); + Vector128 r2 = Sse2.Add(r1, r0); + + Vector128 g0 = Sse2.MultiplyHigh(u0.AsUInt16(), K6419.AsUInt16()); + Vector128 g1 = Sse2.MultiplyHigh(v0.AsUInt16(), K13320.AsUInt16()); + Vector128 g2 = Sse2.Add(y1.AsUInt16(), K8708.AsUInt16()); + Vector128 g3 = Sse2.Add(g0, g1); + Vector128 g4 = Sse2.Subtract(g2, g3); + + Vector128 b0 = Sse2.MultiplyHigh(u0.AsUInt16(), K33050.AsUInt16()); + Vector128 b1 = Sse2.AddSaturate(b0, y1); + Vector128 b2 = Sse2.SubtractSaturate(b1, K17685.AsUInt16()); + + // use logical shift for B2, which can be larger than 32767 + r = Sse2.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815] + g = Sse2.ShiftRightArithmetic(g4.AsInt16(), 6); // range: [-10953, 27710] + b = Sse2.ShiftRightLogical(b2.AsInt16(), 6); // range: [0, 34238] + } + + // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. + private static Vector128 LoadHigh(Span src) + { + Vector64 tmp = Unsafe.As>(ref MemoryMarshal.GetReference(src)); + Vector128 tmp2 = Unsafe.As, Vector128>(ref tmp); + return Sse2.UnpackLow(Vector128.Zero, tmp2); + } +#endif + [MethodImpl(InliningOptions.ShortMethod)] public static int YuvToB(int y, int u) => Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); From 2a03d00c680da4f0f112eea1401421c94dd7e96e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 18 Nov 2021 11:21:25 +0100 Subject: [PATCH 139/511] Upsample last block --- .../Formats/Webp/Lossy/YuvConversion.cs | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 0f5c56c74..1c2208732 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -67,6 +67,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #endif // UpSample from YUV to RGB. + // Given samples laid out in a square as: + // [a b] + // [c d] + // we interpolate u/v as: + // ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 + // ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) { #if SUPPORTS_RUNTIME_INTRINSICS @@ -171,12 +177,33 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // For UpSample32Pixels, 17 u/v values must be read-able for each block. - for (int pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) + int pos; + int uvPos; + for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) { UpSample32Pixels(topU.Slice(uvPos), curU.Slice(uvPos), ru); UpSample32Pixels(topV.Slice(uvPos), curV.Slice(uvPos), rv); ConvertToBgrSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); } + + // Process last block. + if (len > 1) + { + int leftOver = ((len + 1) >> 1) - (pos >> 1); + Span tmpTopDst = ru.Slice(4 * 32); + Span tmpBottomDst = tmpTopDst.Slice(4 * 32); + Span tmpTop = tmpBottomDst.Slice(4 * 32); + Span tmpBottom = (bottomY == null) ? null : tmpTop.Slice(32); + UpSampleLastBlock(topU.Slice(uvPos), curU.Slice(uvPos), leftOver, ru); + UpSampleLastBlock(topV.Slice(uvPos), curV.Slice(uvPos), leftOver, rv); + topY.Slice(pos, len - pos).CopyTo(tmpTop); + ConvertToBgrSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); + tmpTopDst.Slice(0, (len - pos) * xStep).CopyTo(topDst.Slice(pos * xStep)); + if (bottomY != null) + { + tmpBottomDst.Slice(0, (len - pos) * xStep).CopyTo(bottomDst.Slice(pos * xStep)); + } + } } // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. @@ -209,6 +236,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy PackAndStore(c, d, diag2, diag1, output.Slice(2 * 32)); } + private static void UpSampleLastBlock(Span tb, Span bb, int numPixels, Span output) + { + Span r1 = stackalloc byte[17]; + Span r2 = stackalloc byte[17]; + tb.Slice(0, numPixels).CopyTo(r1); + bb.Slice(0, numPixels).CopyTo(r2); + + // Replicate last byte. + int length = 17 - numPixels; + if (length > 0) + { + r1.Slice(numPixels, length).Fill(r1[numPixels - 1]); + r2.Slice(numPixels, length).Fill(r2[numPixels - 1]); + } + + UpSample32Pixels(r1, r2, output); + } + // Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 private static Vector128 GetM(Vector128 k, Vector128 st, Vector128 ij, Vector128 input) { From 3f4388323b3d8ac4efc50606ddb19128cc96b6cf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 18 Nov 2021 13:28:35 +0100 Subject: [PATCH 140/511] Fix shuffle masks --- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 1c2208732..080739db9 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -57,13 +57,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static readonly Vector128 PlanarTo24Shuffle4 = Vector128.Create(5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255, 10); - private static readonly Vector128 PlanarTo24Shuffle5 = Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 24, 255, 255, 15, 255); + private static readonly Vector128 PlanarTo24Shuffle5 = Vector128.Create(255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15, 255); private static readonly Vector128 PlanarTo24Shuffle6 = Vector128.Create(255, 255, 0, 255, 255, 1, 255, 255, 2, 255, 255, 3, 255, 255, 4, 255); private static readonly Vector128 PlanarTo24Shuffle7 = Vector128.Create(255, 5, 255, 255, 6, 255, 255, 7, 255, 255, 8, 255, 255, 9, 255, 255); - private static readonly Vector128 PlanarTo24Shuffle8 = Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 24, 255, 255, 15); + private static readonly Vector128 PlanarTo24Shuffle8 = Vector128.Create(10, 255, 255, 11, 255, 255, 12, 255, 255, 13, 255, 255, 14, 255, 255, 15); #endif // UpSample from YUV to RGB. @@ -183,7 +183,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { UpSample32Pixels(topU.Slice(uvPos), curU.Slice(uvPos), ru); UpSample32Pixels(topV.Slice(uvPos), curV.Slice(uvPos), rv); - ConvertToBgrSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); + ConvertYuvToBgrSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); } // Process last block. @@ -197,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy UpSampleLastBlock(topU.Slice(uvPos), curU.Slice(uvPos), leftOver, ru); UpSampleLastBlock(topV.Slice(uvPos), curV.Slice(uvPos), leftOver, rv); topY.Slice(pos, len - pos).CopyTo(tmpTop); - ConvertToBgrSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); + ConvertYuvToBgrSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); tmpTopDst.Slice(0, (len - pos) * xStep).CopyTo(topDst.Slice(pos * xStep)); if (bottomY != null) { @@ -576,7 +576,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS - private static void ConvertToBgrSse41(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) + private static void ConvertYuvToBgrSse41(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) { YuvToBgrSse41(topY.Slice(curX), ru, rv, topDst.Slice(curX * step)); From ec18321a814739bfd5ab93423c562e17168f6364 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 18 Nov 2021 14:56:21 +0100 Subject: [PATCH 141/511] Fix last block --- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 080739db9..3413f6f18 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -196,7 +196,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Span tmpBottom = (bottomY == null) ? null : tmpTop.Slice(32); UpSampleLastBlock(topU.Slice(uvPos), curU.Slice(uvPos), leftOver, ru); UpSampleLastBlock(topV.Slice(uvPos), curV.Slice(uvPos), leftOver, rv); + topY.Slice(pos, len - pos).CopyTo(tmpTop); + if (bottomY != null) + { + bottomY.Slice(pos, len - pos).CopyTo(tmpBottom); + } + ConvertYuvToBgrSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); tmpTopDst.Slice(0, (len - pos) * xStep).CopyTo(topDst.Slice(pos * xStep)); if (bottomY != null) From 251e802b7e36d4723f78c4820e945ab0f3c381e2 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 18 Nov 2021 21:13:59 +0100 Subject: [PATCH 142/511] 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 c2bf48e9456124bed37f214be98c62074bc053c9 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 10:53:27 +0100 Subject: [PATCH 143/511] remove HistogramEqualizationOptions.Default --- .../Normalization/HistogramEqualizationExtensions.cs | 2 +- .../Processors/Normalization/HistogramEqualizationOptions.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs index a8ac3376a..c1046f82d 100644 --- a/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs +++ b/src/ImageSharp/Processing/Extensions/Normalization/HistogramEqualizationExtensions.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing /// The image this method extends. /// The to allow chaining of operations. public static IImageProcessingContext HistogramEqualization(this IImageProcessingContext source) => - HistogramEqualization(source, HistogramEqualizationOptions.Default); + HistogramEqualization(source, new HistogramEqualizationOptions()); /// /// Equalizes the histogram of an image to increases the contrast. diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs index 602dc0c4b..1b8723e4f 100644 --- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs +++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationOptions.cs @@ -8,11 +8,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization /// public class HistogramEqualizationOptions { - /// - /// Gets the default instance. - /// - public static HistogramEqualizationOptions Default { get; } = new HistogramEqualizationOptions(); - /// /// Gets or sets the histogram equalization method to use. Defaults to global histogram equalization. /// From 83455e7e0af3d07d360ca6201e10e9f60786e584 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 10:53:44 +0100 Subject: [PATCH 144/511] replace tabs with spaces in LossyUtilsTests --- tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index d176a5933..69a24843c 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -38,7 +38,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.WebP int actual = LossyUtils.Vp8_Sse4X4(a, b); Assert.Equal(expected, actual); - } + } private static void RunMean16x4Test() { From 7ee7952a2c4352cfd219bbea5da3eeb75a84827b Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 11:01:38 +0100 Subject: [PATCH 145/511] make ColorDistanceCache non-readonly --- .../Processors/Quantization/EuclideanPixelMap{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index b82ce71bb..cac11b4a8 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : unmanaged, IPixel { private Rgba32[] rgbaPalette; - private readonly ColorDistanceCache cache; + private ColorDistanceCache cache; private readonly Configuration configuration; /// From 61bfb9e60cc2eb75f7fa18561c046b9c4bdf1fe5 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 11:07:56 +0100 Subject: [PATCH 146/511] add comment on ColorDistanceCache member --- .../Processors/Quantization/EuclideanPixelMap{TPixel}.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs index cac11b4a8..f54489348 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs @@ -22,6 +22,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization where TPixel : unmanaged, IPixel { private Rgba32[] rgbaPalette; + + // Do not make this readonly! Struct value would be always copied on non-readonly method calls. private ColorDistanceCache cache; private readonly Configuration configuration; From c223d2eadbc0be25d6a493251a765cd6944719ac Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 19 Nov 2021 22:04:03 +1100 Subject: [PATCH 147/511] Avoid implicit casting --- .../Formats/Webp/Lossy/WebpLossyDecoder.cs | 4 ++-- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index b27ef88fb..4d21333e6 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -696,7 +696,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (y == 0) { // First line is special cased. We mirror the u/v samples at boundary. - YuvConversion.UpSample(curY, null, curU, curV, curU, curV, dst, null, mbw); + YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst, default, mbw); } else { @@ -736,7 +736,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Process the very last row of even-sized picture. if ((yEnd & 1) == 0) { - YuvConversion.UpSample(curY, null, curU, curV, curU, curV, dst.Slice(bufferStride), null, mbw); + YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst.Slice(bufferStride), default, mbw); } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 3413f6f18..342fc330c 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy uint uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; YuvToBgr(topY[0], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst); - if (bottomY != null) + if (bottomY != default) { uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; YuvToBgr(bottomY[0], (int)uv0 & 0xff, (int)(uv0 >> 16), bottomDst); @@ -117,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy YuvToBgr(topY[xMul2 - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((xMul2 - 1) * xStep)); YuvToBgr(topY[xMul2 - 0], (int)(uv1 & 0xff), (int)(uv1 >> 16), topDst.Slice((xMul2 - 0) * xStep)); - if (bottomY != null) + if (bottomY != default) { uv0 = (diag03 + luv) >> 1; uv1 = (diag12 + uv) >> 1; @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy { uv0 = ((3 * tluv) + luv + 0x00020002u) >> 2; YuvToBgr(topY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), topDst.Slice((len - 1) * xStep)); - if (bottomY != null) + if (bottomY != default) { uv0 = ((3 * luv) + tluv + 0x00020002u) >> 2; YuvToBgr(bottomY[len - 1], (int)(uv0 & 0xff), (int)(uv0 >> 16), bottomDst.Slice((len - 1) * xStep)); @@ -169,7 +169,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int u0t = (topU[0] + uDiag) >> 1; int v0t = (topV[0] + vDiag) >> 1; YuvToBgr(topY[0], u0t, v0t, topDst); - if (bottomY != null) + if (bottomY != default) { int u0b = (curU[0] + uDiag) >> 1; int v0b = (curV[0] + vDiag) >> 1; @@ -198,14 +198,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy UpSampleLastBlock(topV.Slice(uvPos), curV.Slice(uvPos), leftOver, rv); topY.Slice(pos, len - pos).CopyTo(tmpTop); - if (bottomY != null) + if (bottomY != default) { bottomY.Slice(pos, len - pos).CopyTo(tmpBottom); } ConvertYuvToBgrSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); tmpTopDst.Slice(0, (len - pos) * xStep).CopyTo(topDst.Slice(pos * xStep)); - if (bottomY != null) + if (bottomY != default) { tmpBottomDst.Slice(0, (len - pos) * xStep).CopyTo(bottomDst.Slice(pos * xStep)); } From 595492491e54ecfc760efa745ce170002ac3a3c0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 19 Nov 2021 13:10:50 +0100 Subject: [PATCH 148/511] Add upsample tests --- .../Formats/WebP/YuvConversionTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs index 65b4b987e..76dd207fc 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/YuvConversionTests.cs @@ -2,10 +2,14 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.IO; using SixLabors.ImageSharp.Advanced; +using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; namespace SixLabors.ImageSharp.Tests.Formats.Webp @@ -13,6 +17,34 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class YuvConversionTests { + private static WebpDecoder WebpDecoder => new(); + + private static MagickReferenceDecoder ReferenceDecoder => new(); + + private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Webp.Lossy.NoFilter06); + + public static void RunUpSampleYuvToRgbTest() + { + var provider = TestImageProvider.File(TestImageLossyFullPath); + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + [Fact] + public void UpSampleYuvToRgb_Works() => RunUpSampleYuvToRgbTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void UpSampleYuvToRgb_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.AllowAll); + + [Fact] + public void UpSampleYuvToRgb_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunUpSampleYuvToRgbTest, HwIntrinsics.DisableSSE2); + +#endif + [Theory] [WithFile(TestImages.Webp.Yuv, PixelTypes.Rgba32)] public void ConvertRgbToYuv_Works(TestImageProvider provider) From 1eb1e82a2f608c24b7d1ca40a7ab7f579dcdfe8b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 19 Nov 2021 13:25:01 +0100 Subject: [PATCH 149/511] Avoid allocating uvBuffer on each upscale call --- src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs | 9 +++++---- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs index 4d21333e6..202df9039 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/WebpLossyDecoder.cs @@ -692,16 +692,17 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int mbw = io.MbW; int uvw = (mbw + 1) / 2; int y = io.MbY; + byte[] uvBuffer = new byte[(14 * 32) + 15]; if (y == 0) { // First line is special cased. We mirror the u/v samples at boundary. - YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst, default, mbw); + YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst, default, mbw, uvBuffer); } else { // We can finish the left-over line from previous call. - YuvConversion.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf.Slice(dstStartIdx - bufferStride), dst, mbw); + YuvConversion.UpSample(tmpYBuffer, curY, topU, topV, curU, curV, buf.Slice(dstStartIdx - bufferStride), dst, mbw, uvBuffer); numLinesOut++; } @@ -714,7 +715,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy topV = curV; curU = curU.Slice(io.UvStride); curV = curV.Slice(io.UvStride); - YuvConversion.UpSample(curY.Slice(io.YStride), curY.Slice(ioStride2), topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(bufferStride2), mbw); + YuvConversion.UpSample(curY.Slice(io.YStride), curY.Slice(ioStride2), topU, topV, curU, curV, dst.Slice(bufferStride), dst.Slice(bufferStride2), mbw, uvBuffer); curY = curY.Slice(ioStride2); dst = dst.Slice(bufferStride2); } @@ -736,7 +737,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Process the very last row of even-sized picture. if ((yEnd & 1) == 0) { - YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst.Slice(bufferStride), default, mbw); + YuvConversion.UpSample(curY, default, curU, curV, curU, curV, dst.Slice(bufferStride), default, mbw, uvBuffer); } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 342fc330c..54d7ed65d 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -73,12 +73,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // we interpolate u/v as: // ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 // ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 - public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + public static void UpSample(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) { #if SUPPORTS_RUNTIME_INTRINSICS if (Sse41.IsSupported) { - UpSampleSse41(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len); + UpSampleSse41(topY, bottomY, topU, topV, curU, curV, topDst, bottomDst, len, uvBuffer); } else #endif @@ -156,10 +156,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // // Then m can be written as // m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 - public static void UpSampleSse41(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + public static void UpSampleSse41(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) { const int xStep = 3; - byte[] uvBuffer = new byte[(14 * 32) + 15]; + Array.Clear(uvBuffer, 0, uvBuffer.Length); Span ru = uvBuffer.AsSpan(15); Span rv = ru.Slice(32); From c59ae02e64ce3c905e566837e48a66ceac3b3459 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 19 Nov 2021 14:02:45 +0100 Subject: [PATCH 150/511] Change some methods to be private --- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 54d7ed65d..18cff1578 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - public static void UpSampleScalar(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) + private static void UpSampleScalar(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len) { int xStep = 3; int lastPixelPair = (len - 1) >> 1; @@ -156,7 +156,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // // Then m can be written as // m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 - public static void UpSampleSse41(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) + private static void UpSampleSse41(Span topY, Span bottomY, Span topU, Span topV, Span curU, Span curV, Span topDst, Span bottomDst, int len, byte[] uvBuffer) { const int xStep = 3; Array.Clear(uvBuffer, 0, uvBuffer.Length); @@ -213,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. - public static void UpSample32Pixels(Span r1, Span r2, Span output) + private static void UpSample32Pixels(Span r1, Span r2, Span output) { // Load inputs. Vector128 a = Unsafe.As>(ref MemoryMarshal.GetReference(r1)); @@ -592,7 +592,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } } - public static void YuvToBgrSse41(Span y, Span u, Span v, Span dst) + private static void YuvToBgrSse41(Span y, Span u, Span v, Span dst) { ConvertYuv444ToRgbSse41(y, u, v, out Vector128 r0, out Vector128 g0, out Vector128 b0); ConvertYuv444ToRgbSse41(y.Slice(8), u.Slice(8), v.Slice(8), out Vector128 r1, out Vector128 g1, out Vector128 b1); From c5170f950418c7ced1d9bf5fd75ada5a51180e0a Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Fri, 19 Nov 2021 15:50:03 +0100 Subject: [PATCH 151/511] Re-grouping the code to do identical operations Co-authored-by: Anton Firszov --- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 18cff1578..251060cee 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -719,13 +719,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 v0 = LoadHigh(v); Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16()); - Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); + Vector128 g0 = Sse2.MultiplyHigh(u0.AsUInt16(), K6419.AsUInt16()); + Vector128 g1 = Sse2.MultiplyHigh(v0.AsUInt16(), K13320.AsUInt16()); + Vector128 r1 = Sse2.Subtract(y1.AsUInt16(), K14234.AsUInt16()); Vector128 r2 = Sse2.Add(r1, r0); - Vector128 g0 = Sse2.MultiplyHigh(u0.AsUInt16(), K6419.AsUInt16()); - Vector128 g1 = Sse2.MultiplyHigh(v0.AsUInt16(), K13320.AsUInt16()); Vector128 g2 = Sse2.Add(y1.AsUInt16(), K8708.AsUInt16()); Vector128 g3 = Sse2.Add(g0, g1); Vector128 g4 = Sse2.Subtract(g2, g3); From 0c057278fdac4ceec2568b1e6d6faa3dd2ce2945 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 19 Nov 2021 15:52:06 +0100 Subject: [PATCH 152/511] Add InliningOptions.ShortMethod to LoadHigh --- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 251060cee..d40b674e6 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -722,7 +722,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); Vector128 g0 = Sse2.MultiplyHigh(u0.AsUInt16(), K6419.AsUInt16()); Vector128 g1 = Sse2.MultiplyHigh(v0.AsUInt16(), K13320.AsUInt16()); - + Vector128 r1 = Sse2.Subtract(y1.AsUInt16(), K14234.AsUInt16()); Vector128 r2 = Sse2.Add(r1, r0); @@ -734,13 +734,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy Vector128 b1 = Sse2.AddSaturate(b0, y1); Vector128 b2 = Sse2.SubtractSaturate(b1, K17685.AsUInt16()); - // use logical shift for B2, which can be larger than 32767 + // Use logical shift for B2, which can be larger than 32767. r = Sse2.ShiftRightArithmetic(r2.AsInt16(), 6); // range: [-14234, 30815] g = Sse2.ShiftRightArithmetic(g4.AsInt16(), 6); // range: [-10953, 27710] b = Sse2.ShiftRightLogical(b2.AsInt16(), 6); // range: [0, 34238] } // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. + [MethodImpl(InliningOptions.ShortMethod)] private static Vector128 LoadHigh(Span src) { Vector64 tmp = Unsafe.As>(ref MemoryMarshal.GetReference(src)); From d58dde006067a56ba289b6ce5bb93a502fb5ec30 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 19 Nov 2021 16:02:23 +0100 Subject: [PATCH 153/511] Group load uv vectors together --- .../Formats/Webp/Lossy/YuvConversion.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index d40b674e6..75a9963a5 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -714,9 +714,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Convert 32 samples of YUV444 to R/G/B private static void ConvertYuv444ToRgbSse41(Span y, Span u, Span v, out Vector128 r, out Vector128 g, out Vector128 b) { - Vector128 y0 = LoadHigh(y); - Vector128 u0 = LoadHigh(u); - Vector128 v0 = LoadHigh(v); + Vector64 yTmp = Unsafe.As>(ref MemoryMarshal.GetReference(y)); + Vector64 uTmp = Unsafe.As>(ref MemoryMarshal.GetReference(u)); + Vector64 vTmp = Unsafe.As>(ref MemoryMarshal.GetReference(v)); + Vector128 y0 = LoadHigh(yTmp); + Vector128 u0 = LoadHigh(uTmp); + Vector128 v0 = LoadHigh(vTmp); Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16()); Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); @@ -742,11 +745,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 LoadHigh(Span src) + private static Vector128 LoadHigh(Vector64 src) { - Vector64 tmp = Unsafe.As>(ref MemoryMarshal.GetReference(src)); - Vector128 tmp2 = Unsafe.As, Vector128>(ref tmp); - return Sse2.UnpackLow(Vector128.Zero, tmp2); + Vector128 tmp = Unsafe.As, Vector128>(ref src); + return Sse2.UnpackLow(Vector128.Zero, tmp); } #endif From 7cf0c32e9f02677d95399f00f3f4735364ccbaec Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 19 Nov 2021 16:51:31 +0100 Subject: [PATCH 154/511] Pass in parameters as ref to UpSample32Pixels --- .../Formats/Webp/Lossy/YuvConversion.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 75a9963a5..8e3b15389 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -179,10 +179,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // For UpSample32Pixels, 17 u/v values must be read-able for each block. int pos; int uvPos; + ref byte topURef = ref MemoryMarshal.GetReference(topU); + ref byte topVRef = ref MemoryMarshal.GetReference(topV); + ref byte curURef = ref MemoryMarshal.GetReference(curU); + ref byte curVRef = ref MemoryMarshal.GetReference(curV); for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) { - UpSample32Pixels(topU.Slice(uvPos), curU.Slice(uvPos), ru); - UpSample32Pixels(topV.Slice(uvPos), curV.Slice(uvPos), rv); + UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); + UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); ConvertYuvToBgrSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); } @@ -213,13 +217,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } // Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. - private static void UpSample32Pixels(Span r1, Span r2, Span output) + private static void UpSample32Pixels(ref byte r1, ref byte r2, Span output) { // Load inputs. - Vector128 a = Unsafe.As>(ref MemoryMarshal.GetReference(r1)); - Vector128 b = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(r1), 1)); - Vector128 c = Unsafe.As>(ref MemoryMarshal.GetReference(r2)); - Vector128 d = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(r2), 1)); + Vector128 a = Unsafe.As>(ref r1); + Vector128 b = Unsafe.As>(ref Unsafe.Add(ref r1, 1)); + Vector128 c = Unsafe.As>(ref r2); + Vector128 d = Unsafe.As>(ref Unsafe.Add(ref r2, 1)); Vector128 s = Sse2.Average(a, d); // s = (a + d + 1) / 2 Vector128 t = Sse2.Average(b, c); // t = (b + c + 1) / 2 @@ -257,7 +261,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy r2.Slice(numPixels, length).Fill(r2[numPixels - 1]); } - UpSample32Pixels(r1, r2, output); + ref byte r1Ref = ref MemoryMarshal.GetReference(r1); + ref byte r2Ref = ref MemoryMarshal.GetReference(r2); + UpSample32Pixels(ref r1Ref, ref r2Ref, output); } // Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 From ab0480f1bd7117971051f1aa5f53d09f59a01ac2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 19 Nov 2021 18:57:55 +0100 Subject: [PATCH 155/511] 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 156/511] 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 157/511] 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 158/511] 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 159/511] 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 160/511] 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 3de317f6d589c01d6332584c79dd2bc07b14374a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 16:56:08 +0100 Subject: [PATCH 161/511] Change hashchain to use the memoryAllocator --- .../Webp/Lossless/BackwardReferenceEncoder.cs | 15 +++-- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 9 ++- .../Formats/Webp/Lossless/Vp8LHashChain.cs | 59 ++++++++++++++----- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index dc546f8ac..93f6372c6 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -3,10 +3,11 @@ using System; using System.Collections.Generic; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal class BackwardReferenceEncoder + internal static class BackwardReferenceEncoder { /// /// Maximum bit length. @@ -41,6 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int quality, int lz77TypesToTry, ref int cacheBits, + MemoryAllocator memoryAllocator, Vp8LHashChain hashChain, Vp8LBackwardRefs best, Vp8LBackwardRefs worst) @@ -69,7 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless BackwardReferencesLz77(width, height, bgra, 0, hashChain, worst); break; case Vp8LLz77Type.Lz77Box: - hashChainBox = new Vp8LHashChain(width * height); + hashChainBox = new Vp8LHashChain(memoryAllocator, width * height); BackwardReferencesLz77Box(width, height, bgra, 0, hashChain, hashChainBox, worst); break; } @@ -617,7 +619,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - hashChain.OffsetLength[0] = 0; + Span hashChainOffsetLength = hashChain.OffsetLength.GetSpan(); + hashChainOffsetLength[0] = 0; for (i = 1; i < pixelCount; i++) { int ind; @@ -695,19 +698,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (bestLength <= MinLength) { - hashChain.OffsetLength[i] = 0; + hashChainOffsetLength[i] = 0; bestOffsetPrev = 0; bestLengthPrev = 0; } else { - hashChain.OffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); + hashChainOffsetLength[i] = (uint)((bestOffset << MaxLengthBits) | bestLength); bestOffsetPrev = bestOffset; bestLengthPrev = bestLength; } } - hashChain.OffsetLength[0] = 0; + hashChainOffsetLength[0] = 0; BackwardReferencesLz77(xSize, ySize, bgra, cacheBits, hashChain, refs); } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index da815a479..48f7d0e2b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.EncodedData = memoryAllocator.Allocate(pixelCount); this.Palette = memoryAllocator.Allocate(WebpConstants.MaxPaletteSize); this.Refs = new Vp8LBackwardRefs[3]; - this.HashChain = new Vp8LHashChain(pixelCount); + this.HashChain = new Vp8LHashChain(memoryAllocator, pixelCount); // We round the block size up, so we're guaranteed to have at most MaxRefsBlockPerImage blocks used: int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; @@ -515,7 +515,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Calculate backward references from BGRA image. - this.HashChain.Fill(this.memoryAllocator, bgra, this.quality, width, height, lowEffort); + this.HashChain.Fill(bgra, this.quality, width, height, lowEffort); Vp8LBitWriter bitWriterBest = config.SubConfigs.Count > 1 ? this.bitWriter.Clone() : this.bitWriter; Vp8LBitWriter bwInit = this.bitWriter; @@ -529,6 +529,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.quality, subConfig.Lz77, ref cacheBits, + this.memoryAllocator, this.HashChain, this.Refs[0], this.Refs[1]); @@ -735,7 +736,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Calculate backward references from the image pixels. - hashChain.Fill(this.memoryAllocator, bgra, quality, width, height, lowEffort); + hashChain.Fill(bgra, quality, width, height, lowEffort); Vp8LBackwardRefs refs = BackwardReferenceEncoder.GetBackwardReferences( width, @@ -744,6 +745,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless quality, (int)Vp8LLz77Type.Lz77Standard | (int)Vp8LLz77Type.Lz77Rle, ref cacheBits, + this.memoryAllocator, hashChain, refsTmp1, refsTmp2); @@ -1802,6 +1804,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.BgraScratch.Dispose(); this.Palette.Dispose(); this.TransformData.Dispose(); + this.HashChain.Dispose(); } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs index 977a094bd..2aa35e392 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal class Vp8LHashChain + internal class Vp8LHashChain : IDisposable { private const uint HashMultiplierHi = 0xc6a4a793u; @@ -28,14 +28,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private const int WindowSize = (1 << WindowSizeBits) - 120; + private readonly MemoryAllocator memoryAllocator; + + private bool disposed; + /// /// Initializes a new instance of the class. /// + /// The memory allocator. /// The size off the chain. - public Vp8LHashChain(int size) + public Vp8LHashChain(MemoryAllocator memoryAllocator, int size) { - this.OffsetLength = new uint[size]; - this.OffsetLength.AsSpan().Fill(0xcdcdcdcd); + this.memoryAllocator = memoryAllocator; + this.OffsetLength = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); this.Size = size; } @@ -45,16 +50,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// These 20 bits are the limit defined by GetWindowSizeForHashChain (through WindowSize = 1 << 20). /// The lower 12 bits contain the length of the match. /// - public uint[] OffsetLength { get; } + public IMemoryOwner OffsetLength { get; } /// /// Gets the size of the hash chain. - /// This is the maximum size of the hash_chain that can be constructed. + /// This is the maximum size of the hashchain that can be constructed. /// Typically this is the pixel count (width x height) for a given image. /// public int Size { get; } - public void Fill(MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int quality, int xSize, int ySize, bool lowEffort) + public void Fill(ReadOnlySpan bgra, int quality, int xSize, int ySize, bool lowEffort) { int size = xSize * ySize; int iterMax = GetMaxItersForQuality(quality); @@ -63,20 +68,21 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (size <= 2) { - this.OffsetLength[0] = 0; + this.OffsetLength.GetSpan()[0] = 0; return; } - using IMemoryOwner hashToFirstIndexBuffer = memoryAllocator.Allocate(HashSize); + using IMemoryOwner hashToFirstIndexBuffer = this.memoryAllocator.Allocate(HashSize); + using IMemoryOwner chainBuffer = this.memoryAllocator.Allocate(size, AllocationOptions.Clean); Span hashToFirstIndex = hashToFirstIndexBuffer.GetSpan(); + Span chain = chainBuffer.GetSpan(); // Initialize hashToFirstIndex array to -1. hashToFirstIndex.Fill(-1); - int[] chain = new int[size]; - // Fill the chain linking pixels with the same hash. bool bgraComp = bgra.Length > 1 && bgra[0] == bgra[1]; + Span tmp = stackalloc uint[2]; for (pos = 0; pos < size - 2;) { uint hashCode; @@ -85,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { // Consecutive pixels with the same color will share the same hash. // We therefore use a different hash: the color and its repetition length. - uint[] tmp = new uint[2]; + tmp.Clear(); uint len = 1; tmp[0] = bgra[pos]; @@ -134,7 +140,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Find the best match interval at each pixel, defined by an offset to the // pixel and a length. The right-most pixel cannot match anything to the right // (hence a best length of 0) and the left-most pixel nothing to the left (hence an offset of 0). - this.OffsetLength[0] = this.OffsetLength[size - 1] = 0; + Span offsetLength = this.OffsetLength.GetSpan(); + offsetLength[0] = offsetLength[size - 1] = 0; for (int basePosition = size - 2; basePosition > 0;) { int maxLen = LosslessUtils.MaxFindCopyLength(size - 1 - basePosition); @@ -208,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless uint maxBasePosition = (uint)basePosition; while (true) { - this.OffsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; + offsetLength[basePosition] = (bestDistance << BackwardReferenceEncoder.MaxLengthBits) | (uint)bestLength; --basePosition; // Stop if we don't have a match or if we are out of bounds. @@ -242,10 +249,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - public int FindLength(int basePosition) => (int)(this.OffsetLength[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); + public int FindLength(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] & ((1U << BackwardReferenceEncoder.MaxLengthBits) - 1)); [MethodImpl(InliningOptions.ShortMethod)] - public int FindOffset(int basePosition) => (int)(this.OffsetLength[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); + public int FindOffset(int basePosition) => (int)(this.OffsetLength.GetSpan()[basePosition] >> BackwardReferenceEncoder.MaxLengthBits); /// /// Calculates the hash for a pixel pair. @@ -280,5 +287,25 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.OffsetLength.Dispose(); + } + + this.disposed = true; + } + } + + /// + public void Dispose() + { + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } From 40b6f4e55bb12127bc84d33c8885dd21a74fb768 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 17:00:34 +0100 Subject: [PATCH 162/511] Change huffman code to a struct --- .../Formats/Webp/Lossless/HTreeGroup.cs | 4 -- .../Formats/Webp/Lossless/HuffmanCode.cs | 2 +- .../Formats/Webp/Lossless/HuffmanUtils.cs | 45 ++++++++++--------- .../Webp/Lossless/WebpLosslessDecoder.cs | 14 +++--- .../WebP/ColorSpaceTransformUtilsTests.cs | 2 +- .../Formats/WebP/LossyUtilsTests.cs | 2 +- .../Formats/WebP/QuantEncTests.cs | 2 +- .../Formats/WebP/Vp8EncodingTests.cs | 2 +- 8 files changed, 35 insertions(+), 38 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs index a038248f1..09ceb0334 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs @@ -19,10 +19,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); this.PackedTable = new HuffmanCode[packedTableSize]; - for (int i = 0; i < packedTableSize; i++) - { - this.PackedTable[i] = new HuffmanCode(); - } } /// diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs index f75c64de1..efb928356 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanCode.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// A classic way to do entropy coding where a smaller number of bits are used for more frequent codes. /// [DebuggerDisplay("BitsUsed: {BitsUsed}, Value: {Value}")] - internal class HuffmanCode + internal struct HuffmanCode { /// /// Gets or sets the number of bits used for this symbol. diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 5db01ca1c..66170b85f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { @@ -307,9 +308,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static int BuildHuffmanTable(Span table, int rootBits, int[] codeLengths, int codeLengthsSize) { - Guard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); - Guard.NotNull(codeLengths, nameof(codeLengths)); - Guard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); + DebugGuard.MustBeGreaterThan(rootBits, 0, nameof(rootBits)); + DebugGuard.NotNull(codeLengths, nameof(codeLengths)); + DebugGuard.MustBeGreaterThan(codeLengthsSize, 0, nameof(codeLengthsSize)); // sorted[codeLengthsSize] is a pre-allocated array for sorting symbols by code length. int[] sorted = new int[codeLengthsSize]; @@ -467,27 +468,27 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless break; } - else if (repetitions < 11) + + if (repetitions < 11) { tokens[pos].Code = 17; tokens[pos].ExtraBits = (byte)(repetitions - 3); pos++; break; } - else if (repetitions < 139) + + if (repetitions < 139) { tokens[pos].Code = 18; tokens[pos].ExtraBits = (byte)(repetitions - 11); pos++; break; } - else - { - tokens[pos].Code = 18; - tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s - pos++; - repetitions -= 138; - } + + tokens[pos].Code = 18; + tokens[pos].ExtraBits = 0x7f; // 138 repeated 0s + pos++; + repetitions -= 138; } return pos; @@ -519,20 +520,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless break; } - else if (repetitions < 7) + + if (repetitions < 7) { tokens[pos].Code = 16; tokens[pos].ExtraBits = (byte)(repetitions - 3); pos++; break; } - else - { - tokens[pos].Code = 16; - tokens[pos].ExtraBits = 3; - pos++; - repetitions -= 6; - } + + tokens[pos].Code = 16; + tokens[pos].ExtraBits = 3; + pos++; + repetitions -= 6; } return pos; @@ -541,7 +541,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Get the actual bit values for a tree of bit depths. /// - /// The hiffman tree. + /// The huffman tree. private static void ConvertBitDepthsToSymbols(HuffmanTreeCode tree) { // 0 bit-depth means that the symbol does not exist. @@ -628,7 +628,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private static void ReplicateValue(Span table, int step, int end, HuffmanCode code) { - Guard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); + DebugGuard.IsTrue(end % step == 0, nameof(end), "end must be a multiple of step"); do { @@ -656,6 +656,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// /// Heuristics for selecting the stride ranges to collapse. /// + [MethodImpl(InliningOptions.ShortMethod)] private static bool ValuesShouldBeCollapsedToStrideAverage(int a, int b) => Math.Abs(a - b) < 4; } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 4f7a4eb3d..82fd13c7d 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -834,10 +834,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void BuildPackedTable(HTreeGroup hTreeGroup) { - for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; ++code) + for (uint code = 0; code < HuffmanUtils.HuffmanPackedTableSize; code++) { uint bits = code; - HuffmanCode huff = hTreeGroup.PackedTable[bits]; + ref HuffmanCode huff = ref hTreeGroup.PackedTable[bits]; HuffmanCode hCode = hTreeGroup.HTrees[HuffIndex.Green][bits]; if (hCode.Value >= WebpConstants.NumLiteralCodes) { @@ -848,10 +848,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { huff.BitsUsed = 0; huff.Value = 0; - bits >>= AccumulateHCode(hCode, 8, huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, huff); - bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, huff); + bits >>= AccumulateHCode(hCode, 8, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Red][bits], 16, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Blue][bits], 0, ref huff); + bits >>= AccumulateHCode(hTreeGroup.HTrees[HuffIndex.Alpha][bits], 24, ref huff); } } } @@ -992,7 +992,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } [MethodImpl(InliningOptions.ShortMethod)] - private static int AccumulateHCode(HuffmanCode hCode, int shift, HuffmanCode huff) + private static int AccumulateHCode(HuffmanCode hCode, int shift, ref HuffmanCode huff) { huff.BitsUsed += hCode.BitsUsed; huff.Value |= hCode.Value << shift; diff --git a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs index 5306a8c78..f7eef0d85 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/ColorSpaceTransformUtilsTests.cs @@ -5,7 +5,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class ColorSpaceTransformUtilsTests diff --git a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs index 69a24843c..907b18300 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LossyUtilsTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class LossyUtilsTests diff --git a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs index 55738199b..80b5f0a53 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/QuantEncTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class QuantEncTests diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs index 17c9beb9b..6bcb4f21f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8EncodingTests.cs @@ -6,7 +6,7 @@ using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Tests.TestUtilities; using Xunit; -namespace SixLabors.ImageSharp.Tests.Formats.WebP +namespace SixLabors.ImageSharp.Tests.Formats.Webp { [Trait("Format", "Webp")] public class Vp8EncodingTests From 44316b223157d59873753548cedfe9b176fd9e80 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 18:01:45 +0100 Subject: [PATCH 163/511] Make HTreeGroup a struct --- src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs index 09ceb0334..6c2217eb6 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HTreeGroup.cs @@ -13,12 +13,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// - UsePackedTable: few enough literal symbols, so all the bit codes can fit into a small look-up table PackedTable[] /// The common literal base, if applicable, is stored in 'LiteralArb'. /// - internal class HTreeGroup + internal struct HTreeGroup { public HTreeGroup(uint packedTableSize) { this.HTrees = new List(WebpConstants.HuffmanCodesPerMetaCode); this.PackedTable = new HuffmanCode[packedTableSize]; + this.IsTrivialCode = false; + this.IsTrivialLiteral = false; + this.LiteralArb = 0; + this.UsePackedTable = false; } /// From c712f98e055caa7a23eedcce1bc8cfe476664453 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Nov 2021 18:17:55 +0100 Subject: [PATCH 164/511] Change CodeLengthCodeOrder, LiteralMap and Norm to ReadOnlySpan --- .../Formats/Webp/Lossless/WebpLosslessDecoder.cs | 13 ++++++------- src/ImageSharp/Formats/Webp/WebpLookupTables.cs | 3 ++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs index 82fd13c7d..82bd32a02 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/WebpLosslessDecoder.cs @@ -65,15 +65,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless FixedTableSize + 2704 }; - private static readonly byte[] CodeLengthCodeOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - private static readonly int NumCodeLengthCodes = CodeLengthCodeOrder.Length; - private static readonly byte[] LiteralMap = - { - 0, 1, 1, 1, 0 - }; - /// /// Initializes a new instance of the class. /// @@ -87,6 +80,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.configuration = configuration; } + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan CodeLengthCodeOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan LiteralMap => new byte[] { 0, 1, 1, 1, 0 }; + /// /// Decodes the image from the stream using the bitreader. /// diff --git a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs index bf47b01bc..c89411435 100644 --- a/src/ImageSharp/Formats/Webp/WebpLookupTables.cs +++ b/src/ImageSharp/Formats/Webp/WebpLookupTables.cs @@ -239,7 +239,8 @@ namespace SixLabors.ImageSharp.Formats.Webp } }; - public static readonly byte[] Norm = + // This uses C#'s compiler optimization to refer to assembly's static data directly. + public static ReadOnlySpan Norm => new byte[] { // renorm_sizes[i] = 8 - log2(i) 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, From af7c3c509c6dcc2359ad36e21749a59b20ba0ceb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 04:38:12 +0300 Subject: [PATCH 165/511] Added generic benchmark --- .../Codecs/Jpeg/DecodeJpeg.cs | 81 +++++++++++++++++++ .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 3 +- .../Jpg/baseline/winter444_interleaved.jpg | 3 + ...inter.jpg => winter420_noninterleaved.jpg} | 0 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs create mode 100644 tests/Images/Input/Jpg/baseline/winter444_interleaved.jpg rename tests/Images/Input/Jpg/progressive/{winter.jpg => winter420_noninterleaved.jpg} (100%) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs new file mode 100644 index 000000000..7a878738d --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -0,0 +1,81 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Tests; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg +{ + public class DecodeJpeg + { + private JpegDecoder decoder; + + private MemoryStream preloadedImageStream; + + private void GenericSetup(string imageSubpath) + { + this.decoder = new JpegDecoder(); + byte[] bytes = File.ReadAllBytes(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, imageSubpath)); + this.preloadedImageStream = new MemoryStream(bytes); + } + + private void GenericBechmark() + { + this.preloadedImageStream.Position = 0; + using Image img = this.decoder.Decode(Configuration.Default, this.preloadedImageStream); + } + + [GlobalSetup(Target = nameof(JpegBaselineInterleaved444))] + public void SetupBaselineInterleaved444() => + this.GenericSetup(TestImages.Jpeg.Baseline.Winter444_Interleaved); + + [GlobalSetup(Target = nameof(JpegBaselineInterleaved420))] + public void SetupBaselineInterleaved420() => + this.GenericSetup(TestImages.Jpeg.Baseline.Hiyamugi); + + [GlobalSetup(Target = nameof(JpegBaseline400))] + public void SetupBaselineSingleComponent() => + this.GenericSetup(TestImages.Jpeg.Baseline.Jpeg400); + + [GlobalSetup(Target = nameof(JpegProgressiveNonInterleaved420))] + public void SetupProgressiveNoninterleaved420() => + this.GenericSetup(TestImages.Jpeg.Progressive.Winter420_NonInterleaved); + + [GlobalCleanup] + public void Cleanup() + { + this.preloadedImageStream.Dispose(); + this.preloadedImageStream = null; + } + + [Benchmark(Description = "Baseline 4:4:4 Interleaved")] + public void JpegBaselineInterleaved444() => this.GenericBechmark(); + + [Benchmark(Description = "Baseline 4:2:0 Interleaved")] + public void JpegBaselineInterleaved420() => this.GenericBechmark(); + + [Benchmark(Description = "Baseline 4:0:0 (grayscale)")] + public void JpegBaseline400() => this.GenericBechmark(); + + [Benchmark(Description = "Progressive 4:2:0 Non-Interleaved")] + public void JpegProgressiveNonInterleaved420() => this.GenericBechmark(); + } +} + + +/* +BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1288 (20H2/October2020Update) +Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores +.NET SDK=6.0.100-preview.3.21202.5 + [Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT + DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT +MASTER +| Method | Mean | Error | StdDev | +|------------------------------------ |----------:|----------:|----------:| +| 'Baseline 4:4:4 Interleaved' | 12.710 ms | 0.1120 ms | 0.0990 ms | +| 'Baseline 4:2:0 Interleaved' | 8.855 ms | 0.1447 ms | 0.1353 ms | +| 'Baseline 4:0:0 (grayscale)' | 1.660 ms | 0.0106 ms | 0.0088 ms | +| 'Progressive 4:2:0 Non-Interleaved' | 14.138 ms | 0.2797 ms | 0.3330 ms | +*/ diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 5e42c6c8f..7b3e20aa2 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { TestImages.Jpeg.Progressive.Fb, 75 }, { TestImages.Jpeg.Issues.IncorrectQuality845, 98 }, { TestImages.Jpeg.Baseline.ForestBridgeDifferentComponentsQuality, 89 }, - { TestImages.Jpeg.Progressive.Winter, 80 } + { TestImages.Jpeg.Progressive.Winter420_NonInterleaved, 80 } }; [Theory] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 116c5adc3..e00364913 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -163,7 +163,7 @@ namespace SixLabors.ImageSharp.Tests public const string Fb = "Jpg/progressive/fb.jpg"; public const string Progress = "Jpg/progressive/progress.jpg"; public const string Festzug = "Jpg/progressive/Festzug.jpg"; - public const string Winter = "Jpg/progressive/winter.jpg"; + public const string Winter420_NonInterleaved = "Jpg/progressive/winter420_noninterleaved.jpg"; public static class Bad { @@ -213,6 +213,7 @@ namespace SixLabors.ImageSharp.Tests public const string ArithmeticCoding = "Jpg/baseline/arithmetic_coding.jpg"; public const string ArithmeticCodingProgressive = "Jpg/progressive/arithmetic_progressive.jpg"; public const string Lossless = "Jpg/baseline/lossless.jpg"; + public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; public static readonly string[] All = { diff --git a/tests/Images/Input/Jpg/baseline/winter444_interleaved.jpg b/tests/Images/Input/Jpg/baseline/winter444_interleaved.jpg new file mode 100644 index 000000000..9ae834389 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/winter444_interleaved.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73b1deb4e2fb8027f6bb4fb293e5b2615c80b3ac0a7f99fd90118fd340a9fd12 +size 283330 diff --git a/tests/Images/Input/Jpg/progressive/winter.jpg b/tests/Images/Input/Jpg/progressive/winter420_noninterleaved.jpg similarity index 100% rename from tests/Images/Input/Jpg/progressive/winter.jpg rename to tests/Images/Input/Jpg/progressive/winter420_noninterleaved.jpg From cdb2a648a6cdc2061ee399ceae4a96e7e9b59f3b Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 04:47:37 +0300 Subject: [PATCH 166/511] Structured benchmark files --- tests/ImageSharp.Benchmarks/Codecs/{ => Bmp}/DecodeBmp.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Bmp}/EncodeBmp.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Bmp}/EncodeBmpMultiple.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Gif}/DecodeGif.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Gif}/EncodeGif.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Gif}/EncodeGifMultiple.cs | 0 .../Codecs/Jpeg/{ => ColorConversion}/CmykColorConversion.cs | 0 .../Codecs/Jpeg/{ => ColorConversion}/ColorConversionBenchmark.cs | 0 .../Codecs/Jpeg/{ => ColorConversion}/GrayscaleColorConversion.cs | 0 .../Codecs/Jpeg/{ => ColorConversion}/RgbColorConversion.cs | 0 .../Codecs/Jpeg/{ => ColorConversion}/YCbCrColorConversion.cs | 0 .../Jpeg/ColorConversion}/YCbCrForwardConverterBenchmark.cs | 0 .../Codecs/Jpeg/{ => ColorConversion}/YccKColorConverter.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Png}/DecodeFilteredPng.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Png}/DecodePng.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Png}/EncodeIndexedPng.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Png}/EncodePng.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Tga}/DecodeTga.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Tga}/EncodeTga.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Tiff}/DecodeTiff.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Tiff}/EncodeTiff.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Webp}/DecodeWebp.cs | 0 tests/ImageSharp.Benchmarks/Codecs/{ => Webp}/EncodeWebp.cs | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename tests/ImageSharp.Benchmarks/Codecs/{ => Bmp}/DecodeBmp.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Bmp}/EncodeBmp.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Bmp}/EncodeBmpMultiple.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Gif}/DecodeGif.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Gif}/EncodeGif.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Gif}/EncodeGifMultiple.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/Jpeg/{ => ColorConversion}/CmykColorConversion.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/Jpeg/{ => ColorConversion}/ColorConversionBenchmark.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/Jpeg/{ => ColorConversion}/GrayscaleColorConversion.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/Jpeg/{ => ColorConversion}/RgbColorConversion.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/Jpeg/{ => ColorConversion}/YCbCrColorConversion.cs (100%) rename tests/ImageSharp.Benchmarks/{Format/Jpeg/Components/Encoder => Codecs/Jpeg/ColorConversion}/YCbCrForwardConverterBenchmark.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/Jpeg/{ => ColorConversion}/YccKColorConverter.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Png}/DecodeFilteredPng.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Png}/DecodePng.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Png}/EncodeIndexedPng.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Png}/EncodePng.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Tga}/DecodeTga.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Tga}/EncodeTga.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Tiff}/DecodeTiff.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Tiff}/EncodeTiff.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Webp}/DecodeWebp.cs (100%) rename tests/ImageSharp.Benchmarks/Codecs/{ => Webp}/EncodeWebp.cs (100%) diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/DecodeBmp.cs rename to tests/ImageSharp.Benchmarks/Codecs/Bmp/DecodeBmp.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeBmp.cs rename to tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmp.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeBmpMultiple.cs rename to tests/ImageSharp.Benchmarks/Codecs/Bmp/EncodeBmpMultiple.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/DecodeGif.cs rename to tests/ImageSharp.Benchmarks/Codecs/Gif/DecodeGif.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeGif.cs rename to tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGif.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs b/tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeGifMultiple.cs rename to tests/ImageSharp.Benchmarks/Codecs/Gif/EncodeGifMultiple.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/Jpeg/CmykColorConversion.cs rename to tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/CmykColorConversion.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversionBenchmark.cs rename to tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/ColorConversionBenchmark.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/Jpeg/GrayscaleColorConversion.cs rename to tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/GrayscaleColorConversion.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/Jpeg/RgbColorConversion.cs rename to tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/RgbColorConversion.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/Jpeg/YCbCrColorConversion.cs rename to tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrColorConversion.cs diff --git a/tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs rename to tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YCbCrForwardConverterBenchmark.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/Jpeg/YccKColorConverter.cs rename to tests/ImageSharp.Benchmarks/Codecs/Jpeg/ColorConversion/YccKColorConverter.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/DecodeFilteredPng.cs rename to tests/ImageSharp.Benchmarks/Codecs/Png/DecodeFilteredPng.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/DecodePng.cs rename to tests/ImageSharp.Benchmarks/Codecs/Png/DecodePng.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeIndexedPng.cs rename to tests/ImageSharp.Benchmarks/Codecs/Png/EncodeIndexedPng.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs rename to tests/ImageSharp.Benchmarks/Codecs/Png/EncodePng.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs rename to tests/ImageSharp.Benchmarks/Codecs/Tga/DecodeTga.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs rename to tests/ImageSharp.Benchmarks/Codecs/Tga/EncodeTga.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs rename to tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs b/tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeTiff.cs rename to tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/DecodeWebp.cs rename to tests/ImageSharp.Benchmarks/Codecs/Webp/DecodeWebp.cs diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs b/tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs similarity index 100% rename from tests/ImageSharp.Benchmarks/Codecs/EncodeWebp.cs rename to tests/ImageSharp.Benchmarks/Codecs/Webp/EncodeWebp.cs From e143093d98c38082d64e7c0e83d0793fd7c50e8a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 04:52:40 +0300 Subject: [PATCH 167/511] Removed excess comment --- tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 7a878738d..842eea685 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -71,7 +71,7 @@ Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 [Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT -MASTER + | Method | Mean | Error | StdDev | |------------------------------------ |----------:|----------:|----------:| | 'Baseline 4:4:4 Interleaved' | 12.710 ms | 0.1120 ms | 0.0990 ms | From 582fa51229cc9a8c0a8f600546ea3d9f5acfaa7d Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 05:53:36 +0300 Subject: [PATCH 168/511] Fixed jpeg component mcu size calculation bug --- .../Components/Decoder/HuffmanScanDecoder.cs | 1 + .../Components/Decoder/SpectralConverter.cs | 19 +++++++++++++ .../Decoder/SpectralConverter{TPixel}.cs | 28 ++++++++----------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index bc9a53ea0..622657c48 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -151,6 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder if (this.componentsCount == this.frame.ComponentCount) { this.ParseBaselineDataInterleaved(); + this.spectralConverter.CommitConvertion(); } else { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index e975b11fb..aca9dc36b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -13,6 +13,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// internal abstract class SpectralConverter { + /// + /// Gets a value indicating whether this converter has converted spectral + /// data of the current image or not. + /// + protected bool Converted { get; private set; } + /// /// Injects jpeg image decoding metadata. /// @@ -33,6 +39,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public abstract void ConvertStrideBaseline(); + /// + /// Marks current converter state as 'converted'. + /// + /// + /// This must be called only for baseline interleaved jpeg's. + /// + public void CommitConvertion() + { + DebugGuard.IsFalse(this.Converted, nameof(this.Converted), $"{nameof(this.CommitConvertion)} must be called only once"); + + this.Converted = true; + } + /// /// Gets the color converter. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index ec7f3e5c3..2e965e0ac 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -3,6 +3,7 @@ using System; using System.Buffers; +using System.Linq; using System.Numerics; using System.Threading; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters; @@ -29,8 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder private Buffer2D pixelBuffer; - private int blockRowsPerStep; - private int pixelRowsPerStep; private int pixelRowCounter; @@ -41,8 +40,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.cancellationToken = cancellationToken; } - private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height; - public Buffer2D GetPixelBuffer() { if (!this.Converted) @@ -52,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int step = 0; step < steps; step++) { this.cancellationToken.ThrowIfCancellationRequested(); - this.ConvertNextStride(step); + this.ConvertStride(step); } } @@ -65,18 +62,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder MemoryAllocator allocator = this.configuration.MemoryAllocator; // iteration data - IJpegComponent c0 = frame.Components[0]; + int majorBlockWidth = frame.Components.Max((component) => component.SizeInBlocks.Width); + int majorVerticalSamplingFactor = frame.Components.Max((component) => component.SamplingFactors.Height); const int blockPixelHeight = 8; - this.blockRowsPerStep = c0.SamplingFactors.Height; - this.pixelRowsPerStep = this.blockRowsPerStep * blockPixelHeight; + this.pixelRowsPerStep = majorVerticalSamplingFactor * blockPixelHeight; // pixel buffer for resulting image this.pixelBuffer = allocator.Allocate2D(frame.PixelWidth, frame.PixelHeight); this.paddedProxyPixelRow = allocator.Allocate(frame.PixelWidth + 3); // component processors from spectral to Rgba32 - var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep); + const int blockPixelWidth = 8; + var postProcessorBufferSize = new Size(majorBlockWidth * blockPixelWidth, this.pixelRowsPerStep); this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length]; for (int i = 0; i < this.componentProcessors.Length; i++) { @@ -84,7 +82,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } // single 'stride' rgba32 buffer for conversion between spectral and TPixel - // this.rgbaBuffer = allocator.Allocate(frame.PixelWidth); this.rgbBuffer = allocator.Allocate(frame.PixelWidth * 3); // color converter from Rgba32 to TPixel @@ -95,18 +92,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder public override void ConvertStrideBaseline() { // Convert next pixel stride using single spectral `stride' - // Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor - this.ConvertNextStride(spectralStep: 0); + // Note that zero passing eliminates the need of virtual call + // from JpegComponentPostProcessor + this.ConvertStride(spectralStep: 0); - // Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride - // Which leads to decoding artifacts - // Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride foreach (JpegComponentPostProcessor cpp in this.componentProcessors) { cpp.ClearSpectralBuffers(); } } + /// public void Dispose() { if (this.componentProcessors != null) @@ -121,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.paddedProxyPixelRow?.Dispose(); } - private void ConvertNextStride(int spectralStep) + private void ConvertStride(int spectralStep) { int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep); From e1c0a39c9d2c8ef712af09f7694068efd0d323bd Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 06:25:31 +0300 Subject: [PATCH 169/511] Added bug proof image to jpeg decoder test suit --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs | 2 ++ .../JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index d12240cba..ef817154d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -20,6 +20,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Baseline.Jpeg420Small, TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, TestImages.Jpeg.Baseline.Jpeg444, + TestImages.Jpeg.Baseline.Jpeg422, TestImages.Jpeg.Baseline.Bad.BadEOF, TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, TestImages.Jpeg.Baseline.YcckSubsample1222, @@ -100,6 +101,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, + [TestImages.Jpeg.Baseline.Jpeg422] = 0.0013f / 100, [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, [TestImages.Jpeg.Baseline.Jpeg420Small] = 0.287f / 100, [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, diff --git a/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png new file mode 100644 index 000000000..018ecda7a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/JpegDecoderTests/DecodeBaselineJpeg_jpeg422.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:733cc46271c4402974db2536a55e6ecae3110856df73031ca48dad03745d852d +size 35375 From 5e40977eb033fbc3296561f5ebf30cef2bff7467 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 06:36:57 +0300 Subject: [PATCH 170/511] Fixed typo --- .../Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 2 +- .../Formats/Jpeg/Components/Decoder/SpectralConverter.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 622657c48..ad09b5065 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder if (this.componentsCount == this.frame.ComponentCount) { this.ParseBaselineDataInterleaved(); - this.spectralConverter.CommitConvertion(); + this.spectralConverter.CommitConversion(); } else { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs index aca9dc36b..4e74f6226 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs @@ -45,9 +45,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// This must be called only for baseline interleaved jpeg's. /// - public void CommitConvertion() + public void CommitConversion() { - DebugGuard.IsFalse(this.Converted, nameof(this.Converted), $"{nameof(this.CommitConvertion)} must be called only once"); + DebugGuard.IsFalse(this.Converted, nameof(this.Converted), $"{nameof(this.CommitConversion)} must be called only once"); this.Converted = true; } From 2a182d75637702ae8c633545dba1b7fe032bd8b2 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 07:07:03 +0300 Subject: [PATCH 171/511] Updated benchmark results --- tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 842eea685..cb89f9082 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -74,8 +74,8 @@ Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores | Method | Mean | Error | StdDev | |------------------------------------ |----------:|----------:|----------:| -| 'Baseline 4:4:4 Interleaved' | 12.710 ms | 0.1120 ms | 0.0990 ms | -| 'Baseline 4:2:0 Interleaved' | 8.855 ms | 0.1447 ms | 0.1353 ms | -| 'Baseline 4:0:0 (grayscale)' | 1.660 ms | 0.0106 ms | 0.0088 ms | -| 'Progressive 4:2:0 Non-Interleaved' | 14.138 ms | 0.2797 ms | 0.3330 ms | +| 'Baseline 4:4:4 Interleaved' | 11.781 ms | 0.0737 ms | 0.0654 ms | +| 'Baseline 4:2:0 Interleaved' | 8.688 ms | 0.0345 ms | 0.0306 ms | +| 'Baseline 4:0:0 (grayscale)' | 1.643 ms | 0.0092 ms | 0.0086 ms | +| 'Progressive 4:2:0 Non-Interleaved' | 13.770 ms | 0.0928 ms | 0.0823 ms | */ From 408462a4ac358abd10ce8d1f2d542bd811915992 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 09:09:18 +0300 Subject: [PATCH 172/511] Added new IDCT implementation --- .../FastFloatingPointDCT.Intrinsic.cs | 237 ++++---- .../Jpeg/Components/FastFloatingPointDCT.cs | 525 ++++++++---------- 2 files changed, 346 insertions(+), 416 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs index ab9462632..94864005e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. #if SUPPORTS_RUNTIME_INTRINSICS -using System.Diagnostics; -using System.Numerics; -using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -12,149 +9,147 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components { internal static partial class FastFloatingPointDCT { -#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings +#pragma warning disable SA1310, SA1311, IDE1006 // naming rule violation warnings private static readonly Vector256 mm256_F_0_7071 = Vector256.Create(0.707106781f); private static readonly Vector256 mm256_F_0_3826 = Vector256.Create(0.382683433f); private static readonly Vector256 mm256_F_0_5411 = Vector256.Create(0.541196100f); private static readonly Vector256 mm256_F_1_3065 = Vector256.Create(1.306562965f); - private static readonly Vector256 mm256_F_1_1758 = Vector256.Create(1.175876f); - private static readonly Vector256 mm256_F_n1_9615 = Vector256.Create(-1.961570560f); - private static readonly Vector256 mm256_F_n0_3901 = Vector256.Create(-0.390180644f); - private static readonly Vector256 mm256_F_n0_8999 = Vector256.Create(-0.899976223f); - private static readonly Vector256 mm256_F_n2_5629 = Vector256.Create(-2.562915447f); - private static readonly Vector256 mm256_F_0_2986 = Vector256.Create(0.298631336f); - private static readonly Vector256 mm256_F_2_0531 = Vector256.Create(2.053119869f); - private static readonly Vector256 mm256_F_3_0727 = Vector256.Create(3.072711026f); - private static readonly Vector256 mm256_F_1_5013 = Vector256.Create(1.501321110f); - private static readonly Vector256 mm256_F_n1_8477 = Vector256.Create(-1.847759065f); - private static readonly Vector256 mm256_F_0_7653 = Vector256.Create(0.765366865f); + private static readonly Vector256 mm256_F_1_4142 = Vector256.Create(1.414213562f); + private static readonly Vector256 mm256_F_1_8477 = Vector256.Create(1.847759065f); + private static readonly Vector256 mm256_F_n1_0823 = Vector256.Create(-1.082392200f); + private static readonly Vector256 mm256_F_n2_6131 = Vector256.Create(-2.613125930f); #pragma warning restore SA1310, SA1311, IDE1006 /// /// Apply floating point FDCT inplace using simd operations. /// - /// Input matrix. - private static void ForwardTransform_Avx(ref Block8x8F block) + /// Input block. + private static void FDCT8x8_Avx(ref Block8x8F block) { DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); // First pass - process rows block.TransposeInplace(); - FDCT8x8_Avx(ref block); + FDCT8x8_1D_Avx(ref block); // Second pass - process columns block.TransposeInplace(); - FDCT8x8_Avx(ref block); + FDCT8x8_1D_Avx(ref block); + + // Applies 1D floating point FDCT inplace + static void FDCT8x8_1D_Avx(ref Block8x8F block) + { + Vector256 tmp0 = Avx.Add(block.V0, block.V7); + Vector256 tmp7 = Avx.Subtract(block.V0, block.V7); + Vector256 tmp1 = Avx.Add(block.V1, block.V6); + Vector256 tmp6 = Avx.Subtract(block.V1, block.V6); + Vector256 tmp2 = Avx.Add(block.V2, block.V5); + Vector256 tmp5 = Avx.Subtract(block.V2, block.V5); + Vector256 tmp3 = Avx.Add(block.V3, block.V4); + Vector256 tmp4 = Avx.Subtract(block.V3, block.V4); + + // Even part + Vector256 tmp10 = Avx.Add(tmp0, tmp3); + Vector256 tmp13 = Avx.Subtract(tmp0, tmp3); + Vector256 tmp11 = Avx.Add(tmp1, tmp2); + Vector256 tmp12 = Avx.Subtract(tmp1, tmp2); + + block.V0 = Avx.Add(tmp10, tmp11); + block.V4 = Avx.Subtract(tmp10, tmp11); + + Vector256 z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071); + block.V2 = Avx.Add(tmp13, z1); + block.V6 = Avx.Subtract(tmp13, z1); + + // Odd part + tmp10 = Avx.Add(tmp4, tmp5); + tmp11 = Avx.Add(tmp5, tmp6); + tmp12 = Avx.Add(tmp6, tmp7); + + Vector256 z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), mm256_F_0_3826); + Vector256 z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_0_5411, tmp10); + Vector256 z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_1_3065, tmp12); + Vector256 z3 = Avx.Multiply(tmp11, mm256_F_0_7071); + + Vector256 z11 = Avx.Add(tmp7, z3); + Vector256 z13 = Avx.Subtract(tmp7, z3); + + block.V5 = Avx.Add(z13, z2); + block.V3 = Avx.Subtract(z13, z2); + block.V1 = Avx.Add(z11, z4); + block.V7 = Avx.Subtract(z11, z4); + } } /// - /// Apply 1D floating point FDCT inplace using AVX operations on 8x8 matrix. + /// Apply floating point IDCT inplace using simd operations. /// - /// - /// Requires Avx support. - /// - /// Input matrix. - public static void FDCT8x8_Avx(ref Block8x8F block) + /// Transposed input block. + private static void IDCT8x8_Avx(ref Block8x8F transposedBlock) { DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation."); - Vector256 tmp0 = Avx.Add(block.V0, block.V7); - Vector256 tmp7 = Avx.Subtract(block.V0, block.V7); - Vector256 tmp1 = Avx.Add(block.V1, block.V6); - Vector256 tmp6 = Avx.Subtract(block.V1, block.V6); - Vector256 tmp2 = Avx.Add(block.V2, block.V5); - Vector256 tmp5 = Avx.Subtract(block.V2, block.V5); - Vector256 tmp3 = Avx.Add(block.V3, block.V4); - Vector256 tmp4 = Avx.Subtract(block.V3, block.V4); - - // Even part - Vector256 tmp10 = Avx.Add(tmp0, tmp3); - Vector256 tmp13 = Avx.Subtract(tmp0, tmp3); - Vector256 tmp11 = Avx.Add(tmp1, tmp2); - Vector256 tmp12 = Avx.Subtract(tmp1, tmp2); - - block.V0 = Avx.Add(tmp10, tmp11); - block.V4 = Avx.Subtract(tmp10, tmp11); - - Vector256 z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071); - block.V2 = Avx.Add(tmp13, z1); - block.V6 = Avx.Subtract(tmp13, z1); - - // Odd part - tmp10 = Avx.Add(tmp4, tmp5); - tmp11 = Avx.Add(tmp5, tmp6); - tmp12 = Avx.Add(tmp6, tmp7); - - Vector256 z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), mm256_F_0_3826); - Vector256 z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_0_5411, tmp10); - Vector256 z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_1_3065, tmp12); - Vector256 z3 = Avx.Multiply(tmp11, mm256_F_0_7071); - - Vector256 z11 = Avx.Add(tmp7, z3); - Vector256 z13 = Avx.Subtract(tmp7, z3); - - block.V5 = Avx.Add(z13, z2); - block.V3 = Avx.Subtract(z13, z2); - block.V1 = Avx.Add(z11, z4); - block.V7 = Avx.Subtract(z11, z4); - } - - /// - /// Combined operation of and - /// using AVX commands. - /// - /// Source - /// Destination - public static void IDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d) - { - Debug.Assert(Avx.IsSupported, "AVX is required to execute this method"); - - Vector256 my1 = s.V1; - Vector256 my7 = s.V7; - Vector256 mz0 = Avx.Add(my1, my7); - - Vector256 my3 = s.V3; - Vector256 mz2 = Avx.Add(my3, my7); - Vector256 my5 = s.V5; - Vector256 mz1 = Avx.Add(my3, my5); - Vector256 mz3 = Avx.Add(my1, my5); - - Vector256 mz4 = Avx.Multiply(Avx.Add(mz0, mz1), mm256_F_1_1758); - - mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz2, mm256_F_n1_9615); - mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz3, mm256_F_n0_3901); - mz0 = Avx.Multiply(mz0, mm256_F_n0_8999); - mz1 = Avx.Multiply(mz1, mm256_F_n2_5629); - - Vector256 mb3 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my7, mm256_F_0_2986), mz2); - Vector256 mb2 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my5, mm256_F_2_0531), mz3); - Vector256 mb1 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my3, mm256_F_3_0727), mz2); - Vector256 mb0 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my1, mm256_F_1_5013), mz3); - - Vector256 my2 = s.V2; - Vector256 my6 = s.V6; - mz4 = Avx.Multiply(Avx.Add(my2, my6), mm256_F_0_5411); - Vector256 my0 = s.V0; - Vector256 my4 = s.V4; - mz0 = Avx.Add(my0, my4); - mz1 = Avx.Subtract(my0, my4); - mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my6, mm256_F_n1_8477); - mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my2, mm256_F_0_7653); - - my0 = Avx.Add(mz0, mz3); - my3 = Avx.Subtract(mz0, mz3); - my1 = Avx.Add(mz1, mz2); - my2 = Avx.Subtract(mz1, mz2); - - d.V0 = Avx.Add(my0, mb0); - d.V7 = Avx.Subtract(my0, mb0); - d.V1 = Avx.Add(my1, mb1); - d.V6 = Avx.Subtract(my1, mb1); - d.V2 = Avx.Add(my2, mb2); - d.V5 = Avx.Subtract(my2, mb2); - d.V3 = Avx.Add(my3, mb3); - d.V4 = Avx.Subtract(my3, mb3); + // First pass - process columns + IDCT8x8_1D_Avx(ref transposedBlock); + + // Second pass - process rows + transposedBlock.TransposeInplace(); + IDCT8x8_1D_Avx(ref transposedBlock); + + // Applies 1D floating point FDCT inplace + static void IDCT8x8_1D_Avx(ref Block8x8F block) + { + // Even part + Vector256 tmp0 = block.V0; + Vector256 tmp1 = block.V2; + Vector256 tmp2 = block.V4; + Vector256 tmp3 = block.V6; + + Vector256 z5 = tmp0; + Vector256 tmp10 = Avx.Add(z5, tmp2); + Vector256 tmp11 = Avx.Subtract(z5, tmp2); + + Vector256 tmp13 = Avx.Add(tmp1, tmp3); + Vector256 tmp12 = SimdUtils.HwIntrinsics.MultiplySubstract(tmp13, Avx.Subtract(tmp1, tmp3), mm256_F_1_4142); + + tmp0 = Avx.Add(tmp10, tmp13); + tmp3 = Avx.Subtract(tmp10, tmp13); + tmp1 = Avx.Add(tmp11, tmp12); + tmp2 = Avx.Subtract(tmp11, tmp12); + + // Odd part + Vector256 tmp4 = block.V1; + Vector256 tmp5 = block.V3; + Vector256 tmp6 = block.V5; + Vector256 tmp7 = block.V7; + + Vector256 z13 = Avx.Add(tmp6, tmp5); + Vector256 z10 = Avx.Subtract(tmp6, tmp5); + Vector256 z11 = Avx.Add(tmp4, tmp7); + Vector256 z12 = Avx.Subtract(tmp4, tmp7); + + tmp7 = Avx.Add(z11, z13); + tmp11 = Avx.Multiply(Avx.Subtract(z11, z13), mm256_F_1_4142); + + z5 = Avx.Multiply(Avx.Add(z10, z12), mm256_F_1_8477); + + tmp10 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z12, mm256_F_n1_0823); + tmp12 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, z10, mm256_F_n2_6131); + + tmp6 = Avx.Subtract(tmp12, tmp7); + tmp5 = Avx.Subtract(tmp11, tmp6); + tmp4 = Avx.Subtract(tmp10, tmp5); + + block.V0 = Avx.Add(tmp0, tmp7); + block.V7 = Avx.Subtract(tmp0, tmp7); + block.V1 = Avx.Add(tmp1, tmp6); + block.V6 = Avx.Subtract(tmp1, tmp6); + block.V2 = Avx.Add(tmp2, tmp5); + block.V5 = Avx.Subtract(tmp2, tmp5); + block.V3 = Avx.Add(tmp3, tmp4); + block.V4 = Avx.Subtract(tmp3, tmp4); + } } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs index 6963c3636..a1c03e65c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -15,102 +15,196 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// internal static partial class FastFloatingPointDCT { -#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore - private const float C_1_175876 = 1.175875602f; - private const float C_1_961571 = -1.961570560f; - private const float C_0_390181 = -0.390180644f; - private const float C_0_899976 = -0.899976223f; - private const float C_2_562915 = -2.562915447f; - private const float C_0_298631 = 0.298631336f; - private const float C_2_053120 = 2.053119869f; - private const float C_3_072711 = 3.072711026f; - private const float C_1_501321 = 1.501321110f; - private const float C_0_541196 = 0.541196100f; - private const float C_1_847759 = -1.847759065f; - private const float C_0_765367 = 0.765366865f; - - private const float C_0_125 = 0.1250f; - -#pragma warning disable SA1311, IDE1006 // naming rules violation warnings - private static readonly Vector4 mm128_F_0_7071 = new Vector4(0.707106781f); - private static readonly Vector4 mm128_F_0_3826 = new Vector4(0.382683433f); - private static readonly Vector4 mm128_F_0_5411 = new Vector4(0.541196100f); - private static readonly Vector4 mm128_F_1_3065 = new Vector4(1.306562965f); -#pragma warning restore SA1311, IDE1006 - -#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore +#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings + private static readonly Vector4 mm128_F_0_7071 = new(0.707106781f); + private static readonly Vector4 mm128_F_0_3826 = new(0.382683433f); + private static readonly Vector4 mm128_F_0_5411 = new(0.541196100f); + private static readonly Vector4 mm128_F_1_3065 = new(1.306562965f); + + private static readonly Vector4 mm128_F_1_4142 = new(1.414213562f); + private static readonly Vector4 mm128_F_1_8477 = new(1.847759065f); + private static readonly Vector4 mm128_F_n1_0823 = new(-1.082392200f); + private static readonly Vector4 mm128_F_n2_6131 = new(-2.613125930f); +#pragma warning restore SA1310, SA1311, IDE1006 /// - /// Gets reciprocal coefficients for jpeg quantization tables calculation. + /// Gets adjustment table for quantization tables. /// /// /// - /// Current FDCT implementation expects its results to be multiplied by - /// a reciprocal quantization table. To get 8x8 reciprocal block values in this - /// table must be divided by quantization table values scaled with quality settings. + /// Current IDCT and FDCT implementations are based on Arai, Agui, + /// and Nakajima's algorithm. Both DCT methods does not + /// produce finished DCT output, final step is fused into the + /// quantization step. Quantization and de-quantization coefficients + /// must be multiplied by these values. /// /// - /// These values were calculates with this formula: - /// - /// value[row * 8 + col] = scalefactor[row] * scalefactor[col] * 8; - /// - /// Where: + /// Given values were generated by formula: /// + /// scalefactor[row] * scalefactor[col], where /// scalefactor[0] = 1 - /// - /// /// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 /// - /// Values are also scaled by 8 so DCT code won't do extra division/multiplication. /// /// - internal static readonly float[] DctReciprocalAdjustmentCoefficients = new float[] + private static readonly float[] AdjustmentCoefficients = new float[] { - 0.125f, 0.09011998f, 0.09567086f, 0.10630376f, 0.125f, 0.15909483f, 0.23096988f, 0.45306373f, - 0.09011998f, 0.064972885f, 0.068974845f, 0.07664074f, 0.09011998f, 0.11470097f, 0.16652f, 0.32664075f, - 0.09567086f, 0.068974845f, 0.07322331f, 0.081361376f, 0.09567086f, 0.121765904f, 0.17677669f, 0.34675997f, - 0.10630376f, 0.07664074f, 0.081361376f, 0.09040392f, 0.10630376f, 0.13529903f, 0.19642374f, 0.38529903f, - 0.125f, 0.09011998f, 0.09567086f, 0.10630376f, 0.125f, 0.15909483f, 0.23096988f, 0.45306373f, - 0.15909483f, 0.11470097f, 0.121765904f, 0.13529903f, 0.15909483f, 0.2024893f, 0.2939689f, 0.5766407f, - 0.23096988f, 0.16652f, 0.17677669f, 0.19642374f, 0.23096988f, 0.2939689f, 0.4267767f, 0.8371526f, - 0.45306373f, 0.32664075f, 0.34675997f, 0.38529903f, 0.45306373f, 0.5766407f, 0.8371526f, 1.642134f, + 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, + 1.3870399f, 1.9238797f, 1.812255f, 1.6309863f, 1.3870399f, 1.0897902f, 0.7506606f, 0.38268346f, + 1.306563f, 1.812255f, 1.707107f, 1.5363555f, 1.306563f, 1.02656f, 0.7071068f, 0.36047992f, + 1.1758755f, 1.6309863f, 1.5363555f, 1.3826833f, 1.1758755f, 0.9238795f, 0.63637924f, 0.32442334f, + 1f, 1.3870399f, 1.306563f, 1.1758755f, 1f, 0.78569496f, 0.5411961f, 0.27589938f, + 0.78569496f, 1.0897902f, 1.02656f, 0.9238795f, 0.78569496f, 0.61731654f, 0.42521507f, 0.21677275f, + 0.5411961f, 0.7506606f, 0.7071068f, 0.63637924f, 0.5411961f, 0.42521507f, 0.29289323f, 0.14931567f, + 0.27589938f, 0.38268346f, 0.36047992f, 0.32442334f, 0.27589938f, 0.21677275f, 0.14931567f, 0.076120466f, }; /// - /// Adjusts given quantization table to be complient with FDCT implementation. + /// Adjusts given quantization table for usage with . + /// + /// Quantization table to adjust. + public static void AdjustToIDCT(ref Block8x8F quantTable) + { + for (int i = 0; i < Block8x8F.Size; i++) + { + quantTable[i] = quantTable[i] * AdjustmentCoefficients[i] * 0.125f; + } + + // Spectral macroblocks are transposed before quantization + // so we must transpose quantization table + quantTable.TransposeInplace(); + } + + /// + /// Adjusts given quantization table for usage with . + /// + /// Quantization table to adjust. + public static void AdjustToFDCT(ref Block8x8F quantTable) + { + for (int i = 0; i < Block8x8F.Size; i++) + { + quantTable[i] = 0.125f / (quantTable[i] * AdjustmentCoefficients[i]); + } + } + + /// + /// Apply 2D floating point IDCT inplace. /// /// - /// See docs for explanation. + /// Input block must be dequantized before this method with table + /// adjusted by . /// - /// Quantization table to adjust. - public static void AdjustToFDCT(ref Block8x8F quantizationtable) + /// Input block. + public static void TransformIDCT(ref Block8x8F block) { - for (int i = 0; i < Block8x8F.Size; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx.IsSupported) + { + IDCT8x8_Avx(ref block); + } + else +#endif { - quantizationtable[i] = DctReciprocalAdjustmentCoefficients[i] / quantizationtable[i]; + IDCT_Vector4(ref block); } } /// - /// Apply 2D floating point FDCT inplace. + /// Apply 2D floating point IDCT inplace. /// - /// Input matrix. + /// + /// Input block must be quantized after this method with table adjusted + /// by . + /// + /// Input block. public static void TransformFDCT(ref Block8x8F block) { #if SUPPORTS_RUNTIME_INTRINSICS if (Avx.IsSupported) { - ForwardTransform_Avx(ref block); + FDCT8x8_Avx(ref block); } else #endif if (Vector.IsHardwareAccelerated) { - ForwardTransform_Vector4(ref block); + FDCT_Vector4(ref block); } else { - ForwardTransform_Scalar(ref block); + FDCT_Scalar(ref block); + } + } + + /// + /// Apply floating point IDCT inplace using API. + /// + /// Input block. + private static void IDCT_Vector4(ref Block8x8F transposedBlock) + { + DebugGuard.IsTrue(Vector.IsHardwareAccelerated, "Scalar implementation should be called for non-accelerated hardware."); + + // First pass - process columns + IDCT8x4_Vector4(ref transposedBlock.V0L); + IDCT8x4_Vector4(ref transposedBlock.V0R); + + // Second pass - process rows + transposedBlock.TransposeInplace(); + IDCT8x4_Vector4(ref transposedBlock.V0L); + IDCT8x4_Vector4(ref transposedBlock.V0R); + + // Applies 1D floating point IDCT inplace on 8x4 part of 8x8 block + static void IDCT8x4_Vector4(ref Vector4 vecRef) + { + // Even part + Vector4 tmp0 = Unsafe.Add(ref vecRef, 0 * 2); + Vector4 tmp1 = Unsafe.Add(ref vecRef, 2 * 2); + Vector4 tmp2 = Unsafe.Add(ref vecRef, 4 * 2); + Vector4 tmp3 = Unsafe.Add(ref vecRef, 6 * 2); + + Vector4 z5 = tmp0; + Vector4 tmp10 = z5 + tmp2; + Vector4 tmp11 = z5 - tmp2; + + Vector4 tmp13 = tmp1 + tmp3; + Vector4 tmp12 = ((tmp1 - tmp3) * mm128_F_1_4142) - tmp13; + + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + + // Odd part + Vector4 tmp4 = Unsafe.Add(ref vecRef, 1 * 2); + Vector4 tmp5 = Unsafe.Add(ref vecRef, 3 * 2); + Vector4 tmp6 = Unsafe.Add(ref vecRef, 5 * 2); + Vector4 tmp7 = Unsafe.Add(ref vecRef, 7 * 2); + + Vector4 z13 = tmp6 + tmp5; + Vector4 z10 = tmp6 - tmp5; + Vector4 z11 = tmp4 + tmp7; + Vector4 z12 = tmp4 - tmp7; + + tmp7 = z11 + z13; + tmp11 = (z11 - z13) * mm128_F_1_4142; + + z5 = (z10 + z12) * mm128_F_1_8477; + + tmp10 = (z12 * mm128_F_n1_0823) + z5; + tmp12 = (z10 * mm128_F_n2_6131) + z5; + + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 - tmp5; + + Unsafe.Add(ref vecRef, 0 * 2) = tmp0 + tmp7; + Unsafe.Add(ref vecRef, 7 * 2) = tmp0 - tmp7; + Unsafe.Add(ref vecRef, 1 * 2) = tmp1 + tmp6; + Unsafe.Add(ref vecRef, 6 * 2) = tmp1 - tmp6; + Unsafe.Add(ref vecRef, 2 * 2) = tmp2 + tmp5; + Unsafe.Add(ref vecRef, 5 * 2) = tmp2 - tmp5; + Unsafe.Add(ref vecRef, 3 * 2) = tmp3 + tmp4; + Unsafe.Add(ref vecRef, 4 * 2) = tmp3 - tmp4; } } @@ -120,8 +214,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Ported from libjpeg-turbo https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jfdctflt.c. /// - /// Input matrix. - private static void ForwardTransform_Scalar(ref Block8x8F block) + /// Input block. + private static void FDCT_Scalar(ref Block8x8F block) { const int dctSize = 8; @@ -130,17 +224,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components float z1, z2, z3, z4, z5, z11, z13; // First pass - process rows - ref float dataRef = ref Unsafe.As(ref block); + ref float blockRef = ref Unsafe.As(ref block); for (int ctr = 7; ctr >= 0; ctr--) { - tmp0 = Unsafe.Add(ref dataRef, 0) + Unsafe.Add(ref dataRef, 7); - tmp7 = Unsafe.Add(ref dataRef, 0) - Unsafe.Add(ref dataRef, 7); - tmp1 = Unsafe.Add(ref dataRef, 1) + Unsafe.Add(ref dataRef, 6); - tmp6 = Unsafe.Add(ref dataRef, 1) - Unsafe.Add(ref dataRef, 6); - tmp2 = Unsafe.Add(ref dataRef, 2) + Unsafe.Add(ref dataRef, 5); - tmp5 = Unsafe.Add(ref dataRef, 2) - Unsafe.Add(ref dataRef, 5); - tmp3 = Unsafe.Add(ref dataRef, 3) + Unsafe.Add(ref dataRef, 4); - tmp4 = Unsafe.Add(ref dataRef, 3) - Unsafe.Add(ref dataRef, 4); + tmp0 = Unsafe.Add(ref blockRef, 0) + Unsafe.Add(ref blockRef, 7); + tmp7 = Unsafe.Add(ref blockRef, 0) - Unsafe.Add(ref blockRef, 7); + tmp1 = Unsafe.Add(ref blockRef, 1) + Unsafe.Add(ref blockRef, 6); + tmp6 = Unsafe.Add(ref blockRef, 1) - Unsafe.Add(ref blockRef, 6); + tmp2 = Unsafe.Add(ref blockRef, 2) + Unsafe.Add(ref blockRef, 5); + tmp5 = Unsafe.Add(ref blockRef, 2) - Unsafe.Add(ref blockRef, 5); + tmp3 = Unsafe.Add(ref blockRef, 3) + Unsafe.Add(ref blockRef, 4); + tmp4 = Unsafe.Add(ref blockRef, 3) - Unsafe.Add(ref blockRef, 4); // Even part tmp10 = tmp0 + tmp3; @@ -148,12 +242,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components tmp11 = tmp1 + tmp2; tmp12 = tmp1 - tmp2; - Unsafe.Add(ref dataRef, 0) = tmp10 + tmp11; - Unsafe.Add(ref dataRef, 4) = tmp10 - tmp11; + Unsafe.Add(ref blockRef, 0) = tmp10 + tmp11; + Unsafe.Add(ref blockRef, 4) = tmp10 - tmp11; z1 = (tmp12 + tmp13) * 0.707106781f; - Unsafe.Add(ref dataRef, 2) = tmp13 + z1; - Unsafe.Add(ref dataRef, 6) = tmp13 - z1; + Unsafe.Add(ref blockRef, 2) = tmp13 + z1; + Unsafe.Add(ref blockRef, 6) = tmp13 - z1; // Odd part tmp10 = tmp4 + tmp5; @@ -168,26 +262,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components z11 = tmp7 + z3; z13 = tmp7 - z3; - Unsafe.Add(ref dataRef, 5) = z13 + z2; - Unsafe.Add(ref dataRef, 3) = z13 - z2; - Unsafe.Add(ref dataRef, 1) = z11 + z4; - Unsafe.Add(ref dataRef, 7) = z11 - z4; + Unsafe.Add(ref blockRef, 5) = z13 + z2; + Unsafe.Add(ref blockRef, 3) = z13 - z2; + Unsafe.Add(ref blockRef, 1) = z11 + z4; + Unsafe.Add(ref blockRef, 7) = z11 - z4; - dataRef = ref Unsafe.Add(ref dataRef, dctSize); + blockRef = ref Unsafe.Add(ref blockRef, dctSize); } // Second pass - process columns - dataRef = ref Unsafe.As(ref block); + blockRef = ref Unsafe.As(ref block); for (int ctr = 7; ctr >= 0; ctr--) { - tmp0 = Unsafe.Add(ref dataRef, dctSize * 0) + Unsafe.Add(ref dataRef, dctSize * 7); - tmp7 = Unsafe.Add(ref dataRef, dctSize * 0) - Unsafe.Add(ref dataRef, dctSize * 7); - tmp1 = Unsafe.Add(ref dataRef, dctSize * 1) + Unsafe.Add(ref dataRef, dctSize * 6); - tmp6 = Unsafe.Add(ref dataRef, dctSize * 1) - Unsafe.Add(ref dataRef, dctSize * 6); - tmp2 = Unsafe.Add(ref dataRef, dctSize * 2) + Unsafe.Add(ref dataRef, dctSize * 5); - tmp5 = Unsafe.Add(ref dataRef, dctSize * 2) - Unsafe.Add(ref dataRef, dctSize * 5); - tmp3 = Unsafe.Add(ref dataRef, dctSize * 3) + Unsafe.Add(ref dataRef, dctSize * 4); - tmp4 = Unsafe.Add(ref dataRef, dctSize * 3) - Unsafe.Add(ref dataRef, dctSize * 4); + tmp0 = Unsafe.Add(ref blockRef, dctSize * 0) + Unsafe.Add(ref blockRef, dctSize * 7); + tmp7 = Unsafe.Add(ref blockRef, dctSize * 0) - Unsafe.Add(ref blockRef, dctSize * 7); + tmp1 = Unsafe.Add(ref blockRef, dctSize * 1) + Unsafe.Add(ref blockRef, dctSize * 6); + tmp6 = Unsafe.Add(ref blockRef, dctSize * 1) - Unsafe.Add(ref blockRef, dctSize * 6); + tmp2 = Unsafe.Add(ref blockRef, dctSize * 2) + Unsafe.Add(ref blockRef, dctSize * 5); + tmp5 = Unsafe.Add(ref blockRef, dctSize * 2) - Unsafe.Add(ref blockRef, dctSize * 5); + tmp3 = Unsafe.Add(ref blockRef, dctSize * 3) + Unsafe.Add(ref blockRef, dctSize * 4); + tmp4 = Unsafe.Add(ref blockRef, dctSize * 3) - Unsafe.Add(ref blockRef, dctSize * 4); // Even part tmp10 = tmp0 + tmp3; @@ -195,12 +289,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components tmp11 = tmp1 + tmp2; tmp12 = tmp1 - tmp2; - Unsafe.Add(ref dataRef, dctSize * 0) = tmp10 + tmp11; - Unsafe.Add(ref dataRef, dctSize * 4) = tmp10 - tmp11; + Unsafe.Add(ref blockRef, dctSize * 0) = tmp10 + tmp11; + Unsafe.Add(ref blockRef, dctSize * 4) = tmp10 - tmp11; z1 = (tmp12 + tmp13) * 0.707106781f; - Unsafe.Add(ref dataRef, dctSize * 2) = tmp13 + z1; - Unsafe.Add(ref dataRef, dctSize * 6) = tmp13 - z1; + Unsafe.Add(ref blockRef, dctSize * 2) = tmp13 + z1; + Unsafe.Add(ref blockRef, dctSize * 6) = tmp13 - z1; // Odd part tmp10 = tmp4 + tmp5; @@ -215,12 +309,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components z11 = tmp7 + z3; z13 = tmp7 - z3; - Unsafe.Add(ref dataRef, dctSize * 5) = z13 + z2; - Unsafe.Add(ref dataRef, dctSize * 3) = z13 - z2; - Unsafe.Add(ref dataRef, dctSize * 1) = z11 + z4; - Unsafe.Add(ref dataRef, dctSize * 7) = z11 - z4; + Unsafe.Add(ref blockRef, dctSize * 5) = z13 + z2; + Unsafe.Add(ref blockRef, dctSize * 3) = z13 - z2; + Unsafe.Add(ref blockRef, dctSize * 1) = z11 + z4; + Unsafe.Add(ref blockRef, dctSize * 7) = z11 - z4; - dataRef = ref Unsafe.Add(ref dataRef, 1); + blockRef = ref Unsafe.Add(ref blockRef, 1); } } @@ -230,11 +324,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// This implementation must be called only if hardware supports 4 /// floating point numbers vector. Otherwise explicit scalar - /// implementation is faster - /// because it does not rely on matrix transposition. + /// implementation is faster + /// because it does not rely on block transposition. /// - /// Input matrix. - private static void ForwardTransform_Vector4(ref Block8x8F block) + /// Input block. + public static void FDCT_Vector4(ref Block8x8F block) { DebugGuard.IsTrue(Vector.IsHardwareAccelerated, "Scalar implementation should be called for non-accelerated hardware."); @@ -247,209 +341,50 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components block.TransposeInplace(); FDCT8x4_Vector4(ref block.V0L); FDCT8x4_Vector4(ref block.V0R); - } - /// - /// Apply 1D floating point FDCT inplace on 8x4 part of 8x8 matrix. - /// - /// - /// Implemented using Vector4 API operations for either scalar or sse hardware implementation. - /// Must be called on both 8x4 matrix parts for the full FDCT transform. - /// - /// Input reference to the first - private static void FDCT8x4_Vector4(ref Vector4 blockRef) - { - Vector4 tmp0 = Unsafe.Add(ref blockRef, 0) + Unsafe.Add(ref blockRef, 14); - Vector4 tmp7 = Unsafe.Add(ref blockRef, 0) - Unsafe.Add(ref blockRef, 14); - Vector4 tmp1 = Unsafe.Add(ref blockRef, 2) + Unsafe.Add(ref blockRef, 12); - Vector4 tmp6 = Unsafe.Add(ref blockRef, 2) - Unsafe.Add(ref blockRef, 12); - Vector4 tmp2 = Unsafe.Add(ref blockRef, 4) + Unsafe.Add(ref blockRef, 10); - Vector4 tmp5 = Unsafe.Add(ref blockRef, 4) - Unsafe.Add(ref blockRef, 10); - Vector4 tmp3 = Unsafe.Add(ref blockRef, 6) + Unsafe.Add(ref blockRef, 8); - Vector4 tmp4 = Unsafe.Add(ref blockRef, 6) - Unsafe.Add(ref blockRef, 8); - - // Even part - Vector4 tmp10 = tmp0 + tmp3; - Vector4 tmp13 = tmp0 - tmp3; - Vector4 tmp11 = tmp1 + tmp2; - Vector4 tmp12 = tmp1 - tmp2; - - Unsafe.Add(ref blockRef, 0) = tmp10 + tmp11; - Unsafe.Add(ref blockRef, 8) = tmp10 - tmp11; - - Vector4 z1 = (tmp12 + tmp13) * mm128_F_0_7071; - Unsafe.Add(ref blockRef, 4) = tmp13 + z1; - Unsafe.Add(ref blockRef, 12) = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - Vector4 z5 = (tmp10 - tmp12) * mm128_F_0_3826; - Vector4 z2 = (mm128_F_0_5411 * tmp10) + z5; - Vector4 z4 = (mm128_F_1_3065 * tmp12) + z5; - Vector4 z3 = tmp11 * mm128_F_0_7071; - - Vector4 z11 = tmp7 + z3; - Vector4 z13 = tmp7 - z3; - - Unsafe.Add(ref blockRef, 10) = z13 + z2; - Unsafe.Add(ref blockRef, 6) = z13 - z2; - Unsafe.Add(ref blockRef, 2) = z11 + z4; - Unsafe.Add(ref blockRef, 14) = z11 - z4; - } + // Applies 1D floating point FDCT inplace on 8x4 part of 8x8 block + static void FDCT8x4_Vector4(ref Vector4 vecRef) + { + Vector4 tmp0 = Unsafe.Add(ref vecRef, 0) + Unsafe.Add(ref vecRef, 14); + Vector4 tmp7 = Unsafe.Add(ref vecRef, 0) - Unsafe.Add(ref vecRef, 14); + Vector4 tmp1 = Unsafe.Add(ref vecRef, 2) + Unsafe.Add(ref vecRef, 12); + Vector4 tmp6 = Unsafe.Add(ref vecRef, 2) - Unsafe.Add(ref vecRef, 12); + Vector4 tmp2 = Unsafe.Add(ref vecRef, 4) + Unsafe.Add(ref vecRef, 10); + Vector4 tmp5 = Unsafe.Add(ref vecRef, 4) - Unsafe.Add(ref vecRef, 10); + Vector4 tmp3 = Unsafe.Add(ref vecRef, 6) + Unsafe.Add(ref vecRef, 8); + Vector4 tmp4 = Unsafe.Add(ref vecRef, 6) - Unsafe.Add(ref vecRef, 8); - /// - /// Apply floating point IDCT inplace. - /// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239. - /// - /// Input matrix. - /// Matrix to store temporal results. - public static void TransformIDCT(ref Block8x8F block, ref Block8x8F temp) - { - block.TransposeInplace(); - IDCT8x8(ref block, ref temp); - temp.TransposeInplace(); - IDCT8x8(ref temp, ref block); + // Even part + Vector4 tmp10 = tmp0 + tmp3; + Vector4 tmp13 = tmp0 - tmp3; + Vector4 tmp11 = tmp1 + tmp2; + Vector4 tmp12 = tmp1 - tmp2; - // TODO: This can be fused into quantization table step - block.MultiplyInPlace(C_0_125); - } + Unsafe.Add(ref vecRef, 0) = tmp10 + tmp11; + Unsafe.Add(ref vecRef, 8) = tmp10 - tmp11; - /// - /// Performs 8x8 matrix Inverse Discrete Cosine Transform - /// - /// Source - /// Destination - private static void IDCT8x8(ref Block8x8F s, ref Block8x8F d) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (Avx.IsSupported) - { - IDCT8x8_Avx(ref s, ref d); - } - else -#endif - { - IDCT8x4_LeftPart(ref s, ref d); - IDCT8x4_RightPart(ref s, ref d); - } - } + Vector4 z1 = (tmp12 + tmp13) * mm128_F_0_7071; + Unsafe.Add(ref vecRef, 4) = tmp13 + z1; + Unsafe.Add(ref vecRef, 12) = tmp13 - z1; - /// - /// Do IDCT internal operations on the left part of the block. Original src: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// - /// The source block - /// Destination block - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) - { - Vector4 my1 = s.V1L; - Vector4 my7 = s.V7L; - Vector4 mz0 = my1 + my7; - - Vector4 my3 = s.V3L; - Vector4 mz2 = my3 + my7; - Vector4 my5 = s.V5L; - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = (mz0 + mz1) * C_1_175876; - - mz2 = (mz2 * C_1_961571) + mz4; - mz3 = (mz3 * C_0_390181) + mz4; - mz0 = mz0 * C_0_899976; - mz1 = mz1 * C_2_562915; - - Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; - Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; - Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; - Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; - - Vector4 my2 = s.V2L; - Vector4 my6 = s.V6L; - mz4 = (my2 + my6) * C_0_541196; - Vector4 my0 = s.V0L; - Vector4 my4 = s.V4L; - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * C_1_847759); - mz3 = mz4 + (my2 * C_0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - - d.V0L = my0 + mb0; - d.V7L = my0 - mb0; - d.V1L = my1 + mb1; - d.V6L = my1 - mb1; - d.V2L = my2 + mb2; - d.V5L = my2 - mb2; - d.V3L = my3 + mb3; - d.V4L = my3 - mb3; - } + // Odd part + tmp10 = tmp4 + tmp5; + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; - /// - /// Do IDCT internal operations on the right part of the block. - /// Original src: - /// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261 - /// - /// The source block - /// The destination block - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) - { - Vector4 my1 = s.V1R; - Vector4 my7 = s.V7R; - Vector4 mz0 = my1 + my7; - - Vector4 my3 = s.V3R; - Vector4 mz2 = my3 + my7; - Vector4 my5 = s.V5R; - Vector4 mz1 = my3 + my5; - Vector4 mz3 = my1 + my5; - - Vector4 mz4 = (mz0 + mz1) * C_1_175876; - - mz2 = (mz2 * C_1_961571) + mz4; - mz3 = (mz3 * C_0_390181) + mz4; - mz0 = mz0 * C_0_899976; - mz1 = mz1 * C_2_562915; - - Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2; - Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3; - Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2; - Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3; - - Vector4 my2 = s.V2R; - Vector4 my6 = s.V6R; - mz4 = (my2 + my6) * C_0_541196; - Vector4 my0 = s.V0R; - Vector4 my4 = s.V4R; - mz0 = my0 + my4; - mz1 = my0 - my4; - - mz2 = mz4 + (my6 * C_1_847759); - mz3 = mz4 + (my2 * C_0_765367); - - my0 = mz0 + mz3; - my3 = mz0 - mz3; - my1 = mz1 + mz2; - my2 = mz1 - mz2; - - d.V0R = my0 + mb0; - d.V7R = my0 - mb0; - d.V1R = my1 + mb1; - d.V6R = my1 - mb1; - d.V2R = my2 + mb2; - d.V5R = my2 - mb2; - d.V3R = my3 + mb3; - d.V4R = my3 - mb3; + Vector4 z5 = (tmp10 - tmp12) * mm128_F_0_3826; + Vector4 z2 = (mm128_F_0_5411 * tmp10) + z5; + Vector4 z4 = (mm128_F_1_3065 * tmp12) + z5; + Vector4 z3 = tmp11 * mm128_F_0_7071; + + Vector4 z11 = tmp7 + z3; + Vector4 z13 = tmp7 - z3; + + Unsafe.Add(ref vecRef, 10) = z13 + z2; + Unsafe.Add(ref vecRef, 6) = z13 - z2; + Unsafe.Add(ref vecRef, 2) = z11 + z4; + Unsafe.Add(ref vecRef, 14) = z11 - z4; + } } } } From 22946934461af71b8e5bd0006608b1b3120f76f5 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 09:09:36 +0300 Subject: [PATCH 173/511] Fused transpose with zig-zag ordering --- .../Components/Decoder/HuffmanScanDecoder.cs | 10 +++++----- src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs | 16 ++++++++++++++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 3 +++ .../ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs | 14 +++++++++++--- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index bc9a53ea0..71f1689f5 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -501,7 +501,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { i += r; s = buffer.Receive(s); - Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[i++]) = (short)s; + Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i++]) = (short)s; } else { @@ -570,7 +570,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder if (s != 0) { s = buffer.Receive(s); - Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[i]) = (short)(s << low); + Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[i]) = (short)(s << low); } else { @@ -646,7 +646,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder do { - ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]); + ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); if (coef != 0) { buffer.CheckBits(); @@ -672,7 +672,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder if ((s != 0) && (k < 64)) { - Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]) = (short)s; + Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]) = (short)s; } } } @@ -681,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { for (; k <= end; k++) { - ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]); + ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.TransposingOrder[k]); if (coef != 0) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index e519a8a1d..f52cb6c10 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -35,5 +35,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63 }; + + public static ReadOnlySpan TransposingOrder => new byte[] + { + 0, 8, 1, 2, 9, 16, 24, 17, + 10, 3, 4, 11, 18, 25, 32, 40, + 33, 26, 19, 12, 5, 6, 13, 20, + 27, 34, 41, 48, 56, 49, 42, 35, + 28, 21, 14, 7, 15, 22, 29, 36, + 43, 50, 57, 58, 51, 44, 37, 30, + 23, 31, 38, 45, 52, 59, 60, 53, + 46, 39, 47, 54, 61, 62, 55, 63, + + // Extra entries for safety in decoder + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63, 63 + }; } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 9a9e5eb79..73763f4ab 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -942,6 +942,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg break; } } + + // Adjusting table for IDCT step during decompression + FastFloatingPointDCT.AdjustToIDCT(ref table); } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs index 39046438a..b67ad85ee 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using SixLabors.ImageSharp.Formats.Jpeg.Components; using Xunit; @@ -9,8 +10,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public class ZigZagTests { - [Fact] - public void ZigZagCanHandleAllPossibleCoefficients() + private static void CanHandleAllPossibleCoefficients(ReadOnlySpan order) { // Mimic the behaviour of the huffman scan decoder using all possible byte values short[] block = new short[64]; @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (s != 0) { i += r; - block[ZigZag.ZigZagOrder[i++]] = (short)s; + block[order[i++]] = (short)s; } else { @@ -40,5 +40,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } + + [Fact] + public static void ZigZagCanHandleAllPossibleCoefficients() => + CanHandleAllPossibleCoefficients(ZigZag.ZigZagOrder); + + [Fact] + public static void TrasposingZigZagCanHandleAllPossibleCoefficients() => + CanHandleAllPossibleCoefficients(ZigZag.TransposingOrder); } } From eaa73732cf8a8a7b64354f7a8f2b81b814e644e0 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 09:10:44 +0300 Subject: [PATCH 174/511] Comments --- src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs index f52cb6c10..ab80b3ae6 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs @@ -36,6 +36,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components 63, 63, 63, 63, 63, 63, 63, 63 }; + /// + /// Gets span of zig-zag with fused transpose step ordering indices. + /// + /// + /// When reading corrupted data, the Huffman decoders could attempt + /// to reference an entry beyond the end of this array (if the decoded + /// zero run length reaches past the end of the block). To prevent + /// wild stores without adding an inner-loop test, we put some extra + /// "63"s after the real entries. This will cause the extra coefficient + /// to be stored in location 63 of the block, not somewhere random. + /// The worst case would be a run-length of 15, which means we need 16 + /// fake entries. + /// public static ReadOnlySpan TransposingOrder => new byte[] { 0, 8, 1, 2, 9, 16, 24, 17, From 7c1f05b0a8aebad8a5515cb8c5c42780b0fc9a22 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 11:28:02 +0100 Subject: [PATCH 175/511] Avoid allocating too many CostInterval objects --- .../Formats/Webp/Lossless/CostManager.cs | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 94c7bd847..213971764 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs @@ -14,6 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { private CostInterval head; + private const int FreeIntervalsStartCount = 25; + + private readonly Stack freeIntervals = new(FreeIntervalsStartCount); + public CostManager(ushort[] distArray, int pixCount, CostModel costModel) { int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; @@ -24,6 +28,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.DistArray = distArray; this.Count = 0; + for (int i = 0; i < FreeIntervalsStartCount; i++) + { + this.freeIntervals.Push(new CostInterval()); + } + // Fill in the cost cache. this.CacheIntervalsSize++; this.CostCache.Add(costModel.GetLengthCost(0)); @@ -201,10 +210,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.InsertInterval(interval, interval.Cost, interval.Index, end, endOriginal); break; } - else - { - interval.End = start; - } + + interval.End = start; } } @@ -226,6 +233,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.ConnectIntervals(interval.Previous, interval.Next); this.Count--; + + interval.Next = null; + interval.Previous = null; + this.freeIntervals.Push(interval); } private void InsertInterval(CostInterval intervalIn, float cost, int position, int start, int end) @@ -236,13 +247,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // TODO: should we use COST_CACHE_INTERVAL_SIZE_MAX? - var intervalNew = new CostInterval() + CostInterval intervalNew; + if (this.freeIntervals.Count > 0) { - Cost = cost, - Start = start, - End = end, - Index = position - }; + intervalNew = this.freeIntervals.Pop(); + intervalNew.Cost = cost; + intervalNew.Start = start; + intervalNew.End = end; + intervalNew.Index = position; + } + else + { + intervalNew = new CostInterval() { Cost = cost, Start = start, End = end, Index = position }; + } this.PositionOrphanInterval(intervalNew, intervalIn); this.Count++; From b8925e1aaf231c416a3b0e77d2e66f46c45797f8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 12:33:24 +0100 Subject: [PATCH 176/511] CostsManager now uses MemoryAllocator --- .../Webp/Lossless/BackwardReferenceEncoder.cs | 32 ++++++----- .../Formats/Webp/Lossless/CostManager.cs | 57 ++++++++++++++----- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index 93f6372c6..82aa3ff7b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.Collections.Generic; using SixLabors.ImageSharp.Memory; @@ -102,7 +103,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if ((lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard || lz77TypeBest == (int)Vp8LLz77Type.Lz77Box) && quality >= 25) { Vp8LHashChain hashChainTmp = lz77TypeBest == (int)Vp8LLz77Type.Lz77Standard ? hashChain : hashChainBox; - BackwardReferencesTraceBackwards(width, height, bgra, cacheBits, hashChainTmp, best, worst); + BackwardReferencesTraceBackwards(width, height, memoryAllocator, bgra, cacheBits, hashChainTmp, best, worst); var histo = new Vp8LHistogram(worst, cacheBits); double bitCostTrace = histo.EstimateBits(stats, bitsEntropy); if (bitCostTrace < bitCostBest) @@ -236,6 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static void BackwardReferencesTraceBackwards( int xSize, int ySize, + MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, @@ -243,22 +245,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vp8LBackwardRefs refsDst) { int distArraySize = xSize * ySize; - ushort[] distArray = new ushort[distArraySize]; + using IMemoryOwner distArrayBuffer = memoryAllocator.Allocate(distArraySize); + Span distArray = distArrayBuffer.GetSpan(); - BackwardReferencesHashChainDistanceOnly(xSize, ySize, bgra, cacheBits, hashChain, refsSrc, distArray); + BackwardReferencesHashChainDistanceOnly(xSize, ySize, memoryAllocator, bgra, cacheBits, hashChain, refsSrc, distArrayBuffer); int chosenPathSize = TraceBackwards(distArray, distArraySize); - Span chosenPath = distArray.AsSpan(distArraySize - chosenPathSize); + Span chosenPath = distArray.Slice(distArraySize - chosenPathSize); BackwardReferencesHashChainFollowChosenPath(bgra, cacheBits, chosenPath, chosenPathSize, hashChain, refsDst); } private static void BackwardReferencesHashChainDistanceOnly( int xSize, int ySize, + MemoryAllocator memoryAllocator, ReadOnlySpan bgra, int cacheBits, Vp8LHashChain hashChain, Vp8LBackwardRefs refs, - ushort[] distArray) + IMemoryOwner distArrayBuffer) { int pixCount = xSize * ySize; bool useColorCache = cacheBits > 0; @@ -277,22 +281,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } costModel.Build(xSize, cacheBits, refs); - var costManager = new CostManager(distArray, pixCount, costModel); + var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); + Span costManagerCosts = costManager.Costs.GetSpan(); + Span distArray = distArrayBuffer.GetSpan(); // We loop one pixel at a time, but store all currently best points to non-processed locations from this point. distArray[0] = 0; // Add first pixel as literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManager.Costs, distArray); + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, 0, useColorCache, 0.0f, costManagerCosts, distArray); for (int i = 1; i < pixCount; i++) { - float prevCost = costManager.Costs[i - 1]; + float prevCost = costManagerCosts[i - 1]; int offset = hashChain.FindOffset(i); int len = hashChain.FindLength(i); // Try adding the pixel as a literal. - AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManager.Costs, distArray); + AddSingleLiteralWithCostModel(bgra, colorCache, costModel, i, useColorCache, prevCost, costManagerCosts, distArray); // If we are dealing with a non-literal. if (len >= 2) @@ -336,7 +342,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless costManager.UpdateCostAtIndex(j - 1, false); costManager.UpdateCostAtIndex(j, false); - costManager.PushInterval(costManager.Costs[j - 1] + offsetCost, j, lenJ); + costManager.PushInterval(costManagerCosts[j - 1] + offsetCost, j, lenJ); reach = j + lenJ - 1; } } @@ -348,7 +354,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - private static int TraceBackwards(ushort[] distArray, int distArraySize) + private static int TraceBackwards(Span distArray, int distArraySize) { int chosenPathSize = 0; int pathPos = distArraySize; @@ -428,8 +434,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int idx, bool useColorCache, float prevCost, - float[] cost, - ushort[] distArray) + Span cost, + Span distArray) { double costVal = prevCost; uint color = bgra[idx]; diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 213971764..3ee102138 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs @@ -1,7 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers; using System.Collections.Generic; +using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { @@ -10,21 +13,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// It caches the different CostCacheInterval, caches the different /// GetLengthCost(costModel, k) in costCache and the CostInterval's. /// - internal class CostManager + internal class CostManager : IDisposable { + private bool disposed; + private CostInterval head; private const int FreeIntervalsStartCount = 25; private readonly Stack freeIntervals = new(FreeIntervalsStartCount); - public CostManager(ushort[] distArray, int pixCount, CostModel costModel) + public CostManager(MemoryAllocator memoryAllocator, IMemoryOwner distArray, int pixCount, CostModel costModel) { int costCacheSize = pixCount > BackwardReferenceEncoder.MaxLength ? BackwardReferenceEncoder.MaxLength : pixCount; this.CacheIntervals = new List(); this.CostCache = new List(); - this.Costs = new float[pixCount]; + this.Costs = memoryAllocator.Allocate(pixCount); this.DistArray = distArray; this.Count = 0; @@ -73,10 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } // Set the initial costs high for every pixel as we will keep the minimum. - for (int i = 0; i < pixCount; i++) - { - this.Costs[i] = 1e38f; - } + this.Costs.GetSpan().Fill(1e38f); } /// @@ -91,9 +93,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public int CacheIntervalsSize { get; } - public float[] Costs { get; } + public IMemoryOwner Costs { get; } - public ushort[] DistArray { get; } + public IMemoryOwner DistArray { get; } public List CacheIntervals { get; } @@ -137,6 +139,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // interval logic, just serialize it right away. This constant is empirical. int skipDistance = 10; + Span costs = this.Costs.GetSpan(); + Span distArray = this.DistArray.GetSpan(); if (len < skipDistance) { for (int j = position; j < position + len; j++) @@ -144,10 +148,10 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int k = j - position; float costTmp = (float)(distanceCost + this.CostCache[k]); - if (this.Costs[j] > costTmp) + if (costs[j] > costTmp) { - this.Costs[j] = costTmp; - this.DistArray[j] = (ushort)(k + 1); + costs[j] = costTmp; + distArray[j] = (ushort)(k + 1); } } @@ -314,12 +318,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// private void UpdateCost(int i, int position, float cost) { + Span costs = this.Costs.GetSpan(); + Span distArray = this.DistArray.GetSpan(); int k = i - position; - if (this.Costs[i] > cost) + if (costs[i] > cost) + { + costs[i] = cost; + distArray[i] = (ushort)(k + 1); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) { - this.Costs[i] = cost; - this.DistArray[i] = (ushort)(k + 1); + if (disposing) + { + this.Costs.Dispose(); + } + + this.disposed = true; } } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } } } From e03709d7b6d80ea0a1681cecfe92842a9ac39db6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 13:18:43 +0100 Subject: [PATCH 177/511] Make StorageOrder bytes a ReadOnlySpan --- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 48f7d0e2b..8ca80d5ab 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -137,6 +137,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } + // RFC 1951 will calm you down if you are worried about this funny sequence. + // This sequence is tuned from that, but more weighted for lower symbol count, + // and more spiking histograms. + // This uses C#'s compiler optimization to refer to assembly's static data directly. + private static ReadOnlySpan StorageOrder => new byte[] { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + // This uses C#'s compiler optimization to refer to assembly's static data directly. private static ReadOnlySpan Order => new byte[] { 1, 2, 0, 3 }; @@ -942,16 +948,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private void StoreHuffmanTreeOfHuffmanTreeToBitMask(byte[] codeLengthBitDepth) { - // RFC 1951 will calm you down if you are worried about this funny sequence. - // This sequence is tuned from that, but more weighted for lower symbol count, - // and more spiking histograms. - byte[] storageOrder = { 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - // Throw away trailing zeros: int codesToStore = WebpConstants.CodeLengthCodes; for (; codesToStore > 4; codesToStore--) { - if (codeLengthBitDepth[storageOrder[codesToStore - 1]] != 0) + if (codeLengthBitDepth[StorageOrder[codesToStore - 1]] != 0) { break; } @@ -960,7 +961,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.bitWriter.PutBits((uint)codesToStore - 4, 4); for (int i = 0; i < codesToStore; i++) { - this.bitWriter.PutBits(codeLengthBitDepth[storageOrder[i]], 3); + this.bitWriter.PutBits(codeLengthBitDepth[StorageOrder[i]], 3); } } From 92ac52221cf2a34b20b5c0aaca8124b522dba449 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 13:58:34 +0100 Subject: [PATCH 178/511] Remove not needed DeepClone --- src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs | 4 +--- src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs index 0376311ed..07fec7f99 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanTree.cs @@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Represents the Huffman tree. /// [DebuggerDisplay("TotalCount = {TotalCount}, Value = {Value}, Left = {PoolIndexLeft}, Right = {PoolIndexRight}")] - internal struct HuffmanTree : IDeepCloneable + internal struct HuffmanTree { /// /// Initializes a new instance of the struct. @@ -57,7 +57,5 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return t1.Value < t2.Value ? -1 : 1; } - - public IDeepCloneable DeepClone() => new HuffmanTree(this); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs index 66170b85f..56f2ee9ce 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/HuffmanUtils.cs @@ -219,8 +219,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless while (treeSize > 1) { // Finish when we have only one root. - treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 1].DeepClone(); - treePool[treePoolSize++] = (HuffmanTree)tree[treeSize - 2].DeepClone(); + treePool[treePoolSize++] = tree[treeSize - 1]; + treePool[treePoolSize++] = tree[treeSize - 2]; int count = treePool[treePoolSize - 1].TotalCount + treePool[treePoolSize - 2].TotalCount; treeSize -= 2; @@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int startIdx = endIdx + num - 1; for (int i = startIdx; i >= endIdx; i--) { - tree[i] = (HuffmanTree)tree[i - 1].DeepClone(); + tree[i] = tree[i - 1]; } tree[k].TotalCount = count; From d0382bbb03c1c4f7c1447bcd46d8378d85d7b314 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 14:51:12 +0100 Subject: [PATCH 179/511] Change PixOrCopyMode, HistoIx and EntropyIx enums to be a byte --- src/ImageSharp/Formats/Webp/EntropyIx.cs | 2 +- src/ImageSharp/Formats/Webp/HistoIx.cs | 2 +- src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/EntropyIx.cs b/src/ImageSharp/Formats/Webp/EntropyIx.cs index c72ddeb42..98e8b7e16 100644 --- a/src/ImageSharp/Formats/Webp/EntropyIx.cs +++ b/src/ImageSharp/Formats/Webp/EntropyIx.cs @@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// /// These five modes are evaluated and their respective entropy is computed. /// - internal enum EntropyIx + internal enum EntropyIx : byte { Direct = 0, diff --git a/src/ImageSharp/Formats/Webp/HistoIx.cs b/src/ImageSharp/Formats/Webp/HistoIx.cs index 68b00394b..83522f9da 100644 --- a/src/ImageSharp/Formats/Webp/HistoIx.cs +++ b/src/ImageSharp/Formats/Webp/HistoIx.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Webp { - internal enum HistoIx + internal enum HistoIx : byte { HistoAlpha = 0, diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs index 0d7023ffc..26099b902 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopyMode.cs @@ -3,7 +3,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal enum PixOrCopyMode + internal enum PixOrCopyMode : byte { Literal, From e011450a90380015893f12f1f5d27aecd8e021ee Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 15:15:24 +0100 Subject: [PATCH 180/511] Dispose cost manager and hashChainBox --- .../Formats/Webp/Lossless/BackwardReferenceEncoder.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs index 82aa3ff7b..c394a8caa 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/BackwardReferenceEncoder.cs @@ -114,6 +114,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless BackwardReferences2DLocality(width, best); + hashChainBox?.Dispose(); + return best; } @@ -281,7 +283,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } costModel.Build(xSize, cacheBits, refs); - var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); + using var costManager = new CostManager(memoryAllocator, distArrayBuffer, pixCount, costModel); Span costManagerCosts = costManager.Costs.GetSpan(); Span distArray = distArrayBuffer.GetSpan(); From fae8f0dc319c4c46fa73e7412e9b5db6b58a9e93 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 16:17:29 +0100 Subject: [PATCH 181/511] Initialize backward refs with the pixel count --- src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs | 2 +- src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs index 502728b15..fca4ec59f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LBackwardRefs.cs @@ -7,7 +7,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { internal class Vp8LBackwardRefs { - public Vp8LBackwardRefs() => this.Refs = new List(); + public Vp8LBackwardRefs(int pixels) => this.Refs = new List(pixels); /// /// Gets or sets the common block-size. diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 8ca80d5ab..adabd0ac3 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int refsBlockSize = ((pixelCount - 1) / MaxRefsBlockPerImage) + 1; for (int i = 0; i < this.Refs.Length; i++) { - this.Refs[i] = new Vp8LBackwardRefs + this.Refs[i] = new Vp8LBackwardRefs(pixelCount) { BlockSize = refsBlockSize < MinBlockSize ? MinBlockSize : refsBlockSize }; From af0b8ac3dd10e7dadd88a4baa84ab298ef2c4b59 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 21:46:11 +0300 Subject: [PATCH 182/511] Fixed compilation errors, fixed tests --- .../Decoder/JpegBlockPostProcessor.cs | 8 +- .../ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 211 ++++++++---------- 2 files changed, 92 insertions(+), 127 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs index 085cd4a29..15f212b40 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs @@ -18,11 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public Block8x8F SourceBlock; - /// - /// Temporal block to store intermediate computation results. - /// - public Block8x8F WorkspaceBlock; - /// /// The quantization table as . /// @@ -45,7 +40,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.subSamplingDivisors = component.SubSamplingDivisors; this.SourceBlock = default; - this.WorkspaceBlock = default; } /// @@ -71,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // Dequantize: block.MultiplyInPlace(ref this.DequantiazationTable); - FastFloatingPointDCT.TransformIDCT(ref block, ref this.WorkspaceBlock); + 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. diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 0a49d20cd..3a6eb4f8b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -#if SUPPORTS_RUNTIME_INTRINSICS -using System.Runtime.Intrinsics.X86; -#endif using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -17,6 +14,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [Trait("Format", "Jpg")] public static class DCTTests { + private const int MaxAllowedValue = short.MaxValue; + private const int MinAllowedValue = short.MinValue; + + internal static Block8x8F CreateBlockFromScalar(float value) + { + Block8x8F result = default; + for (int i = 0; i < Block8x8F.Size; i++) + { + result[i] = value; + } + + return result; + } + public class FastFloatingPoint : JpegFixture { public FastFloatingPoint(ITestOutputHelper output) @@ -24,130 +35,77 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { } - // Reference tests [Theory] [InlineData(1)] [InlineData(2)] [InlineData(3)] public void LLM_TransformIDCT_CompareToNonOptimized(int seed) { - float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); + float[] sourceArray = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); var srcBlock = Block8x8F.Load(sourceArray); + // reference Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref srcBlock); - var temp = default(Block8x8F); - FastFloatingPointDCT.TransformIDCT(ref srcBlock, ref temp); - - this.CompareBlocks(expected, srcBlock, 1f); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - public void LLM_TransformIDCT_CompareToAccurate(int seed) - { - float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); + // testee + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + Block8x8F dequantMatrix = CreateBlockFromScalar(1); - var srcBlock = Block8x8F.Load(sourceArray); + // Dequantization using unit matrix - no values are upscaled + // as quant matrix is all 1's + // This step is needed to apply adjusting multipliers to the input block + FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + srcBlock.MultiplyInPlace(ref dequantMatrix); - Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref srcBlock); + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); - var temp = default(Block8x8F); - FastFloatingPointDCT.TransformIDCT(ref srcBlock, ref temp); + // IDCT calculation + FastFloatingPointDCT.TransformIDCT(ref srcBlock); this.CompareBlocks(expected, srcBlock, 1f); } - // Inverse transform - [Theory] - [InlineData(1)] - [InlineData(2)] - public void IDCT8x4_LeftPart(int seed) - { - Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = default(Block8x8F); - srcBlock.LoadFrom(src); - - var destBlock = default(Block8x8F); - - var expectedDest = new float[64]; - - // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src, expectedDest); - - // testee - FastFloatingPointDCT.IDCT8x4_LeftPart(ref srcBlock, ref destBlock); - - var actualDest = new float[64]; - destBlock.ScaledCopyTo(actualDest); - - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); - } - [Theory] [InlineData(1)] [InlineData(2)] - public void IDCT8x4_RightPart(int seed) + [InlineData(3)] + public void LLM_TransformIDCT_CompareToAccurate(int seed) { - Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - var srcBlock = default(Block8x8F); - srcBlock.LoadFrom(src); + float[] sourceArray = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); - var destBlock = default(Block8x8F); - - var expectedDest = new float[64]; + var srcBlock = Block8x8F.Load(sourceArray); // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); + Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref srcBlock); // testee - FastFloatingPointDCT.IDCT8x4_RightPart(ref srcBlock, ref destBlock); - - var actualDest = new float[64]; - destBlock.ScaledCopyTo(actualDest); - - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); - } - - [Theory] - [InlineData(1)] - [InlineData(2)] - public void IDCT8x8_Avx(int seed) - { -#if SUPPORTS_RUNTIME_INTRINSICS - if (!Avx.IsSupported) - { - this.Output.WriteLine("No AVX present, skipping test!"); - return; - } - - Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); - Block8x8F srcBlock = default; - srcBlock.LoadFrom(src); + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + Block8x8F dequantMatrix = CreateBlockFromScalar(1); - Block8x8F destBlock = default; + // Dequantization using unit matrix - no values are upscaled + // as quant matrix is all 1's + // This step is needed to apply adjusting multipliers to the input block + FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + srcBlock.MultiplyInPlace(ref dequantMatrix); - float[] expectedDest = new float[64]; + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); - // reference, left part - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src, expectedDest); + // IDCT calculation + FastFloatingPointDCT.TransformIDCT(ref srcBlock); - // reference, right part - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); - - // testee, whole 8x8 - FastFloatingPointDCT.IDCT8x8_Avx(ref srcBlock, ref destBlock); - - float[] actualDest = new float[64]; - destBlock.ScaledCopyTo(actualDest); - - Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); -#endif + this.CompareBlocks(expected, srcBlock, 1f); } + // Inverse transform + // This test covers entire IDCT conversion chain + // This test checks all hardware implementations [Theory] [InlineData(1)] [InlineData(2)] @@ -157,41 +115,53 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { int seed = FeatureTestRunner.Deserialize(serialized); - Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + Span src = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); var srcBlock = default(Block8x8F); srcBlock.LoadFrom(src); - var expectedDest = new float[64]; - var temp1 = new float[64]; - var temp2 = default(Block8x8F); + float[] expectedDest = new float[64]; + float[] temp = new float[64]; // reference - ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp1); + ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp); // testee - FastFloatingPointDCT.TransformIDCT(ref srcBlock, ref temp2); + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // before applying IDCT + Block8x8F dequantMatrix = CreateBlockFromScalar(1); + + // Dequantization using unit matrix - no values are upscaled + // as quant matrix is all 1's + // This step is needed to apply adjusting multipliers to the input block + FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); + srcBlock.MultiplyInPlace(ref dequantMatrix); + + // IDCT implementation tranforms blocks after transposition + srcBlock.TransposeInplace(); - var actualDest = new float[64]; - srcBlock.ScaledCopyTo(actualDest); + // IDCT calculation + FastFloatingPointDCT.TransformIDCT(ref srcBlock); + + float[] actualDest = srcBlock.ToArray(); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); } - // 3 paths: + // 4 paths: // 1. AllowAll - call avx/fma implementation - // 2. DisableFMA - call avx implementation without fma acceleration - // 3. DisableAvx - call fallback code of Vector4 implementation - // - // DisableSSE isn't needed because fallback Vector4 code will compile to either sse or fallback code with same result + // 2. DisableFMA - call avx without fma implementation + // 3. DisableAvx - call sse Vector4 implementation + // 4. DisableHWIntrinsic - call scalar fallback implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX); + HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); } // Forward transform - // This test covers entire FDCT conversions chain - // This test checks all implementations: intrinsic and scalar fallback + // This test covers entire FDCT conversion chain + // This test checks all hardware implementations [Theory] [InlineData(1)] [InlineData(2)] @@ -201,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { int seed = FeatureTestRunner.Deserialize(serialized); - Span src = Create8x8RoundedRandomFloatData(-200, 200, seed); + Span src = Create8x8RoundedRandomFloatData(MinAllowedValue, MaxAllowedValue, seed); var block = default(Block8x8F); block.LoadFrom(src); @@ -212,23 +182,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); // testee - // Part of the FDCT calculations is fused into the quantization step - // We must multiply transformed block with reciprocal values from FastFloatingPointDCT.ANN_DCT_reciprocalAdjustmen FastFloatingPointDCT.TransformFDCT(ref block); - for (int i = 0; i < 64; i++) - { - block[i] = block[i] * FastFloatingPointDCT.DctReciprocalAdjustmentCoefficients[i]; - } + + // Part of the IDCT calculations is fused into the quantization step + // We must multiply input block with adjusted no-quantization matrix + // after applying FDCT + Block8x8F quantMatrix = CreateBlockFromScalar(1); + FastFloatingPointDCT.AdjustToFDCT(ref quantMatrix); + block.MultiplyInPlace(ref quantMatrix); float[] actualDest = block.ToArray(); Assert.Equal(expectedDest, actualDest, new ApproximateFloatComparer(1f)); } - // 3 paths: + // 4 paths: // 1. AllowAll - call avx/fma implementation - // 2. DisableFMA - call avx implementation without fma acceleration - // 3. DisableAvx - call sse implementation + // 2. DisableFMA - call avx without fma implementation + // 3. DisableAvx - call sse Vector4 implementation // 4. DisableHWIntrinsic - call scalar fallback implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, From 1631e9d0c7c8be82c99525fcc308d85628d3ff47 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 21 Nov 2021 22:03:37 +0300 Subject: [PATCH 183/511] Updated benchmark results --- tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index cb89f9082..102ba8dd3 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -66,16 +66,17 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg /* -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1288 (20H2/October2020Update) +BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19042.1348 (20H2/October2020Update) Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET SDK=6.0.100-preview.3.21202.5 [Host] : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT DefaultJob : .NET Core 3.1.18 (CoreCLR 4.700.21.35901, CoreFX 4.700.21.36305), X64 RyuJIT + | Method | Mean | Error | StdDev | |------------------------------------ |----------:|----------:|----------:| -| 'Baseline 4:4:4 Interleaved' | 11.781 ms | 0.0737 ms | 0.0654 ms | -| 'Baseline 4:2:0 Interleaved' | 8.688 ms | 0.0345 ms | 0.0306 ms | -| 'Baseline 4:0:0 (grayscale)' | 1.643 ms | 0.0092 ms | 0.0086 ms | -| 'Progressive 4:2:0 Non-Interleaved' | 13.770 ms | 0.0928 ms | 0.0823 ms | +| 'Baseline 4:4:4 Interleaved' | 11.532 ms | 0.0530 ms | 0.0496 ms | +| 'Baseline 4:2:0 Interleaved' | 8.458 ms | 0.0289 ms | 0.0256 ms | +| 'Baseline 4:0:0 (grayscale)' | 1.550 ms | 0.0050 ms | 0.0044 ms | +| 'Progressive 4:2:0 Non-Interleaved' | 13.220 ms | 0.0449 ms | 0.0398 ms | */ From bab85d4372ee7cc784acc7d743ffd2c6886ea460 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 21 Nov 2021 22:17:12 +0100 Subject: [PATCH 184/511] Add SSE version of CombinedShannonEntropy --- .../Formats/Webp/Lossless/LosslessUtils.cs | 154 ++++++++++++++++-- 1 file changed, 137 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 471c083cd..52453c77f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -759,28 +759,147 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// Shanon entropy. public static float CombinedShannonEntropy(Span x, Span y) { - double retVal = 0.0d; - uint sumX = 0, sumXY = 0; - for (int i = 0; i < 256; i++) - { - uint xi = (uint)x[i]; - if (xi != 0) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse41.IsSupported) + { + double retVal = 0.0d; + Span tmp = stackalloc int[4]; + ref int xRef = ref MemoryMarshal.GetReference(x); + ref int yRef = ref MemoryMarshal.GetReference(y); + Vector128 sumXY128 = Vector128.Zero; + Vector128 sumX128 = Vector128.Zero; + ref int tmpRef = ref MemoryMarshal.GetReference(tmp); + for (int i = 0; i < 256; i += 4) { - uint xy = xi + (uint)y[i]; - sumX += xi; - retVal -= FastSLog2(xi); - sumXY += xy; - retVal -= FastSLog2(xy); + Vector128 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); + Vector128 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); + + // Check if any X is non-zero: this actually provides a speedup as X is usually sparse. + if (Sse2.MoveMask(Sse2.CompareEqual(xVec, Vector128.Zero).AsByte()) != 0xFFFF) + { + Vector128 xy128 = Sse2.Add(xVec, yVec); + sumXY128 = Sse2.Add(sumXY128, xy128); + sumX128 = Sse2.Add(sumX128, xVec); + + // Analyze the different X + Y. + Unsafe.As>(ref tmpRef) = xy128; + if (tmp[0] != 0) + { + retVal -= FastSLog2((uint)tmp[0]); + if (x[i + 0] != 0) + { + retVal -= FastSLog2((uint)x[i + 0]); + } + } + + if (tmp[1] != 0) + { + retVal -= FastSLog2((uint)tmp[1]); + if (x[i + 1] != 0) + { + retVal -= FastSLog2((uint)x[i + 1]); + } + } + + if (tmp[2] != 0) + { + retVal -= FastSLog2((uint)tmp[2]); + if (x[i + 2] != 0) + { + retVal -= FastSLog2((uint)x[i + 2]); + } + } + + if (tmp[3] != 0) + { + retVal -= FastSLog2((uint)tmp[3]); + if (x[i + 3] != 0) + { + retVal -= FastSLog2((uint)x[i + 3]); + } + } + } + else + { + // X is fully 0, so only deal with Y. + sumXY128 = Sse2.Add(sumXY128, yVec); + + if (y[i] != 0) + { + retVal -= FastSLog2((uint)y[i]); + } + + if (y[i + 1] != 0) + { + retVal -= FastSLog2((uint)y[i + 1]); + } + + if (y[i + 2] != 0) + { + retVal -= FastSLog2((uint)y[i + 2]); + } + + if (y[i + 3] != 0) + { + retVal -= FastSLog2((uint)y[i + 3]); + } + } } - else if (y[i] != 0) + + // Sum up sumX_128 to get sumX and sum up sumXY_128 to get sumXY. + // note: not using here Numerics.ReduceSum, because grouping the same methods together should be slightly faster. + Vector128 haddSumX = Ssse3.HorizontalAdd(sumX128, sumX128); + Vector128 haddSumXY = Ssse3.HorizontalAdd(sumXY128, sumXY128); + Vector128 swappedSumX = Sse2.Shuffle(haddSumX, 0x1); + Vector128 swappedSumXY = Sse2.Shuffle(haddSumXY, 0x1); + Vector128 tmpSumX = Sse2.Add(haddSumX, swappedSumX); + Vector128 tmpSumXY = Sse2.Add(haddSumXY, swappedSumXY); + int sumX = Sse2.ConvertToInt32(tmpSumX); + int sumXY = Sse2.ConvertToInt32(tmpSumXY); + + retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY); + + return (float)retVal; + } + else +#endif + { + double retVal = 0.0d; + uint sumX = 0, sumXY = 0; + for (int i = 0; i < 256; i++) { - sumXY += (uint)y[i]; - retVal -= FastSLog2((uint)y[i]); + uint xi = (uint)x[i]; + if (xi != 0) + { + uint xy = xi + (uint)y[i]; + sumX += xi; + retVal -= FastSLog2(xi); + sumXY += xy; + retVal -= FastSLog2(xy); + } + else if (y[i] != 0) + { + sumXY += (uint)y[i]; + retVal -= FastSLog2((uint)y[i]); + } } + + retVal += FastSLog2(sumX) + FastSLog2(sumXY); + return (float)retVal; } + } - retVal += FastSLog2(sumX) + FastSLog2(sumXY); - return (float)retVal; + [MethodImpl(InliningOptions.ShortMethod)] + private static void AnalyzeXy(Span tmp, Span x, int i, int pos, ref double retVal) + { + if (tmp[pos] != 0) + { + retVal -= FastSLog2((uint)tmp[pos]); + if (x[i + pos] != 0) + { + retVal -= FastSLog2((uint)x[i + pos]); + } + } } [MethodImpl(InliningOptions.ShortMethod)] @@ -836,6 +955,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static float FastSLog2Slow(uint v) { DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + if (v < ApproxLogWithCorrectionMax) { int logCnt = 0; @@ -865,7 +985,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private static float FastLog2Slow(uint v) { - Guard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); + DebugGuard.MustBeGreaterThanOrEqualTo(v, LogLookupIdxMax, nameof(v)); if (v < ApproxLogWithCorrectionMax) { From cc430cc84626edf63c187f97fe37f6d4ad2ca0da Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 22 Nov 2021 12:56:45 +0100 Subject: [PATCH 185/511] Avoid bounds checks --- .../Formats/Webp/Lossless/LosslessUtils.cs | 61 ++++++++----------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 52453c77f..0f24e8e8f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -783,39 +783,39 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // Analyze the different X + Y. Unsafe.As>(ref tmpRef) = xy128; - if (tmp[0] != 0) + if (tmpRef != 0) { - retVal -= FastSLog2((uint)tmp[0]); - if (x[i + 0] != 0) + retVal -= FastSLog2((uint)tmpRef); + if (Unsafe.Add(ref xRef, i) != 0) { - retVal -= FastSLog2((uint)x[i + 0]); + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i)); } } - if (tmp[1] != 0) + if (Unsafe.Add(ref tmpRef, 1) != 0) { - retVal -= FastSLog2((uint)tmp[1]); - if (x[i + 1] != 0) + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 1)); + if (Unsafe.Add(ref xRef, i + 1) != 0) { - retVal -= FastSLog2((uint)x[i + 1]); + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 1)); } } - if (tmp[2] != 0) + if (Unsafe.Add(ref tmpRef, 2) != 0) { - retVal -= FastSLog2((uint)tmp[2]); - if (x[i + 2] != 0) + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 2)); + if (Unsafe.Add(ref xRef, i + 2) != 0) { - retVal -= FastSLog2((uint)x[i + 2]); + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 2)); } } - if (tmp[3] != 0) + if (Unsafe.Add(ref tmpRef, 3) != 0) { - retVal -= FastSLog2((uint)tmp[3]); - if (x[i + 3] != 0) + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 3)); + if (Unsafe.Add(ref xRef, i + 3) != 0) { - retVal -= FastSLog2((uint)x[i + 3]); + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3)); } } } @@ -824,24 +824,24 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless // X is fully 0, so only deal with Y. sumXY128 = Sse2.Add(sumXY128, yVec); - if (y[i] != 0) + if (Unsafe.Add(ref yRef, i) != 0) { - retVal -= FastSLog2((uint)y[i]); + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i)); } - if (y[i + 1] != 0) + if (Unsafe.Add(ref yRef, i + 1) != 0) { - retVal -= FastSLog2((uint)y[i + 1]); + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 1)); } - if (y[i + 2] != 0) + if (Unsafe.Add(ref yRef, i + 2) != 0) { - retVal -= FastSLog2((uint)y[i + 2]); + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 2)); } - if (y[i + 3] != 0) + if (Unsafe.Add(ref yRef, i + 3) != 0) { - retVal -= FastSLog2((uint)y[i + 3]); + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3)); } } } @@ -889,19 +889,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - [MethodImpl(InliningOptions.ShortMethod)] - private static void AnalyzeXy(Span tmp, Span x, int i, int pos, ref double retVal) - { - if (tmp[pos] != 0) - { - retVal -= FastSLog2((uint)tmp[pos]); - if (x[i + pos] != 0) - { - retVal -= FastSLog2((uint)x[i + pos]); - } - } - } - [MethodImpl(InliningOptions.ShortMethod)] public static byte TransformColorRed(sbyte greenToRed, uint argb) { From ed8bd615f2be3cafd1a23782e9f7d07c6375d967 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 22 Nov 2021 15:38:46 +0100 Subject: [PATCH 186/511] Faster SSE2 version of ShanonEntropy --- .../Formats/Webp/Lossless/LosslessUtils.cs | 115 +++++------------- 1 file changed, 29 insertions(+), 86 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 0f24e8e8f..68004275b 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; #if SUPPORTS_RUNTIME_INTRINSICS +using System.Numerics; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #endif @@ -706,7 +707,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int colorMapLength4 = 4 * newColorMap.Length; for (; i < colorMapLength4; i++) { - newData[i] = 0; // black tail. + newData[i] = 0; // black tail. } } @@ -760,103 +761,45 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static float CombinedShannonEntropy(Span x, Span y) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse41.IsSupported) + if (Sse2.IsSupported) { double retVal = 0.0d; - Span tmp = stackalloc int[4]; ref int xRef = ref MemoryMarshal.GetReference(x); ref int yRef = ref MemoryMarshal.GetReference(y); - Vector128 sumXY128 = Vector128.Zero; - Vector128 sumX128 = Vector128.Zero; - ref int tmpRef = ref MemoryMarshal.GetReference(tmp); - for (int i = 0; i < 256; i += 4) - { - Vector128 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); - Vector128 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); - - // Check if any X is non-zero: this actually provides a speedup as X is usually sparse. - if (Sse2.MoveMask(Sse2.CompareEqual(xVec, Vector128.Zero).AsByte()) != 0xFFFF) - { - Vector128 xy128 = Sse2.Add(xVec, yVec); - sumXY128 = Sse2.Add(sumXY128, xy128); - sumX128 = Sse2.Add(sumX128, xVec); - - // Analyze the different X + Y. - Unsafe.As>(ref tmpRef) = xy128; - if (tmpRef != 0) - { - retVal -= FastSLog2((uint)tmpRef); - if (Unsafe.Add(ref xRef, i) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i)); - } - } - if (Unsafe.Add(ref tmpRef, 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 1)); - if (Unsafe.Add(ref xRef, i + 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 1)); - } - } - - if (Unsafe.Add(ref tmpRef, 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 2)); - if (Unsafe.Add(ref xRef, i + 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 2)); - } - } - - if (Unsafe.Add(ref tmpRef, 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 3)); - if (Unsafe.Add(ref xRef, i + 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3)); - } - } - } - else + int sumXY = 0; + int sumX = 0; + for (int i = 0; i < 256; i += 16) + { + Vector128 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); + Vector128 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); + Vector128 x1 = Unsafe.As>(ref Unsafe.Add(ref xRef, i + 4)); + Vector128 y1 = Unsafe.As>(ref Unsafe.Add(ref yRef, i + 4)); + Vector128 x2 = Unsafe.As>(ref Unsafe.Add(ref xRef, i + 8)); + Vector128 y2 = Unsafe.As>(ref Unsafe.Add(ref yRef, i + 8)); + Vector128 x3 = Unsafe.As>(ref Unsafe.Add(ref xRef, i + 12)); + Vector128 y3 = Unsafe.As>(ref Unsafe.Add(ref yRef, i + 12)); + Vector128 x4 = Sse2.PackSignedSaturate(Sse2.PackSignedSaturate(x0, x1), Sse2.PackSignedSaturate(x2, x3)); + Vector128 y4 = Sse2.PackSignedSaturate(Sse2.PackSignedSaturate(y0, y1), Sse2.PackSignedSaturate(y2, y3)); + int mx = Sse2.MoveMask(Sse2.CompareGreaterThan(x4, Vector128.Zero).AsByte()); + int my = Sse2.MoveMask(Sse2.CompareGreaterThan(y4, Vector128.Zero).AsByte()) | mx; + while (my != 0) { - // X is fully 0, so only deal with Y. - sumXY128 = Sse2.Add(sumXY128, yVec); - - if (Unsafe.Add(ref yRef, i) != 0) + int j = BitOperations.TrailingZeroCount(my); + if (((mx >> j) & 1) != 0) { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i)); + int xij = Unsafe.Add(ref xRef, i + j); + sumXY += xij; + retVal -= FastSLog2((uint)xij); } - if (Unsafe.Add(ref yRef, i + 1) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 1)); - } - - if (Unsafe.Add(ref yRef, i + 2) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 2)); - } - - if (Unsafe.Add(ref yRef, i + 3) != 0) - { - retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3)); - } + int xy = Unsafe.Add(ref xRef, i + j) + Unsafe.Add(ref yRef, i + j); + sumX += xy; + retVal -= FastSLog2((uint)xy); + my &= my - 1; } } - // Sum up sumX_128 to get sumX and sum up sumXY_128 to get sumXY. - // note: not using here Numerics.ReduceSum, because grouping the same methods together should be slightly faster. - Vector128 haddSumX = Ssse3.HorizontalAdd(sumX128, sumX128); - Vector128 haddSumXY = Ssse3.HorizontalAdd(sumXY128, sumXY128); - Vector128 swappedSumX = Sse2.Shuffle(haddSumX, 0x1); - Vector128 swappedSumXY = Sse2.Shuffle(haddSumXY, 0x1); - Vector128 tmpSumX = Sse2.Add(haddSumX, swappedSumX); - Vector128 tmpSumXY = Sse2.Add(haddSumXY, swappedSumXY); - int sumX = Sse2.ConvertToInt32(tmpSumX); - int sumXY = Sse2.ConvertToInt32(tmpSumXY); - retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY); return (float)retVal; From b1df6a97487f1d8ae68da60eaf3953fe6727f523 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 22 Nov 2021 20:34:22 +0100 Subject: [PATCH 187/511] Revert "Faster SSE2 version of ShanonEntropy" Profiling does not proof that this version is actually faster. --- .../Formats/Webp/Lossless/LosslessUtils.cs | 115 +++++++++++++----- 1 file changed, 86 insertions(+), 29 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 68004275b..0f24e8e8f 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -6,7 +6,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; #if SUPPORTS_RUNTIME_INTRINSICS -using System.Numerics; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; #endif @@ -707,7 +706,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless int colorMapLength4 = 4 * newColorMap.Length; for (; i < colorMapLength4; i++) { - newData[i] = 0; // black tail. + newData[i] = 0; // black tail. } } @@ -761,45 +760,103 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static float CombinedShannonEntropy(Span x, Span y) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) + if (Sse41.IsSupported) { double retVal = 0.0d; + Span tmp = stackalloc int[4]; ref int xRef = ref MemoryMarshal.GetReference(x); ref int yRef = ref MemoryMarshal.GetReference(y); - - int sumXY = 0; - int sumX = 0; - for (int i = 0; i < 256; i += 16) + Vector128 sumXY128 = Vector128.Zero; + Vector128 sumX128 = Vector128.Zero; + ref int tmpRef = ref MemoryMarshal.GetReference(tmp); + for (int i = 0; i < 256; i += 4) { - Vector128 x0 = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); - Vector128 y0 = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); - Vector128 x1 = Unsafe.As>(ref Unsafe.Add(ref xRef, i + 4)); - Vector128 y1 = Unsafe.As>(ref Unsafe.Add(ref yRef, i + 4)); - Vector128 x2 = Unsafe.As>(ref Unsafe.Add(ref xRef, i + 8)); - Vector128 y2 = Unsafe.As>(ref Unsafe.Add(ref yRef, i + 8)); - Vector128 x3 = Unsafe.As>(ref Unsafe.Add(ref xRef, i + 12)); - Vector128 y3 = Unsafe.As>(ref Unsafe.Add(ref yRef, i + 12)); - Vector128 x4 = Sse2.PackSignedSaturate(Sse2.PackSignedSaturate(x0, x1), Sse2.PackSignedSaturate(x2, x3)); - Vector128 y4 = Sse2.PackSignedSaturate(Sse2.PackSignedSaturate(y0, y1), Sse2.PackSignedSaturate(y2, y3)); - int mx = Sse2.MoveMask(Sse2.CompareGreaterThan(x4, Vector128.Zero).AsByte()); - int my = Sse2.MoveMask(Sse2.CompareGreaterThan(y4, Vector128.Zero).AsByte()) | mx; - while (my != 0) + Vector128 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); + Vector128 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); + + // Check if any X is non-zero: this actually provides a speedup as X is usually sparse. + if (Sse2.MoveMask(Sse2.CompareEqual(xVec, Vector128.Zero).AsByte()) != 0xFFFF) { - int j = BitOperations.TrailingZeroCount(my); - if (((mx >> j) & 1) != 0) + Vector128 xy128 = Sse2.Add(xVec, yVec); + sumXY128 = Sse2.Add(sumXY128, xy128); + sumX128 = Sse2.Add(sumX128, xVec); + + // Analyze the different X + Y. + Unsafe.As>(ref tmpRef) = xy128; + if (tmpRef != 0) + { + retVal -= FastSLog2((uint)tmpRef); + if (Unsafe.Add(ref xRef, i) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i)); + } + } + + if (Unsafe.Add(ref tmpRef, 1) != 0) { - int xij = Unsafe.Add(ref xRef, i + j); - sumXY += xij; - retVal -= FastSLog2((uint)xij); + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 1)); + if (Unsafe.Add(ref xRef, i + 1) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 1)); + } } - int xy = Unsafe.Add(ref xRef, i + j) + Unsafe.Add(ref yRef, i + j); - sumX += xy; - retVal -= FastSLog2((uint)xy); - my &= my - 1; + if (Unsafe.Add(ref tmpRef, 2) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 2)); + if (Unsafe.Add(ref xRef, i + 2) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 2)); + } + } + + if (Unsafe.Add(ref tmpRef, 3) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 3)); + if (Unsafe.Add(ref xRef, i + 3) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3)); + } + } + } + else + { + // X is fully 0, so only deal with Y. + sumXY128 = Sse2.Add(sumXY128, yVec); + + if (Unsafe.Add(ref yRef, i) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i)); + } + + if (Unsafe.Add(ref yRef, i + 1) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 1)); + } + + if (Unsafe.Add(ref yRef, i + 2) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 2)); + } + + if (Unsafe.Add(ref yRef, i + 3) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3)); + } } } + // Sum up sumX_128 to get sumX and sum up sumXY_128 to get sumXY. + // note: not using here Numerics.ReduceSum, because grouping the same methods together should be slightly faster. + Vector128 haddSumX = Ssse3.HorizontalAdd(sumX128, sumX128); + Vector128 haddSumXY = Ssse3.HorizontalAdd(sumXY128, sumXY128); + Vector128 swappedSumX = Sse2.Shuffle(haddSumX, 0x1); + Vector128 swappedSumXY = Sse2.Shuffle(haddSumXY, 0x1); + Vector128 tmpSumX = Sse2.Add(haddSumX, swappedSumX); + Vector128 tmpSumXY = Sse2.Add(haddSumXY, swappedSumXY); + int sumX = Sse2.ConvertToInt32(tmpSumX); + int sumXY = Sse2.ConvertToInt32(tmpSumXY); + retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY); return (float)retVal; From 32b97f41fc564d32110d6939f398618a7d683fc6 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 22 Nov 2021 22:21:46 +0100 Subject: [PATCH 188/511] Add AVX2 version of CombinedShannonEntropy --- src/ImageSharp/Common/Helpers/Numerics.cs | 15 +++ .../Formats/Webp/Lossless/LosslessUtils.cs | 97 ++++++++++++++----- 2 files changed, 89 insertions(+), 23 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index ba5c588ca..9dc13079d 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -820,6 +820,21 @@ namespace SixLabors.ImageSharp } } + /// + /// Reduces elements of the vector into one sum. + /// + /// The accumulator to reduce. + /// The sum of all elements. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int ReduceSum(Vector256 accumulator) + { + Vector128 vec0 = Avx2.ExtractVector128(accumulator, 0); + Vector128 vec1 = Avx2.ExtractVector128(accumulator, 1); + Vector128 sum128 = Sse2.Add(vec0, vec1); + + return ReduceSum(sum128); + } + /// /// Reduces even elements of the vector into one sum. /// diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 0f24e8e8f..314f26d64 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -760,29 +761,30 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static float CombinedShannonEntropy(Span x, Span y) { #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse41.IsSupported) + if (Avx2.IsSupported) { double retVal = 0.0d; - Span tmp = stackalloc int[4]; + Span tmp = stackalloc int[8]; ref int xRef = ref MemoryMarshal.GetReference(x); ref int yRef = ref MemoryMarshal.GetReference(y); - Vector128 sumXY128 = Vector128.Zero; - Vector128 sumX128 = Vector128.Zero; + Vector256 sumXY256 = Vector256.Zero; + Vector256 sumX256 = Vector256.Zero; ref int tmpRef = ref MemoryMarshal.GetReference(tmp); - for (int i = 0; i < 256; i += 4) + for (nint i = 0; i < 256; i += 8) { - Vector128 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); - Vector128 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); + Vector256 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); + Vector256 yVec = Unsafe.As>(ref Unsafe.Add(ref yRef, i)); // Check if any X is non-zero: this actually provides a speedup as X is usually sparse. - if (Sse2.MoveMask(Sse2.CompareEqual(xVec, Vector128.Zero).AsByte()) != 0xFFFF) + int mask = Avx2.MoveMask(Avx2.CompareEqual(xVec, Vector256.Zero).AsByte()); + if (mask != -1) { - Vector128 xy128 = Sse2.Add(xVec, yVec); - sumXY128 = Sse2.Add(sumXY128, xy128); - sumX128 = Sse2.Add(sumX128, xVec); + Vector256 xy256 = Avx2.Add(xVec, yVec); + sumXY256 = Avx2.Add(sumXY256, xy256); + sumX256 = Avx2.Add(sumX256, xVec); // Analyze the different X + Y. - Unsafe.As>(ref tmpRef) = xy128; + Unsafe.As>(ref tmpRef) = xy256; if (tmpRef != 0) { retVal -= FastSLog2((uint)tmpRef); @@ -818,11 +820,47 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 3)); } } + + if (Unsafe.Add(ref tmpRef, 4) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 4)); + if (Unsafe.Add(ref xRef, i + 4) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 4)); + } + } + + if (Unsafe.Add(ref tmpRef, 5) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 5)); + if (Unsafe.Add(ref xRef, i + 5) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 5)); + } + } + + if (Unsafe.Add(ref tmpRef, 6) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 6)); + if (Unsafe.Add(ref xRef, i + 6) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 6)); + } + } + + if (Unsafe.Add(ref tmpRef, 7) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref tmpRef, 7)); + if (Unsafe.Add(ref xRef, i + 7) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref xRef, i + 7)); + } + } } else { // X is fully 0, so only deal with Y. - sumXY128 = Sse2.Add(sumXY128, yVec); + sumXY256 = Avx2.Add(sumXY256, yVec); if (Unsafe.Add(ref yRef, i) != 0) { @@ -843,19 +881,32 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 3)); } + + if (Unsafe.Add(ref yRef, i + 4) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 4)); + } + + if (Unsafe.Add(ref yRef, i + 5) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 5)); + } + + if (Unsafe.Add(ref yRef, i + 6) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 6)); + } + + if (Unsafe.Add(ref yRef, i + 7) != 0) + { + retVal -= FastSLog2((uint)Unsafe.Add(ref yRef, i + 7)); + } } } - // Sum up sumX_128 to get sumX and sum up sumXY_128 to get sumXY. - // note: not using here Numerics.ReduceSum, because grouping the same methods together should be slightly faster. - Vector128 haddSumX = Ssse3.HorizontalAdd(sumX128, sumX128); - Vector128 haddSumXY = Ssse3.HorizontalAdd(sumXY128, sumXY128); - Vector128 swappedSumX = Sse2.Shuffle(haddSumX, 0x1); - Vector128 swappedSumXY = Sse2.Shuffle(haddSumXY, 0x1); - Vector128 tmpSumX = Sse2.Add(haddSumX, swappedSumX); - Vector128 tmpSumXY = Sse2.Add(haddSumXY, swappedSumXY); - int sumX = Sse2.ConvertToInt32(tmpSumX); - int sumXY = Sse2.ConvertToInt32(tmpSumXY); + // Sum up sumX256 to get sumX and sum up sumXY256 to get sumXY. + int sumX = Numerics.ReduceSum(sumX256); + int sumXY = Numerics.ReduceSum(sumXY256); retVal += FastSLog2((uint)sumX) + FastSLog2((uint)sumXY); From 50b0f0b07f23472fe3093c19ba0fd98a3921639f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 23 Nov 2021 01:21:03 +0300 Subject: [PATCH 189/511] Test qol fixes --- tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 10 ++++------ .../ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 3a6eb4f8b..85f30d28d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -52,16 +52,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Part of the IDCT calculations is fused into the quantization step // We must multiply input block with adjusted no-quantization matrix // before applying IDCT + // Dequantization using unit matrix - no values are upscaled Block8x8F dequantMatrix = CreateBlockFromScalar(1); - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's // This step is needed to apply adjusting multipliers to the input block FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - srcBlock.MultiplyInPlace(ref dequantMatrix); // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); + srcBlock.MultiplyInPlace(ref dequantMatrix); // IDCT calculation FastFloatingPointDCT.TransformIDCT(ref srcBlock); @@ -86,16 +85,15 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // Part of the IDCT calculations is fused into the quantization step // We must multiply input block with adjusted no-quantization matrix // before applying IDCT + // Dequantization using unit matrix - no values are upscaled Block8x8F dequantMatrix = CreateBlockFromScalar(1); - // Dequantization using unit matrix - no values are upscaled - // as quant matrix is all 1's // This step is needed to apply adjusting multipliers to the input block FastFloatingPointDCT.AdjustToIDCT(ref dequantMatrix); - srcBlock.MultiplyInPlace(ref dequantMatrix); // IDCT implementation tranforms blocks after transposition srcBlock.TransposeInplace(); + srcBlock.MultiplyInPlace(ref dequantMatrix); // IDCT calculation FastFloatingPointDCT.TransformIDCT(ref srcBlock); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs index a76b2bf2e..1bdfc6eca 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/JpegFixture.cs @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils bool failed = false; - for (int i = 0; i < 64; i++) + for (int i = 0; i < Block8x8F.Size; i++) { float expected = a[i]; float actual = b[i]; From cf4cf239c71793100ed006541a09bd042e797c5a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 23 Nov 2021 01:42:36 +0300 Subject: [PATCH 190/511] Shared infrastructure subproject commit hash --- shared-infrastructure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-infrastructure b/shared-infrastructure index 33cb12ca7..a042aba17 160000 --- a/shared-infrastructure +++ b/shared-infrastructure @@ -1 +1 @@ -Subproject commit 33cb12ca77f919b44de56f344d2627cc2a108c3a +Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3 From 3069dd18de030544207100b3012ec009787dab3f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 23 Nov 2021 05:48:04 +0300 Subject: [PATCH 191/511] Fixed failing tests --- .../Formats/Jpeg/Components/Block8x8.cs | 58 +++++++++++++++++++ .../Jpg/Utils/LibJpegTools.ComponentData.cs | 15 ++--- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs index 9d49b8c45..27bb2fc3c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs @@ -337,6 +337,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components } } + /// + /// Transpose the block inplace. + /// + [MethodImpl(InliningOptions.ShortMethod)] + public void TransposeInplace() + { + ref short elemRef = ref Unsafe.As(ref this); + + // row #0 + Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8)); + Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16)); + Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24)); + Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32)); + Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40)); + Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48)); + Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56)); + + // row #1 + Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17)); + Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25)); + Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33)); + Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41)); + Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49)); + Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57)); + + // row #2 + Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26)); + Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34)); + Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42)); + Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50)); + Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58)); + + // row #3 + Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35)); + Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43)); + Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51)); + Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59)); + + // row #4 + Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44)); + Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52)); + Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60)); + + // row #5 + Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53)); + Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61)); + + // row #6 + Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62)); + + static void Swap(ref short a, ref short b) + { + short tmp = a; + a = b; + b = tmp; + } + } + /// /// Calculate the total sum of absolute differences of elements in 'a' and 'b'. /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs index adbd695c0..5c00b39af 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/LibJpegTools.ComponentData.cs @@ -48,6 +48,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils public short MaxVal { get; private set; } = short.MinValue; + internal void MakeBlock(Block8x8 block, int y, int x) + { + block.TransposeInplace(); + this.MakeBlock(block.ToArray(), y, x); + } + internal void MakeBlock(short[] data, int y, int x) { this.MinVal = Math.Min(this.MinVal, data.Min()); @@ -66,11 +72,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Span blockRow = data.GetRowSpan(y - startIndex); for (int x = 0; x < this.WidthInBlocks; x++) { - short[] block = blockRow[x].ToArray(); - - // x coordinate stays the same - we load entire stride - // y coordinate is tricky as we load single stride to full buffer - offset is needed - this.MakeBlock(block, y, x); + this.MakeBlock(blockRow[x], y, x); } } } @@ -83,8 +85,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils Span blockRow = data.GetRowSpan(y); for (int x = 0; x < this.WidthInBlocks; x++) { - short[] block = blockRow[x].ToArray(); - this.MakeBlock(block, y, x); + this.MakeBlock(blockRow[x], y, x); } } } From 42315dcd80d7c9ed4708ff9b0b4b31b3e7794427 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 23 Nov 2021 06:00:43 +0300 Subject: [PATCH 192/511] Updated benchmarks --- tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs index 102ba8dd3..9665ca42d 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Jpeg/DecodeJpeg.cs @@ -75,7 +75,7 @@ Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores | Method | Mean | Error | StdDev | |------------------------------------ |----------:|----------:|----------:| -| 'Baseline 4:4:4 Interleaved' | 11.532 ms | 0.0530 ms | 0.0496 ms | +| 'Baseline 4:4:4 Interleaved' | 11.127 ms | 0.0659 ms | 0.0550 ms | | 'Baseline 4:2:0 Interleaved' | 8.458 ms | 0.0289 ms | 0.0256 ms | | 'Baseline 4:0:0 (grayscale)' | 1.550 ms | 0.0050 ms | 0.0044 ms | | 'Progressive 4:2:0 Non-Interleaved' | 13.220 ms | 0.0449 ms | 0.0398 ms | From 14508498d9f0d709e31d4d4e4e44a740cb3353c2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 09:00:33 +0100 Subject: [PATCH 193/511] Seal classes and avoid calling SuppressFinalize --- .../Formats/Webp/Lossless/CostManager.cs | 24 ++----------------- .../Formats/Webp/Lossless/PixOrCopy.cs | 2 +- .../Formats/Webp/Lossless/Vp8LHashChain.cs | 23 ++---------------- 3 files changed, 5 insertions(+), 44 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs index 3ee102138..c121a41a1 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/CostManager.cs @@ -13,10 +13,8 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless /// It caches the different CostCacheInterval, caches the different /// GetLengthCost(costModel, k) in costCache and the CostInterval's. /// - internal class CostManager : IDisposable + internal sealed class CostManager : IDisposable { - private bool disposed; - private CostInterval head; private const int FreeIntervalsStartCount = 25; @@ -328,25 +326,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless } } - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.Costs.Dispose(); - } - - this.disposed = true; - } - } - /// - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() => this.Costs.Dispose(); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs index 6cd109121..96cdc3cbc 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/PixOrCopy.cs @@ -6,7 +6,7 @@ using System.Diagnostics; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { [DebuggerDisplay("Mode: {Mode}, Len: {Len}, BgraOrDistance: {BgraOrDistance}")] - internal class PixOrCopy + internal sealed class PixOrCopy { public PixOrCopyMode Mode { get; set; } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs index 2aa35e392..1bc7613a9 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHashChain.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Webp.Lossless { - internal class Vp8LHashChain : IDisposable + internal sealed class Vp8LHashChain : IDisposable { private const uint HashMultiplierHi = 0xc6a4a793u; @@ -30,8 +30,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless private readonly MemoryAllocator memoryAllocator; - private bool disposed; - /// /// Initializes a new instance of the class. /// @@ -288,24 +286,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return maxWindowSize > WindowSize ? WindowSize : maxWindowSize; } - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.OffsetLength.Dispose(); - } - - this.disposed = true; - } - } - /// - public void Dispose() - { - this.Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() => this.OffsetLength.Dispose(); } } From 0fc3ce721270c50999b715fbff6d9663a8386cc4 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 11:12:46 +0100 Subject: [PATCH 194/511] Add CombinedShannonEntropy tests --- .../Formats/WebP/LosslessUtilsTests.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 97567ba21..9c7a2f758 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -10,6 +10,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class LosslessUtilsTests { + private static void RunCombinedShannonEntropyTest() + { + int[] x = { 3, 5, 2, 5, 3, 1, 2, 2, 3, 3, 1, 2, 1, 2, 1, 1, 0, 0, 0, 1, 1, 2, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 1, 1, 0, 0, 2, 1, 1, 0, 3, 1, 2, 3, 2, 3 }; + int[] y = { 11, 12, 8, 3, 4, 1, 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, 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, 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, 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, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 2, 1, 1, 2, 4, 6, 4 }; + float expected = 884.7585f; + + float actual = LosslessUtils.CombinedShannonEntropy(x, y); + + Assert.Equal(expected, actual, 5); + } + private static void RunSubtractGreenTest() { uint[] pixelData = @@ -193,6 +204,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + [Fact] + public void CombinedShannonEntropy_Works() => RunCombinedShannonEntropyTest(); + [Fact] public void Predictor11_Works() => RunPredictor11Test(); @@ -215,6 +229,12 @@ 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); + + [Fact] + public void CombinedShannonEntropy_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCombinedShannonEntropyTest, HwIntrinsics.DisableAVX2); + [Fact] public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); @@ -237,19 +257,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void SubtractGreen_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.AllowAll); [Fact] - public void SubtractGreen_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX); + public void SubtractGreen_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2); [Fact] - public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSSE3); + public void SubtractGreen_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSubtractGreenTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSSE3); [Fact] public void AddGreenToBlueAndRed_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.AllowAll); [Fact] - public void AddGreenToBlueAndRed_WithoutAvx_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX); + public void AddGreenToBlueAndRed_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2); [Fact] - public void AddGreenToBlueAndRed_WithoutAvxOrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); + public void AddGreenToBlueAndRed_WithoutAVX2OrSSSE3_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddGreenToBlueAndRedTest, HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableSSE2 | HwIntrinsics.DisableSSSE3); [Fact] public void TransformColor_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunTransformColorTest, HwIntrinsics.AllowAll); From 8192e116f2b88eb65f80a366c63e1abee6415915 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 15:09:48 +0100 Subject: [PATCH 195/511] Add AVX2 version of AddVector --- .../Formats/Webp/Lossless/Vp8LHistogram.cs | 51 +++++++++++++++++-- .../Formats/Webp/Lossy/Vp8Histogram.cs | 2 +- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index bdb53f5c6..ac8cc0f65 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -3,10 +3,16 @@ using System; using System.Collections.Generic; +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.Lossless { - internal class Vp8LHistogram : IDeepCloneable + internal sealed class Vp8LHistogram : IDeepCloneable { private const uint NonTrivialSym = 0xffffffff; @@ -505,11 +511,48 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return cost; } - private static void AddVector(uint[] a, uint[] b, uint[] output, int size) + private static void AddVector(Span a, Span b, Span output, int size) { - for (int i = 0; i < size; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) { - output[i] = a[i] + b[i]; + ref uint aRef = ref MemoryMarshal.GetReference(a); + ref uint bRef = ref MemoryMarshal.GetReference(b); + ref uint outputRef = ref MemoryMarshal.GetReference(output); + int i; + + for (i = 0; i + 32 <= size; i += 32) + { + // Load values. + Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, i)); + Vector256 a1 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 8)); + Vector256 a2 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 16)); + Vector256 a3 = Unsafe.As>(ref Unsafe.Add(ref aRef, i + 24)); + Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref bRef, i)); + Vector256 b1 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 8)); + Vector256 b2 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 16)); + Vector256 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 24)); + + // Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But + // that's ok since the histogram values are less than 1<<28 (max picture size). + Unsafe.As>(ref Unsafe.Add(ref outputRef, i)) = Avx2.Add(a0, b0); + Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 8)) = Avx2.Add(a1, b1); + Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 16)) = Avx2.Add(a2, b2); + Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 24)) = Avx2.Add(a3, b3); + } + + for (; i < size; i++) + { + output[i] = a[i] + b[i]; + } + } + else +#endif + { + for (int i = 0; i < size; i++) + { + output[i] = a[i] + b[i]; + } } } } diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs index 6e724e475..89e7baff3 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs @@ -6,7 +6,7 @@ using System.Runtime.CompilerServices; namespace SixLabors.ImageSharp.Formats.Webp.Lossy { - internal class Vp8Histogram + internal sealed class Vp8Histogram { private readonly int[] scratch = new int[16]; From a45f49517b7fb4ed7981becb58898ea175273e80 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 15:59:59 +0100 Subject: [PATCH 196/511] Avoid bounds checks in VectorMismatch --- src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 471c083cd..d24431600 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -81,7 +81,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless { int matchLen = 0; - while (matchLen < length && array1[matchLen] == array2[matchLen]) + ref uint array1Ref = ref MemoryMarshal.GetReference(array1); + ref uint array2Ref = ref MemoryMarshal.GetReference(array2); + while (matchLen < length && Unsafe.Add(ref array1Ref, matchLen) == Unsafe.Add(ref array2Ref, matchLen)) { matchLen++; } From 92445c35812ca66a3c7ac6cf9616ea7d89159a9e Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 23 Nov 2021 19:13:58 +0300 Subject: [PATCH 197/511] Added test for TransposeInplace --- .../Formats/Jpg/Block8x8FTests.cs | 5 +++- .../Formats/Jpg/Block8x8Tests.cs | 26 +++++++++++++++++++ .../Jpg/Utils/ReferenceImplementations.cs | 17 ++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index e5dc0ba01..8f5f10f19 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -183,9 +183,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(expected, actual); } + // This method has only 2 implementations: + // 1. AVX + // 2. Scalar FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, - HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableHWIntrinsic); } private static float[] Create8x8ColorCropTestData() diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs index 3737cce80..b13a196cb 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8Tests.cs @@ -276,5 +276,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg seed, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2); } + + [Fact] + public void TransposeInplace() + { + static void RunTest() + { + short[] expected = Create8x8ShortData(); + ReferenceImplementations.Transpose8x8(expected); + + var block8x8 = default(Block8x8); + block8x8.LoadFrom(Create8x8ShortData()); + + block8x8.TransposeInplace(); + + short[] actual = new short[64]; + block8x8.CopyTo(actual); + + Assert.Equal(expected, actual); + } + + // This method has only 1 implementation: + // 1. Scalar + FeatureTestRunner.RunWithHwIntrinsicsFeature( + RunTest, + HwIntrinsics.DisableHWIntrinsic); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs index c9741521c..8dc1c83d4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Utils/ReferenceImplementations.cs @@ -40,6 +40,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg.Utils } } + /// + /// Transpose 8x8 block stored linearly in a (inplace) + /// + internal static void Transpose8x8(Span data) + { + for (int i = 1; i < 8; i++) + { + int i8 = i * 8; + for (int j = 0; j < i; j++) + { + short tmp = data[i8 + j]; + data[i8 + j] = data[(j * 8) + i]; + data[(j * 8) + i] = tmp; + } + } + } + /// /// Transpose 8x8 block stored linearly in a /// From 6393484e4283719575451aba7d18d91d7d86b6af Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 17:15:42 +0100 Subject: [PATCH 198/511] Remove duplicate FTransform method --- .../Formats/Webp/Lossy/Vp8Histogram.cs | 44 +------------------ 1 file changed, 1 insertion(+), 43 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs index 89e7baff3..d384302b9 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Histogram.cs @@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.distribution.AsSpan().Clear(); for (j = startBlock; j < endBlock; j++) { - this.Vp8FTransform(reference.Slice(WebpLookupTables.Vp8DspScan[j]), pred.Slice(WebpLookupTables.Vp8DspScan[j]), this.output); + 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) @@ -98,48 +98,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.lastNonZero = lastNonZero; } - private void Vp8FTransform(Span src, Span reference, Span output) - { - int i; - Span tmp = this.scratch; - tmp.Clear(); - - for (i = 0; i < 4; i++) - { - int d0 = src[0] - reference[0]; // 9bit dynamic range ([-255,255]) - int d1 = src[1] - reference[1]; - int d2 = src[2] - reference[2]; - int d3 = src[3] - reference[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; - - // Do not change the span in the last iteration. - if (i < 3) - { - src = src.Slice(WebpConstants.Bps); - reference = reference.Slice(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); - } - } - [MethodImpl(InliningOptions.ShortMethod)] private static int ClipMax(int v, int max) => v > max ? max : v; } From 28147059b43a197ffcc8b4b5cb692adc22975e8f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 23 Nov 2021 19:42:27 +0300 Subject: [PATCH 199/511] Removed bound checks from DCT adjustment methods --- .../Jpeg/Components/FastFloatingPointDCT.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs index a1c03e65c..770093195 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -3,6 +3,7 @@ using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.Intrinsics.X86; #endif @@ -65,9 +66,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Quantization table to adjust. public static void AdjustToIDCT(ref Block8x8F quantTable) { - for (int i = 0; i < Block8x8F.Size; i++) + ref float tableRef = ref Unsafe.As(ref quantTable); + ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); + for (nint i = 0; i < Block8x8F.Size; i++) { - quantTable[i] = quantTable[i] * AdjustmentCoefficients[i] * 0.125f; + tableRef = ref Unsafe.Add(ref tableRef, i); + tableRef = 0.125f * tableRef * Unsafe.Add(ref multipliersRef, i); } // Spectral macroblocks are transposed before quantization @@ -81,9 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Quantization table to adjust. public static void AdjustToFDCT(ref Block8x8F quantTable) { - for (int i = 0; i < Block8x8F.Size; i++) + ref float tableRef = ref Unsafe.As(ref quantTable); + ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); + for (nint i = 0; i < Block8x8F.Size; i++) { - quantTable[i] = 0.125f / (quantTable[i] * AdjustmentCoefficients[i]); + tableRef = ref Unsafe.Add(ref tableRef, i); + tableRef = 0.125f / (tableRef * Unsafe.Add(ref multipliersRef, i)); } } From 491b742ae4a70dc6c5ff1e6c3a9db9cb7f00fcf3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 18:38:19 +0100 Subject: [PATCH 200/511] Add SSE2 version of VectorMismatch --- .../Formats/Webp/Lossless/LosslessUtils.cs | 65 +++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index d24431600..319aa8c3d 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -80,15 +80,72 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless public static int VectorMismatch(ReadOnlySpan array1, ReadOnlySpan array2, int length) { int matchLen = 0; - ref uint array1Ref = ref MemoryMarshal.GetReference(array1); ref uint array2Ref = ref MemoryMarshal.GetReference(array2); - while (matchLen < length && Unsafe.Add(ref array1Ref, matchLen) == Unsafe.Add(ref array2Ref, matchLen)) + +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { - matchLen++; + if (length >= 12) + { + Vector128 a0 = Unsafe.As>(ref array1Ref); + Vector128 a1 = Unsafe.As>(ref array2Ref); + + do + { + // Loop unrolling and early load both provide a speedup. + Vector128 cmpA = Sse2.CompareEqual(a0, a1); + Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref array1Ref, matchLen + 4)); + Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref array2Ref, matchLen + 4)); + if (Sse2.MoveMask(cmpA.AsByte()) != 0xffff) + { + break; + } + + matchLen += 4; + + Vector128 cmpB = Sse2.CompareEqual(b0, b1); + a0 = Unsafe.As>(ref Unsafe.Add(ref array1Ref, matchLen + 4)); + a1 = Unsafe.As>(ref Unsafe.Add(ref array2Ref, matchLen + 4)); + if (Sse2.MoveMask(cmpB.AsByte()) != 0xffff) + { + break; + } + + matchLen += 4; + } + while (matchLen + 12 < length); + } + else + { + // Unroll the potential first two loops. + if (length >= 4 + && Sse2.MoveMask( + Sse2.CompareEqual( + Unsafe.As>(ref array1Ref), + Unsafe.As>(ref array2Ref)).AsByte()) == 0xffff) + { + matchLen = 4; + if (length >= 8 + && Sse2.MoveMask( + Sse2.CompareEqual( + Unsafe.As>(ref Unsafe.Add(ref array1Ref, 4)), + Unsafe.As>(ref Unsafe.Add(ref array2Ref, 4))).AsByte()) == 0xffff) + { + matchLen = 8; + } + } + } } +#endif + { + while (matchLen < length && Unsafe.Add(ref array1Ref, matchLen) == Unsafe.Add(ref array2Ref, matchLen)) + { + matchLen++; + } - return matchLen; + return matchLen; + } } [MethodImpl(InliningOptions.ShortMethod)] From 586df2e7b136745736884072a49ec797715d5feb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 23 Nov 2021 21:49:27 +0300 Subject: [PATCH 201/511] Fixed crash bug --- .../Formats/Jpeg/Components/FastFloatingPointDCT.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs index 770093195..81bfe2135 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -70,8 +70,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); for (nint i = 0; i < Block8x8F.Size; i++) { - tableRef = ref Unsafe.Add(ref tableRef, i); tableRef = 0.125f * tableRef * Unsafe.Add(ref multipliersRef, i); + tableRef = ref Unsafe.Add(ref tableRef, 1); } // Spectral macroblocks are transposed before quantization @@ -89,8 +89,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components ref float multipliersRef = ref MemoryMarshal.GetReference(AdjustmentCoefficients); for (nint i = 0; i < Block8x8F.Size; i++) { - tableRef = ref Unsafe.Add(ref tableRef, i); tableRef = 0.125f / (tableRef * Unsafe.Add(ref multipliersRef, i)); + tableRef = ref Unsafe.Add(ref tableRef, 1); } } From 427a39213e2e7429667d376a8b86c09b47f6da62 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 23 Nov 2021 23:11:06 +0100 Subject: [PATCH 202/511] Add VectorMismatch tests --- .../Formats/WebP/LosslessUtilsTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index 97567ba21..b6f15e537 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -10,6 +10,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class LosslessUtilsTests { + private static void RunVectorMismatchTest() + { + uint[] array1 = { 4278193152, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896 }; + uint[] array2 = { 4278193152, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896 }; + int expected1 = 18; + + // Test unroll first two loops path also. + uint[] array3 = { 4279238656, 4278714368, 4279238656, 4279238656, 4279238656, 4279238656, 4279238896, 4279238896, 4279238884 }; + uint[] array4 = { 4279238656, 4278714368, 4279238656, 4279238656, 4278190080, 4278190080, 4278190080, 4278190080, 4278190080 }; + int expected2 = 4; + + int actual1 = LosslessUtils.VectorMismatch(array1, array2, 18); + int actual2 = LosslessUtils.VectorMismatch(array3, array4, 9); + + Assert.Equal(expected1, actual1); + Assert.Equal(expected2, actual2); + } + private static void RunSubtractGreenTest() { uint[] pixelData = @@ -193,6 +211,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + [Fact] + public void VectorMismatch_Works() => RunVectorMismatchTest(); + [Fact] public void Predictor11_Works() => RunPredictor11Test(); @@ -215,6 +236,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void TransformColorInverse_Works() => RunTransformColorInverseTest(); #if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void VectorMismatch_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVectorMismatchTest, HwIntrinsics.AllowAll); + + [Fact] + public void VectorMismatch_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVectorMismatchTest, HwIntrinsics.DisableSSE2); + [Fact] public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); From fa24760a88e0669a49eaae2e0ef5ef209a86c0d1 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 12:20:28 +0100 Subject: [PATCH 203/511] Add AddVector tests --- .../Formats/WebP/Vp8LHistogramTests.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs new file mode 100644 index 000000000..f39e16bc2 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8LHistogramTests.cs @@ -0,0 +1,109 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using SixLabors.ImageSharp.Formats.Webp.Lossless; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + public class Vp8LHistogramTests + { + private static void RunAddVectorTest() + { + // arrange + uint[] pixelData = + { + 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4278191104, 4294577152, + 4294707200, 4294707200, 4294707200, 4294707200, 4294837248, 4294837248, 4293926912, 4294316544, + 4278191104, 4278191104, 4294837248, 4294837248, 4280287232, 4280350720, 4294447104, 4294707200, + 4294838272, 4278516736, 4294837248, 4294837248, 4278516736, 4294707200, 4279298048, 4294837248, + 4294837248, 4294837248, 4294837248, 4280287232, 4280287232, 4292670464, 4279633408, 4294838272, + 4294837248, 4278516736, 4278516736, 4278516736, 4278516736, 4278516736, 4278778880, 4278193152, + 4278191104, 4280287232, 4280287232, 4280287232, 4280287232, 4293971968, 4280612864, 4292802560, + 4294837760, 4278516736, 4278516736, 4294837760, 4294707712, 4278516736, 4294837248, 4278193152, + 4280287232, 4278984704, 4280287232, 4278243328, 4280287232, 4278244352, 4280287232, 4280025088, + 4280025088, 4294837760, 4278192128, 4294838784, 4294837760, 4294707712, 4278778880, 4278324224, + 4280287232, 4280287232, 4278202368, 4279115776, 4280287232, 4278243328, 4280287232, 4280287232, + 4280025088, 4280287232, 4278192128, 4294838272, 4294838272, 4294837760, 4278190592, 4278778880, + 4280875008, 4280287232, 4279896576, 4281075712, 4281075712, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4278190592, 4294709248, 4278516736, 4278516736, 4278584832, 4278909440, + 4280287232, 4280287232, 4294367744, 4294621184, 4279115776, 4280287232, 4280287232, 4280351744, + 4280287232, 4280287232, 4280287232, 4278513664, 4278516736, 4278716416, 4278584832, 4280291328, + 4293062144, 4280287232, 4280287232, 4280287232, 4294456320, 4280291328, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4278513152, 4278716416, 4278584832, 4280291328, + 4278198272, 4278198272, 4278589952, 4278198272, 4278198272, 4280287232, 4278765568, 4280287232, + 4280287232, 4280287232, 4280287232, 4294712832, 4278513152, 4278716640, 4279300608, 4278584832, + 4280156672, 4279373312, 4278589952, 4279373312, 4278328832, 4278328832, 4278328832, 4279634432, + 4280287232, 4280287232, 4280287232, 4280287232, 4278457344, 4280483328, 4278584832, 4278385664, + 4279634432, 4279373312, 4279634432, 4280287232, 4280287232, 4280156672, 4278589952, 4278328832, + 4278198272, 4280156672, 4280483328, 4294363648, 4280287232, 4278376448, 4280287232, 4278647808, + 4280287232, 4280287232, 4279373312, 4280287232, 4280287232, 4280156672, 4280287232, 4278198272, + 4278198272, 4280156672, 4280287232, 4280287232, 4293669888, 4278765568, 4278765568, 4280287232, + 4280287232, 4280287232, 4279634432, 4279634432, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4279373312, 4279764992, 4293539328, 4279896576, + 4280287232, 4280287232, 4280287232, 4279634432, 4278198272, 4279634432, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4279503872, 4279503872, 4280288256, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, + 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232, 4280287232 + }; + + uint[] literals = + { + 198, 0, 14, 0, 46, 0, 22, 0, 36, 0, 24, 0, 12, 0, 10, 0, 10, 0, 2, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, + 10, 0, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 2, 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, 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 6, 0, 2, 0, 2, 0, 2, 0, 0, 0, 8, 0, 2, 0, 38, 0, 4 + }; + + uint[] expectedLiterals = new uint[1305]; + + // All remaining values are expected to be zero. + literals.AsSpan().CopyTo(expectedLiterals); + + var backwardRefs = new Vp8LBackwardRefs(pixelData.Length); + for (int i = 0; i < pixelData.Length; i++) + { + backwardRefs.Add(new PixOrCopy() + { + BgraOrDistance = pixelData[i], + Len = 1, + Mode = PixOrCopyMode.Literal + }); + } + + var histogram0 = new Vp8LHistogram(backwardRefs, 3); + var histogram1 = new Vp8LHistogram(backwardRefs, 3); + for (int i = 0; i < 5; i++) + { + histogram0.IsUsed[i] = true; + histogram1.IsUsed[i] = true; + } + + var output = new Vp8LHistogram(3); + + // act + histogram0.Add(histogram1, output); + + // assert + Assert.True(output.Literal.SequenceEqual(expectedLiterals)); + } + + [Fact] + public void AddVector_Works() => RunAddVectorTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void AddVector_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.AllowAll); + + [Fact] + public void AddVector_WithoutAVX2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunAddVectorTest, HwIntrinsics.DisableAVX2); +#endif + } +} From 110ff3d9e8f9651d73286576e17f69f1e2ecfa31 Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Wed, 24 Nov 2021 12:38:44 +0100 Subject: [PATCH 204/511] Avoid using Span tmp 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/Lossless/LosslessUtils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 314f26d64..4f247c434 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -764,12 +764,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless if (Avx2.IsSupported) { double retVal = 0.0d; - Span tmp = stackalloc int[8]; + Vector256 tmp = Vector256.Zero; // has the size of the scratch space of sizeof(int) * 8 ref int xRef = ref MemoryMarshal.GetReference(x); ref int yRef = ref MemoryMarshal.GetReference(y); Vector256 sumXY256 = Vector256.Zero; Vector256 sumX256 = Vector256.Zero; - ref int tmpRef = ref MemoryMarshal.GetReference(tmp); + ref int tmpRef = ref Unsafe.As, int>(ref tmp); for (nint i = 0; i < 256; i += 8) { Vector256 xVec = Unsafe.As>(ref Unsafe.Add(ref xRef, i)); From 5403fbd8b2a4f42e9a9deed923d3017d449b3ab9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 12:34:34 +0100 Subject: [PATCH 205/511] Add better version of ReduceSum for Vector 256 --- src/ImageSharp/Common/Helpers/Numerics.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 9dc13079d..fa0af823d 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -828,11 +828,16 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReduceSum(Vector256 accumulator) { - Vector128 vec0 = Avx2.ExtractVector128(accumulator, 0); - Vector128 vec1 = Avx2.ExtractVector128(accumulator, 1); - Vector128 sum128 = Sse2.Add(vec0, vec1); + // Add upper lane to lower lane. + Vector128 vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); - return ReduceSum(sum128); + // Add odd to even. + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_11_01_01)); + + // Add high to low. + vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); + + return Sse2.ConvertToInt32(vsum); } /// From cc5f7af71c2fc42f317a5244f0ea660aa3123636 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 12:50:39 +0100 Subject: [PATCH 206/511] Better version of LoadHigh --- src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index 8e3b15389..d0a14db33 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -720,12 +720,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Convert 32 samples of YUV444 to R/G/B private static void ConvertYuv444ToRgbSse41(Span y, Span u, Span v, out Vector128 r, out Vector128 g, out Vector128 b) { - Vector64 yTmp = Unsafe.As>(ref MemoryMarshal.GetReference(y)); - Vector64 uTmp = Unsafe.As>(ref MemoryMarshal.GetReference(u)); - Vector64 vTmp = Unsafe.As>(ref MemoryMarshal.GetReference(v)); - Vector128 y0 = LoadHigh(yTmp); - Vector128 u0 = LoadHigh(uTmp); - Vector128 v0 = LoadHigh(vTmp); + Vector128 y0 = LoadHigh(ref MemoryMarshal.GetReference(y)); + Vector128 u0 = LoadHigh(ref MemoryMarshal.GetReference(u)); + Vector128 v0 = LoadHigh(ref MemoryMarshal.GetReference(v)); Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16()); Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); @@ -751,9 +748,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 LoadHigh(Vector64 src) + private static Vector128 LoadHigh(ref byte src) { - Vector128 tmp = Unsafe.As, Vector128>(ref src); + Vector128 tmp = Unsafe.As>(ref src); return Sse2.UnpackLow(Vector128.Zero, tmp); } #endif From 65870b96f429d03beb75173447fc56a01fd6b305 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 13:14:38 +0100 Subject: [PATCH 207/511] Avoid branching inside loop --- .../Formats/Webp/Lossy/YuvConversion.cs | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index d0a14db33..a8286037b 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -183,11 +183,23 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy ref byte topVRef = ref MemoryMarshal.GetReference(topV); ref byte curURef = ref MemoryMarshal.GetReference(curU); ref byte curVRef = ref MemoryMarshal.GetReference(curV); - for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) + if (bottomY != null) { - UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); - UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); - ConvertYuvToBgrSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); + for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) + { + UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); + UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); + ConvertYuvToBgrWithBottomYSse41(topY, bottomY, topDst, bottomDst, ru, rv, pos, xStep); + } + } + else + { + for (pos = 1, uvPos = 0; pos + 32 + 1 <= len; pos += 32, uvPos += 16) + { + UpSample32Pixels(ref Unsafe.Add(ref topURef, uvPos), ref Unsafe.Add(ref curURef, uvPos), ru); + UpSample32Pixels(ref Unsafe.Add(ref topVRef, uvPos), ref Unsafe.Add(ref curVRef, uvPos), rv); + ConvertYuvToBgrSse41(topY, topDst, ru, rv, pos, xStep); + } } // Process last block. @@ -205,9 +217,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy if (bottomY != default) { bottomY.Slice(pos, len - pos).CopyTo(tmpBottom); + ConvertYuvToBgrWithBottomYSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); + } + else + { + ConvertYuvToBgrSse41(tmpTop, tmpTopDst, ru, rv, 0, xStep); } - ConvertYuvToBgrSse41(tmpTop, tmpBottom, tmpTopDst, tmpBottomDst, ru, rv, 0, xStep); tmpTopDst.Slice(0, (len - pos) * xStep).CopyTo(topDst.Slice(pos * xStep)); if (bottomY != default) { @@ -588,14 +604,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy #if SUPPORTS_RUNTIME_INTRINSICS - private static void ConvertYuvToBgrSse41(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) + [MethodImpl(InliningOptions.ShortMethod)] + private static void ConvertYuvToBgrSse41(Span topY, Span topDst, Span ru, Span rv, int curX, int step) => YuvToBgrSse41(topY.Slice(curX), ru, rv, topDst.Slice(curX * step)); + + [MethodImpl(InliningOptions.ShortMethod)] + private static void ConvertYuvToBgrWithBottomYSse41(Span topY, Span bottomY, Span topDst, Span bottomDst, Span ru, Span rv, int curX, int step) { YuvToBgrSse41(topY.Slice(curX), ru, rv, topDst.Slice(curX * step)); - - if (bottomY != null) - { - YuvToBgrSse41(bottomY.Slice(curX), ru.Slice(64), rv.Slice(64), bottomDst.Slice(curX * step)); - } + YuvToBgrSse41(bottomY.Slice(curX), ru.Slice(64), rv.Slice(64), bottomDst.Slice(curX * step)); } private static void YuvToBgrSse41(Span y, Span u, Span v, Span dst) From 6293f72c809ab18ba844ba33ba0801474f6e81c5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 13:57:23 +0100 Subject: [PATCH 208/511] Use ref parameters in ConvertYuv444ToBgrSse41 --- .../Formats/Webp/Lossy/YuvConversion.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index a8286037b..cf211c16e 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -616,10 +616,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy private static void YuvToBgrSse41(Span y, Span u, Span v, Span dst) { - ConvertYuv444ToRgbSse41(y, u, v, out Vector128 r0, out Vector128 g0, out Vector128 b0); - ConvertYuv444ToRgbSse41(y.Slice(8), u.Slice(8), v.Slice(8), out Vector128 r1, out Vector128 g1, out Vector128 b1); - ConvertYuv444ToRgbSse41(y.Slice(16), u.Slice(16), v.Slice(16), out Vector128 r2, out Vector128 g2, out Vector128 b2); - ConvertYuv444ToRgbSse41(y.Slice(24), u.Slice(24), v.Slice(24), out Vector128 r3, out Vector128 g3, out Vector128 b3); + ref byte yRef = ref MemoryMarshal.GetReference(y); + ref byte uRef = ref MemoryMarshal.GetReference(u); + ref byte vRef = ref MemoryMarshal.GetReference(v); + ConvertYuv444ToBgrSse41(ref yRef, ref uRef, ref vRef, out Vector128 r0, out Vector128 g0, out Vector128 b0); + ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 8), ref Unsafe.Add(ref uRef, 8), ref Unsafe.Add(ref vRef, 8), out Vector128 r1, out Vector128 g1, out Vector128 b1); + ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 16), ref Unsafe.Add(ref uRef, 16), ref Unsafe.Add(ref vRef, 16), out Vector128 r2, out Vector128 g2, out Vector128 b2); + ConvertYuv444ToBgrSse41(ref Unsafe.Add(ref yRef, 24), ref Unsafe.Add(ref uRef, 24), ref Unsafe.Add(ref vRef, 24), out Vector128 r3, out Vector128 g3, out Vector128 b3); // Cast to 8b and store as BBBBGGGGRRRR. Vector128 bgr0 = Sse2.PackUnsignedSaturate(b0, b1); @@ -733,12 +736,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy output5 = Ssse3.Shuffle(input1, shuffle2); } - // Convert 32 samples of YUV444 to R/G/B - private static void ConvertYuv444ToRgbSse41(Span y, Span u, Span v, out Vector128 r, out Vector128 g, out Vector128 b) + // Convert 32 samples of YUV444 to B/G/R + private static void ConvertYuv444ToBgrSse41(ref byte y, ref byte u, ref byte v, out Vector128 r, out Vector128 g, out Vector128 b) { - Vector128 y0 = LoadHigh(ref MemoryMarshal.GetReference(y)); - Vector128 u0 = LoadHigh(ref MemoryMarshal.GetReference(u)); - Vector128 v0 = LoadHigh(ref MemoryMarshal.GetReference(v)); + Vector128 y0 = LoadHigh(ref y); + Vector128 u0 = LoadHigh(ref u); + Vector128 v0 = LoadHigh(ref v); Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16()); Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); From 2ca81aec3c83b36060bdd021a5f52688778fab6e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 14:07:57 +0100 Subject: [PATCH 209/511] Fill buffers with default values only in Debug mode --- src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs index d62d23e17..14bc19e8a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs @@ -76,10 +76,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.TmpVBuffer = memoryAllocator.Allocate((int)width); this.Pixels = memoryAllocator.Allocate((int)(width * height * 4)); +#if DEBUG + // Filling those buffers with 205, is only useful for debugging, + // so the default values are the same as the reference libwebp implementation. this.YuvBuffer.Memory.Span.Fill(205); this.CacheY.Memory.Span.Fill(205); this.CacheU.Memory.Span.Fill(205); this.CacheV.Memory.Span.Fill(205); +#endif this.Vp8BitReaders = new Vp8BitReader[WebpConstants.MaxNumPartitions]; } From cded607d5cd0d30bc381f08170f9e7f9dc8d91e8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 14:27:41 +0100 Subject: [PATCH 210/511] Allocate clean buffers --- src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs index 14bc19e8a..003bdc268 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs @@ -66,15 +66,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int extraRows = WebpConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter int extraY = extraRows * this.CacheYStride; int extraUv = extraRows / 2 * this.CacheUvStride; - this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); - this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); + this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY, AllocationOptions.Clean); + this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY, AllocationOptions.Clean); int cacheUvSize = (16 * this.CacheUvStride) + extraUv; - this.CacheU = memoryAllocator.Allocate(cacheUvSize); - this.CacheV = memoryAllocator.Allocate(cacheUvSize); - this.TmpYBuffer = memoryAllocator.Allocate((int)width); - this.TmpUBuffer = memoryAllocator.Allocate((int)width); - this.TmpVBuffer = memoryAllocator.Allocate((int)width); - this.Pixels = memoryAllocator.Allocate((int)(width * height * 4)); + this.CacheU = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); + this.CacheV = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); + this.TmpYBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); + this.TmpUBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); + this.TmpVBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); + this.Pixels = memoryAllocator.Allocate((int)(width * height * 4), AllocationOptions.Clean); #if DEBUG // Filling those buffers with 205, is only useful for debugging, From 22537b226b6c9f7517ec1cee670157f756abbecf Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 15:31:40 +0100 Subject: [PATCH 211/511] Revert "Allocate clean buffers": the tmp buffers does not need to be clean, they will be overwritten anyway This reverts commit cded607d5cd0d30bc381f08170f9e7f9dc8d91e8. --- src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs index 003bdc268..14bc19e8a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Decoder.cs @@ -66,15 +66,15 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy int extraRows = WebpConstants.FilterExtraRows[(int)LoopFilter.Complex]; // assuming worst case: complex filter int extraY = extraRows * this.CacheYStride; int extraUv = extraRows / 2 * this.CacheUvStride; - this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY, AllocationOptions.Clean); - this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY, AllocationOptions.Clean); + this.YuvBuffer = memoryAllocator.Allocate((WebpConstants.Bps * 17) + (WebpConstants.Bps * 9) + extraY); + this.CacheY = memoryAllocator.Allocate((16 * this.CacheYStride) + extraY); int cacheUvSize = (16 * this.CacheUvStride) + extraUv; - this.CacheU = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); - this.CacheV = memoryAllocator.Allocate(cacheUvSize, AllocationOptions.Clean); - this.TmpYBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); - this.TmpUBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); - this.TmpVBuffer = memoryAllocator.Allocate((int)width, AllocationOptions.Clean); - this.Pixels = memoryAllocator.Allocate((int)(width * height * 4), AllocationOptions.Clean); + this.CacheU = memoryAllocator.Allocate(cacheUvSize); + this.CacheV = memoryAllocator.Allocate(cacheUvSize); + this.TmpYBuffer = memoryAllocator.Allocate((int)width); + this.TmpUBuffer = memoryAllocator.Allocate((int)width); + this.TmpVBuffer = memoryAllocator.Allocate((int)width); + this.Pixels = memoryAllocator.Allocate((int)(width * height * 4)); #if DEBUG // Filling those buffers with 205, is only useful for debugging, From c174ab42bea366257a410e8335055b9d27d487ff Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 24 Nov 2021 20:13:46 +0100 Subject: [PATCH 212/511] Remove SSE2 version of VectorMismatch: Profiling does not show any speedup --- .../Formats/Webp/Lossless/LosslessUtils.cs | 63 +------------------ .../Formats/WebP/LosslessUtilsTests.cs | 28 +-------- 2 files changed, 4 insertions(+), 87 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index 319aa8c3d..0ed180a18 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -83,69 +83,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless ref uint array1Ref = ref MemoryMarshal.GetReference(array1); ref uint array2Ref = ref MemoryMarshal.GetReference(array2); -#if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) + while (matchLen < length && Unsafe.Add(ref array1Ref, matchLen) == Unsafe.Add(ref array2Ref, matchLen)) { - if (length >= 12) - { - Vector128 a0 = Unsafe.As>(ref array1Ref); - Vector128 a1 = Unsafe.As>(ref array2Ref); - - do - { - // Loop unrolling and early load both provide a speedup. - Vector128 cmpA = Sse2.CompareEqual(a0, a1); - Vector128 b0 = Unsafe.As>(ref Unsafe.Add(ref array1Ref, matchLen + 4)); - Vector128 b1 = Unsafe.As>(ref Unsafe.Add(ref array2Ref, matchLen + 4)); - if (Sse2.MoveMask(cmpA.AsByte()) != 0xffff) - { - break; - } - - matchLen += 4; - - Vector128 cmpB = Sse2.CompareEqual(b0, b1); - a0 = Unsafe.As>(ref Unsafe.Add(ref array1Ref, matchLen + 4)); - a1 = Unsafe.As>(ref Unsafe.Add(ref array2Ref, matchLen + 4)); - if (Sse2.MoveMask(cmpB.AsByte()) != 0xffff) - { - break; - } - - matchLen += 4; - } - while (matchLen + 12 < length); - } - else - { - // Unroll the potential first two loops. - if (length >= 4 - && Sse2.MoveMask( - Sse2.CompareEqual( - Unsafe.As>(ref array1Ref), - Unsafe.As>(ref array2Ref)).AsByte()) == 0xffff) - { - matchLen = 4; - if (length >= 8 - && Sse2.MoveMask( - Sse2.CompareEqual( - Unsafe.As>(ref Unsafe.Add(ref array1Ref, 4)), - Unsafe.As>(ref Unsafe.Add(ref array2Ref, 4))).AsByte()) == 0xffff) - { - matchLen = 8; - } - } - } + matchLen++; } -#endif - { - while (matchLen < length && Unsafe.Add(ref array1Ref, matchLen) == Unsafe.Add(ref array2Ref, matchLen)) - { - matchLen++; - } - return matchLen; - } + return matchLen; } [MethodImpl(InliningOptions.ShortMethod)] diff --git a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs index b6f15e537..62e23c1cd 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/LosslessUtilsTests.cs @@ -10,24 +10,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class LosslessUtilsTests { - private static void RunVectorMismatchTest() - { - uint[] array1 = { 4278193152, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896 }; - uint[] array2 = { 4278193152, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896, 4278192896 }; - int expected1 = 18; - - // Test unroll first two loops path also. - uint[] array3 = { 4279238656, 4278714368, 4279238656, 4279238656, 4279238656, 4279238656, 4279238896, 4279238896, 4279238884 }; - uint[] array4 = { 4279238656, 4278714368, 4279238656, 4279238656, 4278190080, 4278190080, 4278190080, 4278190080, 4278190080 }; - int expected2 = 4; - - int actual1 = LosslessUtils.VectorMismatch(array1, array2, 18); - int actual2 = LosslessUtils.VectorMismatch(array3, array4, 9); - - Assert.Equal(expected1, actual1); - Assert.Equal(expected2, actual2); - } - private static void RunSubtractGreenTest() { uint[] pixelData = @@ -211,9 +193,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } - [Fact] - public void VectorMismatch_Works() => RunVectorMismatchTest(); - [Fact] public void Predictor11_Works() => RunPredictor11Test(); @@ -236,12 +215,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp public void TransformColorInverse_Works() => RunTransformColorInverseTest(); #if SUPPORTS_RUNTIME_INTRINSICS - [Fact] - public void VectorMismatch_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVectorMismatchTest, HwIntrinsics.AllowAll); - - [Fact] - public void VectorMismatch_WithoutSSE2_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunVectorMismatchTest, HwIntrinsics.DisableSSE2); - + [Fact] public void Predictor11_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunPredictor11Test, HwIntrinsics.AllowAll); From 77e77008577d34f7b8534c130806ac2c97f287b0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Wed, 24 Nov 2021 21:07:30 +0100 Subject: [PATCH 213/511] 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 214/511] 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 215/511] 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 216/511] 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 217/511] 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 zcmeI5Q4;L9Z2+|a;(KP3M@S=Zod zuI1@yUC)p(fPvpJpuZx=e1882OV8%Gnq=p5TwT5L*Q(|+#{dRa7+Cp++OLR?<~r3R zJFjys$47OpR9d2}GGYJ&_b{;j4Yfoa&Fxr|?7Ysk93R!WTcxM%@f?$C4E(BrmA}z; zhgZCzeWt9|pPdl;W0l*d$!e9l)d`~=XQbyDQ?Z@vE6g$Q zDFbUyrKS03ZjYX3<2u`NeDoylRlROb>g=u3TixS1Ce;|2GqC=7DhYHP|rHP zf_nv1j9kT@<5G=*x&b-c?O0{DB)?K6Z*`t!<7>z>)U%G~xLftQ-S53vo{whw^(-6D zkg3H$)qp*PKDrlm@5c9N<7&re>^bIEZF`hxo-2fzzn=?3M(M5{C8IXBJiSt5`l^*4 zJe&AV239?3eFockb;m79uT-g0r&)F`88hR0;@-_w+?l=hYHgo7>Sf2*G^w4(IsHsa z?B}rh4F=XeX-o4_)tBekxJs2eOVg2L%#W-7eDR%J#r5}@iM7#VK0_S+0zT+7UNq&H z&-F|79Ss~$SI_F5YD#}zW%p^@YL%$#`gEPae9nM1JjdFrEzw8yJ9yF&*O^q_(ZKO! z{jA=tC)xN4Zk=f>S9NNsMB2cbKdpA6eO^A&cg@W`>4m4V~Q`q`_> z@pVtK@wo2w6-$!nbzLrlvzhbtxokNB{ju6tpC+qSs?-T*A7z#C69(+F@KLoR&#`fp zDs^jVLS=lW0ec#BRNs%LwDUT5^e>h(L~3UYv`<&B)XAD+=XIv7hXWboEM|tSNS0XHtoQo&kHlBh5pw?F#~d^GFU@@8q&W8mDtz26G^Hu&g# zXIvEn)|;$WM@#cjm1^r&ok~k3!oXDq{Ci;I>Rm0xM^)-C)p=)oJY%3wl+_tBwHSD= z0eJ)3vAU<0XKi1bF>noa zrXy-G@OcCM_hP26K2cU@$iGl4p8DgAf%U|hZm7k;T@2VaW40?x^U*B*+v~kA8_({3 zEp4_N>M?L11MB|#oqfx6bf4YcwG(Ztov-P7-IHF^4-^c1lY#cVm6dPW z;NO?8cg!6y@QVhn`Bk-!YQKFF{o<2i<eKIs@`8*~eAfwNy*f(W-voU;qP8FtF}7 z)gOQ8FUc{VwQ!L1Y=){ic0NNrYF9cvsLU{cfprE}zxkG@qnVyH$Ih!%OYu>ay4P9S z9E)lUVBi`9_C1wo?p{J^#~E%*la(q}>ic7rdeyJrdOuWR;JXa0`g>LX&EHn*zQ)WM z?iJK4n4_=X%--wIf=Ud0mjV0E&hE|9bW|s6N;|J|TasR>P<4HOUZL{+t68H81FsCM zd`q``^?9=Ts+-Yusqc@Ys#oiMnykL6?q8Sc{@Aa1wc2ON>Z@w5V*mqv1M&{FV?Y1( zYD>~9uj_jmn$Vy3s$Q-2NqY4ap?6)^xAR`b>$R4nS6MXeyZQGjcfb23?J2)y2=vu73LVYmw}aM)v|n4*|%r3afSLVf2+P?@0;No zcdu%n&@6GL8)`8yHn94v_Ia{8?&Iu=RQJcT%Cl>Inyj8xo?Vmr{&-e-cCAm_R?n)= zu8oN^)a{OA@>xGz!@&CutUs$`V*S0kg1NaKb7!8!JE`?P$6an$>fEcW&U8dA2KolZ zZ$Ur*)#{ihS6&f%*QK^U_9|j)ecrYjtM6xBL*4FW$^E-WS1rp&QA(TVNj7ftS;4@k z4A`^jqgq#!*`{VAAZQT&aP;Jou5@juC1j-?)d#VtDRb&kE%XB=ZLFB>Yiudco<}j^mp1D=kT{oYzLKt~slbJCTEdi~)Vl+j)j0xh+MnNETz=fc9zURi>r* zs7j>{1NS%JPx}#9dOxa1vkH}(!@$VvzJBE8W@BoOLpBDUWnlHmAM>^=&vJ9eZm;n> zrryy3Jz-#-f&SUA^7}Mdtx~BI17fm2N>+Yc&C7Q{1qME4z@Gk@E-cAMGh}N0e^2#x zw|K^q<;{?&#lZ6n%)LFbU;7s6NHQ4f2DHz1UgsW_YfizzV}^lWGSI%2vhqt7uG*`( z?2lL76)pxa5H-NN88wy9>${cJJdX+tU|_ugc_a66J^y!!)~t?x*9kkvz#R?nZ%ON) zivHr+`TBc7!~g~`a4!S)t0&RDdZZf+U;qPG8IZ5uK3;VfxER0y2JUKre_y!klc6sR zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~Bl=2Cn$8R97&+`5CY`3}7H)VC9$P zivKO8E0~*?mHWPe7kPH%U;qPE18cu5mL^fPx4lj%cAR7S|Ep@VwDr9Cjv!zF17`;8 z7e%78it}qiXvg!qv#TvjR?e!1cHC^L0&V=jXMft1U;bjH=Gp zHNno$>qb{CLr0?u)-iyAyaD}g9rHYAt2pX=vkJ4vIo2w~Cv}yQhuH=d5yr(c%?>o=a71pV~ z!W;w7F;MxQ_}$re!ACy(RqX5A_!XhO-ZJz`n;%)xgmxYw*qoN(S8V>P$XB=VE5hh{ zOWIaO*{oyWjt1;0p6Sq%e3U19f{pXMEB>x}1=H@+NBMnwf{pXMy~0v-R3%b}f%h8F zr}mgvdpHQaS)SUX9BX;r72m2Wn8zM_v;4k$lw&Q=+ba%&pH+#}Vc?wx{JA~iN*71< zXqKgpIQx2*w&#zTHQrXWnmOieSMq(X=AG}k)?ZIEy+g}vS--1LL9wkHP%tJqO*gx&(<<6O(K8Kcq{}v#_MgS zrSu}3AB%dN>K8wDN3xz|VD0(n(`2>gMXKAe#@nkbZH8oPuXwjsGFR_=CC{>aWIe|s z&p6^(Jz3vv;5ZL{R@t{_wDDSMg=TA3epxK}-qGrPui>@lMXywh>Uuk`&}_|`JzFNx zdkxq#p`%(Kn$XT`%&Y#sTJ46qs|>U!Mz7@Ws;k?1o+lNSC98~2Ghm;ik1D(I3>#O7 zQnQXEM8^9WuuoY>m0opyJFgJ=v$$sMimLhF-|d;uE0vygeLJrZZO#0iEsyA42HG}!rM5p-h*D#TvdV~ocN(y#&`0@BJi*3!9u+wQ z*0+yxtS#FUY@Fx$iupgmJ&_m#83Xo2>nKCnb9Hrpo@Y{#G0?Xzs~M8yN^Ltz1|tTZ zYoI;XvQpWT)b_^;k(!KwzIR#8kR(@X+fgzYG4Nyq?aAI&YCGz)WVObmDr!JHkH;uU z=k;kigE02 z4A{5umv;W?y;}1+`gCX99|ON-fZrUSc;5P(cg#Q03HQVR1~Bk`1IKr7o4x=3+z|s9 zz`$n>@a6lgf9{R}3}65Q7{CAqFn|FJU;qOcz`!~K{5!`wmyj@k0c&9O_hlXLn*YwV zu0QLM2m=^+l7ZFVmz6xrl4#DZQ1)?-CDnXQ#RMY;o@2m%3nZ%dF9bVQd6uH1wZtlI ztJ7Wa6`V0iEe76ez`naWs`a4>cK%De+Fq%`00uG!=6`M2Z{VdKP69X8?8nEwMwp#Kn#Yd7AA3edwagJo#dLKzveDnkx$2pQ| z>vbdF}JG-S1l3ad*o-)|p}8=>{ra6}xBqcIe3Fy^eHkfBu@GGx`j@sb>g#QsrTPC&5KF6k z`8~nLpY6}~(u-F1muFFdfx8&!y&aYB!Rjwezb{$M+mp)v_-(xYE@d^p+q0)zp;3c@ zI~b5Rq8;}gXo~50=zZ*TzJ0q^8Rr+g<>6Ltcy1Jd`c~oHF4hGt@EGyaW zq;5Q}BW6i^{<_ZY&l~S`yOQ^}nx{Lc^rP#U|Ehu6le6|zucfX2eYKu9B34i1*k6VC zF4}gVN`G68?XgOs?)MC=IXRZ%qpVe5Z{sX&%eMrHDx|$e2zIRVw#w^RLay~E)$N$) zwG6+qikat7@oNVB+1ulag+57EvqY)uk6D`JTbiua$JM;OllpHUzms+SS&~Hlez91p z`(uouv-+HERcA5BfH$!2-mCtz*H`stpG3BcBj0j- zbmT>6o^ZrbLO*Ynw?3WK&l@;Sil60s^8_2OIAmXGV8xTMj9!%8L65PoWl7}!f&qJ8synt6ALXdN+QvB+ z+1D7b&qSiiy`;83t|L}R)clHp+28j4(<`evi&E7eb1bs2FwlQ8+iL!9V^)o~cVMac zuBgC3#(+I59ttSl2VhK-7RfwRRpQ?0L4f z7v*?luQt$oCdXR-Zbz-3GI~bV#cW3)9=;#i+`}H35^JwRDc4kk`+})|hz#R;*qUPb6X8(F>Tu5dB%TL$Ef+Q;A8`@MH-pNx*~-8bD~;AsZ<7oqz-G5y7} z^ZWHmM;O2W2G$y|UpUf!{T-@;kGJwfuiw^To<#))Fi(Q z+1s3dqish%%5~_gZTvabnXz0~WMcpWH3Rl*AW?-Swf!+i>@#E))++=2f_!C&>lj%3 z3q-z7{V`{|&ydv&OKQgBI$DNw=6>rn$2zj@K96-~82Cm5^*3d+Vc!-VWnJmo{ya|X z*Xz|d>uir3aUE$bEz_6eYD33dt}6iE!Wo{ zahCK8tiw)kmNnL69BWyc&d>hYrE8gEpl4v_OdY4XH|yPPwAQOX)<#w1^`q9$#x$YA;n=M~z+c8UP34Ub_F-svI1BU^BCinQb9-H+k zvKnLTSyIy$o-4~`^0pV>ql3%^BjwRwafbY zwfwnXSMB-KD;bx%uAOH{Ud3q5*V=eg z#rjEqYZnB!|N2=N}#~~X7*BfY0>9&%+Z=WKoSsMB8H6YJv zJm&i8b8-x_G4L(}_Pkc^Y6(875UF{m0sq8oTp04YY5Kth~1m?p84%&#FIGh}2-c*8R)F9ZC0gPj?u=z^4qX`!7#- zB<)t$eUm@+l(;VjFyIZ$zSs7PC6T{NB$iq`Mk;-dB}nA6qOsKPW3-Aq3}E2S23EY+ zmZ78U&ULMwe~!fe4}+|G@-grZ19$n=X1`QA%39F1cK!{d>>kL+00w#n=DrO((K2+@ zyW@CGSGMzUU3;ai)+=rPctzK>^Ko78N?R{0y=>MnfPpIw$lGVf{2gt*j&dwr+0HYp zwpy<|on-r;{q1gPL>TxD1Nw_#=ehmcS|4RtzShQ&yn(tOPa?*^^9;=W$#35t9py~u z%61-SwY7dF%JOw>{C?8tzkNt=(T*ff>g*_288Pt92Ifz_CHN@g&ez%)(r&<7=|yWy zWhmrg;CTjm?@s=gV4otZaVt_W9#^sAoRz<|#{1f?e5R+f{bf2L7Xwc-Fn%9%I~j96 z_g>Re>=~}}6uTc;7`Tgp@!8LGIHt&zI87@0W1O>P_8Hr1j&n!ScyHPLts`lxdB?h% zIR>Hz>L+d8$-g7%j$f@i_O$NV?I+gqqNkWV3}g+=o-|wUqf9rx*2Wpq$}hx-mFs8E zwmXvOlO*Y(9W!0E)qZ83mLZUffrtTrO7}QYdF1pNvU=pbo=HuAd|j_I`iyN=XT|51 z7q4qG??$ryNYwUyz9Mz~@x0bv?Gv_Dn@5C!IRh(B+L$3%GCfL7J7y?*Zp+wK@*G_; zTYs?bMY67rF5@#x@KKyI`nA0gGZdRUQ)@{?-($d^(j(67@u=#}GNkoq-*%Mi<@d5@ zz7p27kK!(UrH$hp$(*eh4&%2OuqVSu@!k4L8^<|&rmdDJOR@Pg@3kcnjSa}NYR7R+ zd!?<`D{cPSimp87XBAxg9RtUS=*{9Ddez5ToO5Iz)!Xb?XLe@;_NnP8)3vT^=W9qA z3c2?-Q2#!(r%SJ7cCG8|JVWuh^__VV@p}x|XD?Ca-cmCj<uk|phTwY*Fxka59+0Lu?pE+sOds2si z>kPE-hOAt7PsrCBkf+!mufIQcz`!#M@P<6&IpwuRVjsVIufMW8 z`%HE8D+bsn1~LZt*PV>5yXWdJo}J&lbNa&o1~4#dz<%{4nx#^Y0SsW^83yF5w~x>0 znJzJa0Sv4&z`ty)a|sCp7{CAqFn|FJU;qQ&z^dQdt2n+tVhmsa1M3Z}`uEmVoNb4E zMb|%Ph#2@j19M*r`^}OlZ%HccnCJNaD|rylZ&7S+y z!Lo=jfPqy8=H5*Em60gho7A;qj?~u6N`@mf?Uu2G$xl-bOvk-b2^gd4^;weH178N*hNxw#r9Q%9iJA zk2piIxpTkPEsF>PPc~407mhd4&oVxIt&QU(Tj!&9bNu~n9PeiJ_pjbZre|_7aDM}{ z?}Dw@QD*PDuAN6oZKYp{k$hDfui(TOTUL92y%Oh)SvdyT82APQ`Yk%p zt3KAA$vOHT&7%7n)$i+3%9b}KWhmrg;Q0ntybEK-b|vcQoU)D-?dR+}@!W2<-H%Pj z-Pp|dQ?_+P82GG#74O2Bk@-%Xy_Zkstg&-W$@n=ZWly7}Wvj`@zzPG^KY#7%^--)d zU)9Dj&bsg487bP!p4aY0R^l9~=#S6kj4a1@L?#Ai4cNCTOS9EJiqd?YjiZ#&`y{kD z=Zuu0tm4QH+A-5jTdh~(Bwf|cS8(DCGBHp$(4O9HCBy3zWHm~X{QelFY*dL>HIHSIh~v3a$pY#BtqVxT>BvJy8S75#AqC(hV2XFF-{x9Y8$ z<=CCIeZF3?1RcGuJu-A%J3sQyXKkfkIj?Q6>e^Y)EuEG78mK;Pdt!VP-LJ2+ag;Lp zTjEBGGmgw^7YE_Kvzigvk2NEaT#b!V)gNPwvnRfBqm(Ugu8+)}$i0&R``ojg*=ip} zX}-?JQOcHQD<#Shd~WV7^3~rPi`#vy?9&o-6s75!{(PPkC6M<#19N9)+i#yBt8pVz z(T;J3WLk!-GG1X|=1&LvoOBfLR9Ch06`VMO%x4*B&x@=?_aXV?ah?<<_`I1n)3b=L zG0;CZnS1pKvKl2xet(Qo$a|K7{?p!8uzRt!` z3VBa5V4ty%qC4?*HjYvxZ?0M>jL$QW`7_t<&Yl?^*}StzSGDuAO0GTCK>M8aN_1Dc zrkzJAHn09JSPs!I7-&zOtVDM&`QtH4BJVi{#!q{@65q#|6K9Z#fx3bI3C!^N1X<0H z$bE(ZdA8#*x{rB6^iJeqU}V6a!co>}-B$Z(RK@y}4fv;QG5xfobsAoKSY zeLp#~{ZVH!{}u!NXDzGQT}s_PW~<4^z-JBE^Q)uJ_Ql<=GN7MvJFnff`sZ)$Zd74l zm4WsRkd;+;fO9tk@`T&*Zk^H(2Cg>Hz6IOL)pv#eIRpJC&Qt%~6XDLEHDKR}&-TLI zuQy5nEhVZdL6~x=qfvpGqy}yD^ZSdWY7GWa8Q_G00Y+oH3$g2;^b_ z1J@d`@3%y`drDP1#yM@RUWsvZMLUl%M&B*r%~PTTpO^pj=?TOb_)Y`WcSGLlc8q&! zYxPQ$qifoEjMCQWl^ElW-#@*J>XVM{o;(aZ$-vyZVXJi%cc81| z8CynMr&r#`*zct8eavHBkcEM7HgLQTdKTZYu4?B|PFty0-bvB-ZRfF0+B&}y;rJRG zM+ke4t^!<#Tjiq|#aFfQ3eNi&_xkzRsQsC2&$EvrUHY0fj!@S8 zUfS-|-d|Q?eO%Aj`ib=-o%hEWu6|3m zo}!(c^%7^SpjeYWiuU5`Y&=hi63D~AY6JExOBBD0RP@Ifr(Y|pF~*i973~;jj5&8< zY`cs0(wfjKF^aC}&u2NYda^LE+Ccw2Z>zC8jqBw~gd@5AF+$jL`t{ptoFSQ(C9An{ zo?!_RWx9}>c8pWnTD=nE=!$k8W7w?9Q?&%5UpLU6L|KWNk^KHRPl*!tytyvgd#!lK zW*K%*ZI9w7) z`TcrXjd3KqKgJkjeT#wqDcDw{I~)@-l+o|VXcy!?#eh98xvp)kk76ueW#brwtnV;j zpS_P_d-PQ{jxi)_rdAk?PcRVw^VROgo)8_`ym6#!+WEMSE8l0Jeg1kSwqIS*&SMOl zRed)sh3Fd%v?okfV!M^>@fhQf^$iBbr(nAh-Qk!LC6I@KsDb`zi&l^Rokgf|4w+9k zAkTa}#&$ScAG-@#7!U*Y#K~%Gw6#8pG06H31ODmTIKD?)CsDkfObp}=#Q%n(*>XDj zqt0ah?FRa1Kvr|Rm#Tft)sl^Y_ZhG!RY&jZfjiy7fPVJvywYLq&*aLTsKG$ZKzq_< zrN*P`xd!CPx8rm7MCTYdH_)E;ZRNa%tKVp#e-bzi-}tQAlQv*azP`fzmkii*Aki-w zV6VSxfM0#T`|OXM%U5q7k9B4kzyJn*!vKF4{>F1=ml(hR1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? zfB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n z00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO z0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD z3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAq zFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOc zzyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6( z00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC z0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n z1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q z7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? 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 zcmZ_02{=^!|Nno^nbnMK#@b{j6(QM)P>9@ADBUD2QWBMt3T;Y@O55E+w~8bcMbwR? zP+3AMV+oTrgE0%Uo;l}#bl>;q^ZovRzu#P!>!RkGIZfyNem`H&=i_O$9mUvS`M#*l z`=hoQY&m4$yKnpExOF?D4pNpZUTlDUQI^=*QHD-v3ZsoAQM%J9ihWOkhof@?tv`T)BrgKl;umW(eMjJy2bB^>O4bd8q&Dkzw@_CdA zj${#a@9Q)w?5()EdzBSK5f1_oflO6YQlydz5CGy88CI)yU#;jBqSIgRMOlz=CQ;8P zk*>2gkVbR^(syJIA*2zlb_Z58$nWXUuF0!RRqvwL$sWURd)*BJicrU6|~&mYD~%|#CSSm-MRrNqs0_(eaA30i3D?l0Kq7huNJF-gs|R$wWXZ&aMnVR6?lzfuh}>+G zSX)yZZi)EWBWuj0duW3NK>nD%m+TC9SGZiGdC*d~Wza_C32F$ib&P~PkX1oiRaAg^9_ zFn9zmRB|0{(X||G)>tmQ3!agA>5t_D1%tFbQnNLPy&qy3E*5UGM7&(l6^_UT1O7R} zhZcoB$U1ua7mXD&kBA$k`D8y%^Sn7PWhpq%F8&_8-8k`9L_wH`stUu0UPA5X()`W{8(Ny3_;pvy|>t>3ZAo%G{%gcfU6YHz%1vN}lIF36bHK zM|}-QfP$V&*pXW$A3BF71tN)5CY6W;lS7>!N^TtqbJ0@(NCv(~Uk)R}Pv<<9AhV=q zc=Db*UULr$}C*b;(g(@>kwCox|NIGj#!|6<#!sjuh>*Q~=eSW3t|lNKhH4x8cRz zi>Z5eY~C2Qar2J7sTc3QXz1n0P|3*qtQcoCP{DF%(Fi(T6s`p>NM)kkhjnn?ujE}( zWZmoH?)9K&oA@}%u624lutQX-Y1#AL%%G#&z-F=pe*jsh-uDXpgEMy{;^dBcJEN|C za!H6iy3`wWaYt9r7ak+Gz9$}r{k6KAR0AY;anW!#Ay`nWr(YvZ0R35$+ab3`ILCk- z`$KZi0&p%6a&wmA=;7r8S!iTK@21;`>(eG2#5xsJjd5y%e<%i84o4c%UxJ(Un%RcvaRj z;1>638eZH^*|=1n0uc3lQp$$p zqLz&1%Fu$uMwpZ9iPwnhKSZh6rp3MkmJDdGmM7z$aGi9%bH zWZ9^dN#lS>-d7jG5#hI$!0HZZwjnX%EArYOubuD>?Mbr$)z%e^qU;xYY^Z>$?Y8SR z140z$|7fYmzji7qE_&;ht^B!wEW=1sBoa>`ZiltfNHbn1sfeLSf>VBr1hZR z614IDuaU12M8j-p$7*2ZH{lV2%U50`5GO5Dvtf0b&g=fB2mEX}_)b?83C9ZCS+;%1EoeK# zX=A`LgN1&-=t3qe-d{0+@*1v1SgC?Ix|vVZ$v6ys_&hwpJg$N-Z$D-Z&fW7F?cA$I zj%Y{99RdF~*-cH&JID)tjY1@Y;LH9zPrqfvmhrjQ&~EB6bVBB3IPoV?&Lp4aYQ+oZ z?&Z@mo)QMnsc?>|vM&+8l>@my8EcU^^1FUL7hW+jTy~ETGW}Y6!6#k>5XJ5C0=JEg z#}Q5QU}hv~&hZgh*(#z+ScOPfwbKw(^*Y})jUCe$f48DRprV0e(C$;WpH$R;YVTln zw129vcyjyH?jT14MG&G{{eBTU_@|rBd#QqkJFA4EiZB)8sxsNg@j0YOCY)zVj2j=@ z26}aIqkstBrv=)tr$Y$$WZ}eOo{L`pJ8~Sdmhm$eDE~&h#8y4VQ8_nA02&Jj{eO^I zcW|whK<{ABK8SQO8l@OO@)zzR4t@tBG0sp>ZUhB*mVH= z(KO&99IV>ft`dpCb;jy~R^(F+oX;d*AW!-Qm zQ3IcgcsaI!)m_oZ0VT}~!td(Fsy*P;{f# z&p)C1TJTvb`1}LVFiOmC0bjibO4{*PCm6q@>$C@6;*KEU)Hgq^^}qI8GU|D2ac7VM zHIvuy1x1r$^yOk)SSS2w3E=!#JaKI?sJE{P=9Wb20;hhflkZ()!RFD?AW#Hjw4^@Gpa=~K*Ut{a%dN4C<3yt zMBjJ-wvVKvrwl>+n{4@~L{pF+aJvTSnf_ToycmyrS{?;~!15yL&V1L&Y>;wL%#rafqQYE=5j7Vvn3R@y@mL!4dE^6se! zAVyTpnEz$OQN5OJr_UxE#4Vhn6Ag-Gn!}EgU3B#oFxjCViFjE`cWLtutMm8h%j8~|dS3@yV2aCD7N_$4C6pm{H5z`c5Fx(^ zB%$ZQD9H;o^H%;AAe=oI0t9r5FKVcyOz<-ealWtM(TkRV&hC1NuqI& zwu0nf8wLde@KiM&lSS5c_IB2bOmx(!cmSd>Y=V;u+AuZQnHOb7AS_C#5lZTJ>j0J) zN8wk#IKxc-M2d>WMR8{U5HiRP2e$BA&DCB=qQLWL5^zx-q40Xx$W@#Hz5K2cC$q)W ztp&Ueo1nZ7>B`nwBmOq>-wmfvklWwlPD@-(ewGnp(Ienxc`SGqJ&y~SYO_+pG@Fjv zE&2PU;My@Es^*|m?=yd2qzvNKE&O(--+$HEImn(8 zOwUXUrr3j>jj!&f@AR`!$Aij?e$RYAg>tG7*$|12=ep3r%q5`V@j>{7JFvJ={$(w= z^u7F+nhvH-m8{yPLU8BNvtX?JGI#`yC6xU%ah06Ly=^C-m^S=fZsU(wuaoKG2hPxW z2ekOfDkE9YyH?T>nTOF&Oyi@waSz174|y?XQYl6cYG%iP$+l`v7r7MGYgHRnh5%O+RG_xAo38Z0OPry+45d z%==A|xIB+1dg2b6T;WfiR98958{P)FG)s;XojW+8V6r?{!I%4n=E*A|Z9@;@%Z7FQ zkr>tf$G~4LxZG}hUOQMlPQLpEENKFtFo_qY^n%fF^}g4H1L$UQ#f1Ib); zguF=8;HCVkivA>dcGZrg#3*!MEQ%k74slH0MGNmSTTc*uW&3Ivih zOdVH+?@Bsx_QItLXHO*U3SZ@DszC-Jl?DEX3Oj`8WX&-*CD3sDOM&ctECaN=Jtcf# z2RIbVzl1<`Rp=vqm8s-aH+6x?d;&I= zfM4#CEw&0ZQYTf9a_K9Yv_X0gM7wf+1g z1>;=!>ju1GCU^QzTfiu_3uR@xkVp>4K1?ISrAhAEI7rPZC@HI=eV8vnP!xf&ISQWr zM#3L%ugFRYvQmR^+U`lE!^rf9V-6&eOC}5LN;Lv(|D5J#8say6g>#J+lKA~=fiO19 z8=r>cD=r=WYGTsaZDci8Mc#(SDm1iH_rWf9@D6S6QPs&*4dK@L$nq74O~CA+8)C@2 zhG`C%One;Mp6on$(v~y`eh}F)h`e!>Ks$ zzR9hi*B8k}b^0mZ#4!zp&24B~j5^*edkpC~>qN$z?7NrqCR{}-f@OQiMkD39E zGlRtY9k{%1(ghA991YNCKLjp|J?D0}YA)keD0@yc8W?^MYyx&pe|E+v3cd#e?)7My zBjEM|W*uhWmgP>#YIo@a+My2ykm<%tLB=FN*(~tjo$AhU(X12^3Amg|4p$(W6)B?e z&gwe{1I?5Hl5x^$ zW{IRF!&eJbSQwg7$r57rRd;RS)zsMV)jl4sOIvGyxr0G->%mHu$S2dtDevo1M2weTU3c>Y{ALz(~4LYEr{obd&5`_D; z=yasJsTz59Nhm0(($(myN(!@!g{)@k9(lUxD;JUUKHY1t2nR5k!6Lq71QB-?9`rGy05}yRr!_lLF6BIaQCa<_ zy7I;2oJ%P?);Jld-~ft|&%wejF)~sTZ$ThAocfB4=Xen4(Jg5EHmX`On}0@~eqP+M z5{MjU`9eozFEq@*jkwUNxSsTn112`y0`R=p(NdUBXsHGtcM`I{@Ckj; zF>4%dOfUh}*4*ipi`s64Eu;Vxla=w;U$&1-!zc<%gaRIqClE?zf0!O=e|bHAr3v<& zg<&_^MDpG{Yt%rKghqHg%MsK+F)V%JFuNII|K(u=+&G*&le-y+7S`y@`#kDGe>>sG z==iGXD?JMqk1()n)#sn0u(Am~S>(~9i!gH(u?&{mc%y;yrql8DUzDQI?Fw&(7nlz< zYT1u`HrD^h-vAzv)S4+~!gY4Is3E~=b)|$sX`vHfdUgnDiq``SVk-Ia-kdOF%mwi| zRM^ge5#dOCRsQ9p@mn{n4g4)|?S`%KM=$4BwT}o9nA2W($Om&)jKgwz<^0MR13)jn z2^l(T3OE%?2NRUkPYZ^kaCUWYrlMJ`M7J#8 z3UDowurFHz28k_lPEq(=fbMhdBM;@Z-i=>wNChD(!@?tY`+*Y~S8v?BaW&(_f$hN_ z77Qu`QVo~K-)+U7`SF|&9he(l#F4io8UU7;*^(kxz$#zX9f7mJ-p7=BPF6FYmWQ0j z2Tpy|G4Ev>T8_L^43($iUJNnTq8qi@f8blofZ9r+xsc%D56{~!(u(+H2JC*WfQ=L)ns+nFQR@Ptr^^_46~sdll7^24rQv1hwt&VNStn zRnTNd$ryHNnd=r%0J5HIA z1XNvJz=7XyS0nhA$p(_t6!#9#oL5)Nlzv)@OPFa{f{mgV@oV@s8umXws4bt)hmw&H zwVrC+x#)EGE4TP{^Y?KPg3Peq-i%qQ2@y;-WHDu;hLpuP<+Viu(Q`k7 zW@0~#3F+mvXvreNUM_15uw{zrsBjA#_BqD)G zQczOYUFaORGyUO*LG17QN>jbHL5f{cjR5|VW(}!DzJ-5gn1XK4;qI+Oqa3)^3kV&Y z*bJ;6?{QJcMoxm!m?yXiz6zwtLsi;Z6=RT9M!XE-m(tlwfAxm3-jo#EHT8>Oub@n){esP zVCew|DrmU1WJ=ziG9McNdXe{`B&}m?!3Axl6yD%Q(7Tf#3$7jOaa7Ec9{`SEacwFZ zhcDt-TJf`if>Esq#B&K8X(--qBnx)9tH4gtoKDc3K##uc_J_X?nFHVXcx#KiS@}eOw=o9kX3b%P^Y7gGIRk#;iJI-7}E0Ju5uE}@e?u)!NJ3c9GK_k?# z$f>Z8dV;5wFs+%wR#46C#TB-KPreeak7-09-X>Ey!0j1l16Bjgd%A?`%dKV1Z<$&T z?iDrJkdBHv`~jb4K`0kZ%qgBdj9FPTy*p)X5cZlR*40VeFz$RyOByS(E2IT z>;w3Bh>ntMnnZiS^;0eL8MRzrYRR-Wt$}6WJaZe!nKJQ4qBO>?6TUtHTMKc$nWl)B zCu--9x?q}1pPLE2?xuyK@x+&GE0ey@I#w*E8KY)q3lJ-8TC9*RZE^*}`-PY0P-1%F z5)a7WSRdM+WCAd@U}MB!!M24YP;1$qdrf0fR5IRGS9JSKQtXzn_3PG$ZHY}fbGxXn zYg~d#$C~c#S*8V&7H%sT#0Hlw48SC*9ql`20C|+ay)l$ImxTS{plg#fU14#PFc#P{ zQ^PQ0vaEDI_nBC+UlQZda9a0kD3hy?XYNNd=d2!X7C{9Gv-heC&qWV zyc|xTcD(?eeIwjvk+Qm=GPzM4pMHFjc4S&V4DnhxeE}*T)bWyB1jM zZp}oeGbtT@XGKcl4{pJm6~J9l6t#pNb&p!weHM~82^;^|vIB0ULe zl&CJazzn}-ke@`ddIzVIoI5Ah;Lf3$xJYR(eyjW(a8=@^GguB?m|p0Ggy~Nop-mpA z^W%)JDf9N|iMQ(u570*MfK^PzUDz?hY*tw-`vXkG%=$R^0d*;B&>MeLT4zO0o#_by zonNE1KA?G)fR}9t>S3cbJ1>w1>PI~2howD*`#p7t0GVO4IyOD0w6?8_H8lEjbcof} zR$H2r9=qCxK?aCAp8E@Xq;UU(2z`(in8(J3d_B-En)#`N__ywVG%|m_=jw*N;4BXsCOwctH3*>v;#u@?P9$>JZ0LYK73&t^!OX$8e4N4 z;$y3l4u7%=0u#qnse2|GKAk#m33s}ip7Q+%47l#2jOr8 zva*KZ;uRZ@WL5R>5!rC{xitnjqIql?2l<}qLQo2MDdF72lA(ObKs<$URnoZz_^pS( z8}uI<^de-UmvCX?rwYN694HChMtR>!i$K>g#&6;ptAVn{SwYhB%Wq%*zy0>5M#EJK zoBrdsm#QslWI zZ1>Cev0p4qBeqomPdo5AzkHt+zVi87#RgGQvjEiJRB!=T*{iWueiX``@uJs{IjL6* zf-%9Hqh!@A#C(`0*;Nhtndy=J%CrM4_?17AzO_y^RTxAEjiQQ2Q9R=1T-^nV4j4$zeM zk4>Yh0BzVLp?v*-9w~le@u!YIUcvdb-z4E_77BR-?BPcR^t4j|P}YqYT7b=gWP8 zriX<7>*OEV__hilZ&))7-K6rRMP)W`KZPcMXVBAN#7wP`_H*PC)$BF$Cqw1*^l%(S z>m*y4YX{6b#X`TOnt}8+cYh%K`qL|C^V(&*ZoK}^g|WPLob|1#_Vr({{(4>8^o=!+ z<+U*P`|BIKmd)n1D^J(+kso*0&_T1*Zz${3Jisnj$Udn>i5s3it-|;dd2L3$ggnD6CO6XBW9b$y0Id|jdCYPI9{pq z4L(I4sQ%@vwi~aEx{6<=Zrwe+oOl6wI%iG+T4YQ~;f=&8FfzEjE2f~+J)XFHy((_W z$u^m&F@1$PK(*L-xn@v|O2<2@^DiEXUhlu$)ydJxb-Dlg=tCFtt2@S}sCcmE@XS}7_pgaZtrc)^%c65?&G!(_^iZ`1f)up)ZTjU=%h|D4k5TG1@JxnGF!ISb0)T#+b ztp>K6Qju&guvPHMnvwz6E+NEEjPIu~E(s>mb@6`X((zkPI9h1&6w-6!H%&mCO1bjS$)<7(iHl?CZhY~i#bW+#;L(U?B;tWlN*J?i3U)X744Et4cbZW#Boi9O)co>SPh(O z)=z|a&V`IOizynEOTHH;`_IFCY9)O;|E)>qZ|6TNuc)jjf0lpye9~5bJAEb0r=I7Z zT>M=kA1sP7!;@XkF_E8Ht~ia@&uGgg1mRqbvoLrHJIqbW@qcKBw@f9Fb zwqD_dz>oSw8Vw}D&$SFI;jQdmjLw(!F4RpvAZQg@2Y?~=kj*+H6s?)ECt zOz9d0rHBUf%N7OgbRPQv#V{YKG^d<}D-E@t!%0B2^a<5Z@Iqm|EEBjQb~hY*1l(oM zi9lSPkhK=_)%K_(;qgbH?iEn~8hFMe9~W6UOHblnjGEYSpAk31H=u3OLPghsemBxZ zq(o0G3#qZiZD9$o=#n;lLF?9$RW{eb6ZcnA@z%-jXXLEHT{g;qg0XKx)|(!V6q}o- zCx-jGnH|jT{^5yfY_5`WdfsFu_!=t!$~L4wecBzfz0gp#@eBqPegXv{jZ7egx_F*TLB3U9BI@#@kOAvGi0)B09t|@+f z3tAUMRF7@L5HM`N3A{Imaw-lwVXDb8{6aXl=3T#GpE~Wj)?B7jE+d0=oqXIFIhhP%u$e zq~bK(ZfM48(^@wD06Yo%>Wn@H@+0k|Sf}Kbg3K4*cz5)x9!%Tk$E} zt3f??;h*3k^f0#2PXIe5Wtyh%MSG!@J-mGct5@hNJJ7ElZCFRt*w=w}95Dg(VqQ&#;0aUB4w;RXh2(RbOy|kTcmn}BY(WI-YsM0lcOcpO&yEEblDn~v?kk$8fqHXaEa1q9l@&1a#=mgX7TeBS5z}xJ^AbW%V z3rA*PAC#E=c3_qxyA^JV5fE{I@i2~T5y*&dLp%4WlOsMO2}0vwbd?G38dyAP z=q9=ioIql57&D6j4VMXiKYQH?$D|+a2zxoBJBjn|N&8}zRAM{OmKY73!(|pal(SkL z(C|H5&q0OV&(lI}=a4~=MAI{K@bFs`vMywepNE5)9*qQoFy+8d3moc0HV~KGw$O+)K$jxxH z7`(rue7nAH zYWXbRejel77anYaC+~aUHR3zax;3+WJL|uE`(sU`w}12Pgx@8(fAj79C6@e~vwZs= zsiiOK`8VILae9z=kBQ6eA!TsphM|Fa>;fQDyqwY1rs+0Ssp2u-WMJ@K5DDxMv^(Pu z2zo<+WpyZa3%i!WtRoDZ+kH%{#u@-x>+^nKz+q;v2?gVd9YW8NbUo`!lel_pc^oHQyc53J~*?vwlK3|0_h z(F0JnES!)7#{-FQk=p$C;v}4Fn>bxb=Z@^#dYoz2%tVF>=#V`rdl$_B45G_9=*Y7; zTO~lzEac?V&uj@QpJDg5zI$2pF!z4$!=jh(T6@_ua#X_p{PbjqnIfQM8~1Dk<&;Mo z0F3B&vdL@*&?IAGCd(AJ{+sNMj#9d~%@vmSK;scuk)aFkV3MJCYZFXC?dUpq?D8UD$$in! zWSVN~)Yxu<)obLYs!5eB1zO8#nWxt{?nPASHyS=g-=bGxoPf$23Wx13*fc( z=vfy2Ni$g92o`*&921(m!-=H&E{)%?^FJNg1^s~}V2pf$vUNLeD-bfw#weV6q|gi( z`WdOhT(e^=qzOY@79=%F#f@ppQ~;XGzJhOjRK{*Axq2XMh0Q#MnlhE5K%pwDG3ME< z2s?1Kq>U{@`QHlmxzGTWWoeCKX=Bm?47O=PSjRADuL}7biZd%j>M^p2JsbjT<+WQY z7hqk11mq8B7m|+YrQbBV>Pd;PySeZf<$D3xSPnKn!Lz!qDorJ62@=&NE)v_Tz#Cjs zH_16LPvD}%?9jLT@zunjiL)A)CV$RgJVo;u^h{|-5V!=R(6>`nW7^S5`pGHoIbD3m2;f5n@1efDrdq$Y}TKnTZ-JI={!JcoWM10NT~~8 zeQ%nV;{<6%*Ggw9V@x1L=j19jBI+(mU2RUo0Ytj7olj`={*<({=gy|3?2iuhu`{L< z0UXVIb!t(!2w_(xJ1asaF_kmY+Gs7vDTgG@hnBdX>;#0p_rE1CRt_ z^?;$nR4F-B@_@94|8}lP12&XJa=!b5n?^VZWTU&Xo^52!A6`GUBhXG4^HS7oHeG!`iSj?D zdnkh@yKBV4^7TsO)qlZ*$(rPCJ+iw-(E;7`Sds$-??LBb;gR zuRe}=nlpm+ak5f2x*GiI<0Q}aaau2|_@|GP=ZRO3`-Z*``lpYxs0@9gPrHRYo$cf7 z!1kIuSRdyB*2g&p-sjsa;@t*{S*mfe#XjhAXVh~!Vr~-8RXI08$zap=i_N`|DAk^7 z!XYG7>BA3G^MMu}i{9QP^b%PTxMp-P5QrEZk0mTBMjC&E%(7=VSIhwO^FL5#nl(<* zBPCI909 zR`q~|QzL!M_STlxc4ptmlo0z9!NAA-lpu2@fMOn*(9e8UXDJPqg{aBq}nmZe8@TScrXLmh0C9I(`8|wG`!qYaGuaw0la8|iar5v z%1C3$+QOZBqCFZDnfRvn_|sxXGfn|^TWuy4Px~sCh{B+AXcooc8+RMFfp8OuX3x<2 z6@Yya%sQw=@O>tneio#Hvr7Ibg1fTA<`E$cmmN21`msSuG|}5q{VXr*+NFz^u4Uyt zt8VF?z;fs5{+b&*Tr?o!ys+#pSoAU33a1kEY+Crtm!Nf!1sB-^E9%i_X2b+8wmoj+ zesiE?q3575QHi4ObTMf&c4HNA%pAq1@$Z}PnG+g&M0z`g+R0-UsOzf#!Iu#J;Y(`% zzsr@Bc4f5~m9o?5g^I@g-X}tPS`rVC{x2|1Z9zYL@|^8~0zn zWWVx%`4XjtzxWc~M+4)wfBBM{fA|u@|KLkjxuQ`5+Sy@+t3%W@o=!O8Yr?$^k4$}fR?ksJ76-bWMdQh7Sbu1R`E$skYOxDs$Ek$3GopT{IveEu>m&;XC07x{1q zWj_j`LAII0^7?S9%C0VXi36~*QJ!a@bVk}_M@pBMYMXrI1>)XJJ?G zLgSuxZFk`vAXDUIF35!5eZpTGQ%&Kj9UZ6Mp5AJX_&TFLzxsn0@pbLgSgb$T+M(?s zc#8E03n@Mmzxsp6@yj{{`~U3^cEbPk2cP^$UqKa zRpYyqrG29T;GXGkp5W$T?jb7U^32avIyOcrg@1C?A3y&RFthTdG; z;c1NVdpL@^zM1V(Pj4S@&!x6z`sx^ug;O#1+;Q#AkW4=E@{o%XsJE$T68U!A4A93a z$Bp3%>d8N6E;FcyxWk)4&u`Ovz<|-drIfo^>G~j&ff*+IaD`m!MclhU-WXOM^0Ja` z(-XxQNJDLf^s_^xtX|^vQPl$yD>pbnp}9?YBXS7J75XVxaP3$O-Q>2MYrZbz7_HT ztYo(EWG6a$f3+IG2)fZOLdNT^ZE@5lVurM_gMVbgiA%Tc<~_)}d+XAPgh+n}W6ZuL zYCCSZRyU4_+HV9g0JYWkN72rc3$g0kfV{#Nns-YyxQk@+5UE~FOc6I(V+Zg}^75bU zCPrUbW+r`~)cj=_2RdQoEsvoM=7RM#09NjLTMkt~T{jg*E>Z^W;2PgSe@q#B!O8gY zp9`$Us}uu;MU<7C21C8KqF7-4*uYv~9gCk#)wv=55vNE?=tbU!QWXN8@?{?r&2bc` zlo}4oZ^_=_tW5w&S{B}$4rcuM^sl!cnwpzFy#4FxpBV=?d0S|a0D`vjj_ejb%Be|l zqTtLEKg#$|0~DyCZ;{>vnqu4!@eLhnGM}{$SUWJb9*7ma)iZ41tfUkP0~M>rtrq+& z$0x&H##4Vl?;3G;1}GOt6mE7wS5fj}Gn-U`WY@ui=xJ;) zen53)4SLOT0G6{HKrra?8hz)DaRAf2Y+F!2@iW5C#W(q`NTT7Y8uccJ=Nv zcVE=Cb^jQ~IDp|F-EDO*?w;Ab+IH?Q4j`ehTLSmzZ!*9*fLu26IZ+?9&E`#G9Dw&b z^tB5ZJj(%C&T;@=gDh8!1BeGsqX)t3vLKZvrg|_EtJqNmW{=YM3-zNUdhtA!-KKIs ztiin$4Kx!cD)y9vS7vN1rV6oqHy*kpi^N^W8sCvKA57IAp!YNwc}Ud~+{W+HocYkI zH%L$79LOb`EpJP;#8F+-K29TJRcQg32~C=7zwW@*r?p>yu(>lLi9{lr;j({xt$lj+ zz&iW6m@i};kXAK@On*#srQ$48+vMzQ7idmm5AtReG(SiBeIss36_Ur$xQ9MaGyO0V ziM8Rc<055upm;0~FPyTlnYjX%_b4Wc%>rkCC4-Bf+;o*EVi6{rw-Lr9aIgXQI{0FM z;RmOKS>oT6t2yo}FNNXIN#r4IVZ9`kXj3Q8VkjTujwjQUc4O}vL{<)aEfJK2VebFN zhXaW4bJpg?Dj-42c3sk~vgW>Vf#e@R5rHLw@xJD=TS@C|wFscf;>}r~g~-6ejacFk zRw9tR4pSxvVV}%yC0g=0_n0yxOI~L~OqJ9xq&-AVLgB)fD(;+W<=@0vU^?u_7g;2@gC>I1j_zwXZa zD~fZC<1@3JmB7-aV-Ezx2rCv45m1rP#1@J~!5+#*j6o4a5DS8eNC_$tf>==mL=cP= z4JCjGEM26tv@Nj9wwe1bn&h0E`w!gtanE7*%rGqP^FHtQ`Fvh9_H#fM0|RFtQg8+W zDtHm6zwt$Cn42jBA`g`=2s|nZM3VUNInfw@QCCQaH))c7uG! zdhCg@XeQlesA<8HjyA&;>|*Kx?2tljqrx$X)hcO#HZOuPc~Vp0ryVg$OYJ75eWIV^ z8*RgOY7IO?VkKMEn^?Ncu?p4ILrqJyUkgF%f2!RMIXczvgRBC1XIseffp{p|5MGta zmeqxUIY`%EI;FyRTnT$g=^ho!B-b{yF-mG*WjeZ8TzD3Jb}r24y_q_8{X&>3U;cGIW=_a>-!1qKohW z)qVPvx@q&6D-tfQ{6-;G)MyX6Hc7LM5$6i5`4EHR@?0=e;H3^PJeWU@jlYVt*Fp#y ztM*)YT-OgaAgFkTGd4WXKQKJTnUTli`2BT{FYH;RK|r+ZBVU1cAm%3TMPPTiWCb**A(}6~3tyha;|I&8F(FrroIzgf5EZgG*L&??TBud-#sXdfInpH+x7}1%QMwNX?OMk^LdcM66c>XDtf@cGnj~C_jR$F zKQyzt`q=VmDG8^iBI76LB?{0y^8?x4QepKOOOk^9ZMN)>00zb5ceHgMRXcvBe?QS2 z%vP)GK0&Y3)^rVSP{@`ZL=t5Iq(?lf1%pMzNQsTfObn$N?Eb3Z{C7}gE?l06v}KUm zvypcdNZPobJ9?O2)2hBzlmdr?PD=!Kjo`_t`&uO%OH*F(9J_BsKRGq_vl8RPfcWux zL_fR@>%O>>ptvciT`H)*<^kG-YD~Yl{ALi~Lq(k7?&i9;73CFg>zccVIp9V_W6k+- zeoQscc=WhdFOaq;ZBitxyx5Jkh3g~hABzW0FqD2Cn>tB1x{p0ypqz$ft9)I@@g!Ws zB0v`^98M8!Rd21KA4c8v#?z3BcSv?SDWeU^D~H}^PzK`ZgEz=6&!I=3RQ6)KRNMZT z=Z(gI>gp(*05A>vXG`^Ti=-E*<`uGJb=51v-UAff%ahaT=8*A~0kq-xVn}1xy&-I> zEYeOLCL1`0-mV1wPfW}k?rf+kEzEnCS6Eur&^gQ#W1wGJc{|k6fDEhKMV3urL-%%R zK#PwzpaWNoA@lU<$;-Nw1HHm4s_MzI3UlfOX_2m8>Fj<0W=MdKf(k1J^`7@fo9ZrX zuga%K(DN4ZjX`?+043u+(sgaFPaX56PaPt2((NxHAHk0Q-KP$d=Bh2NngwYn@HhLF zZu(rGI_-b;se?eDnr`eF^|6=qoYCA{na@e(noueqw|(cSdqo5|^S9~D(K_i@A^VAAn2!OwyL zQ%&|}^~k2*9Qztp^?D)X=WHP8Mh=Rzm&4Ar=>5gilhf^+;iLS96^fZ43%*bEkme@L zAs)g`5OUdzY{aL@byb9v5ypw>`5}DO=s_ww{NMTZZ|3std;ZS1$ANtN>VN0kpZ=3? zcc06*&&u=d3+M9fsc%00oo~Mj^6haT-+uS+eEX+2sdM@Eh4Os+>|DOx{hxgM(|_mN zSA%?e+~4{3J#+c?Z~mQc4`H7VC>#UhhY2f+uu^Ntx=_{;s6ufs5^?TrrIVP^4PsH#4G#kXLB}p6OCw(M%G3>phOB$ep4R z*dZ$fpIwR-tsop89{{A0v99%$>sTVmgI}V&fm5WgTY3??!(U-OlYo>pQDV8;2gHkh z5b1?Z&f^5l@k{pU@z2q~(MTfS)Kbnbk;wTa)+%LOS8}qPU-A)@p5AhP2^f8S3HT+& zDC@8?ZTl7a#7ANNpR6chTB+IW?tNZ_5g@D?wWiDbaS6N+#|)(kN>$eUJ$2dF0G}|9Pds@ zN_Wtd4z;2sc90c#;!MPUbi@bRG16;Kx`L&UzvI4ET{lrWZ<{0m#95i&@RHz{EZSMV z{$9y4FNskg7cb8PFW(Qm{J+A>`hl1A953}1Ug}-E>_70bAH>W4954M6y!5Z|(oe)o ze-|(PcD$T_;N?6DFXw}JIj_ab`8i(l40y?x;3e;am;4o8@^E;`C*mb9ikJK@Uh>p< z$+zQW-T*K2A9$I^!OMIUUgp*CGCzoyc}~2{*WzW~887qac;Nxyh0lN&UIt$HC3xY< z;Dzsl7v2(H_*Z!0k^NOC{=f6#@WSiE3qKJrJWIUrMe)LW#S4EIFFa(t@Tu{_3&#t; z9WOk6y!Z|9;+w#W{{t^R7QFaz@Zu}Ni$4l4J}9P77r!%JeA{^OpX0?x?;=Bb5qwT_6*98f>CzC1Wiv_ZG0TG?56kGy*} zSJ8T)lL@4ZcNhx`(Tw?wIAQl5;-WOc=s8`2L#~jQvxP>nx!#F#%YL>}gcz%jX4H*K z(V4#5XUUO=cRATG%}u{CHD}s5?K&Kp{H(Tb29=K2WgK12fE2BMF5{w|*KH`YEjc`4 zt^+~$+^8UJ5phqqFpe=l11((0xC2y_%+3L8#oXE52E7YupJ}lKP1Bf=?sg8}v zk;wsHgjs^U-T+zL7Z1l6!ORr4?9bzXQ{7C>fwx1e?*-u`>P>$=vcjPGqnUOU*Mo2Y%~4%cD!npqQ7-nvoZ!_az5gw==Hcx4 z4RcukKebX$9?w6b$+8V5dP(Hq_zLihs|${&puoOeS2Tp=0z}nf#oimcSTydNHqt&K=9yJ zX`6j)Hdrz8n(mFBB)sU;+5zB#$A!Cs-bt!;D)DgMHO;;MAHY`rPlR^ zau@*qeiORTp9{7Vldwy~t;4;JNa!?c6SSjCa79BgY)F^^tW|fo!e_2@ifwW)#?B2` zLL@EPd9`v>Dw}F8Oggr6y@{R*olHbv7)FR>x{993`klv;3R|aS($UJRI~S3NO9FD) zSZ}f|h3@)H$h`y1S{cHjFhz|kg03CVCf0N);@CU9m3RqDB5oHvr@IJ$131LPls8>! zyD%Tkk=saB1^l!De)bNkhH7$1A8%57ZxI?wpv)o7?=e56rUqqCX*?8-9RtFNSj1<# za)IVk^eX*pz(CSgislanDQHBog;`7h+53pKMw-AXZg+Z7{@d6sa$k|n;hV2I0d7e; zGybWyuClE7WpP<$UF)ZD;46}FI$zy9Y$Nv-ZHax$M|-Hr*cD+k{|ZH=1?Y7(m=S?OTGD#lvDGV z)PG57@{BS&f!tCEWsYcjpn=r#kNRu5xnytP_S`;Qs3(p7IFex`OKu!rG8RR9D^64BM8#uoTmZ- z&~4z&kVGL5r!0-S-)ma{Z^q)!-i!z82KneM4UH5D@Mc&QqD{V3)sQyqeXtH}0=yZ# z;y@D;aj|!b+?&x~o_^eOmC^t3W*DvVJf2?OFZX7ocrPZBOahB}z?)$L>jb~Y+Co&R zzD;PM74T+AQZzJfq4@^t4?cS{76WgFh4%YyBg?6WNR$M4Gj0RD67@VsGf1El!ehkE z;{?y~Wc>Ab=cp|uNFv|NLe7(s%6T$2DrG!Z@=ZBUre_pTYUDf_i&t1N;K{s1Sx1y< zo+aYREICi+f}AIF)fHHq^&G-7Ylde5du*_|;(1m|eC)Zi=VIeivYuBo4+2Kn>~KwH zn1dcLLAzd+^JFf_c`{j(;u24q@(~vL67Xb-u~!yyo(v%IjP}TRGB?StyfPIVIZp=o zxqg|pFykj8B`wr*JU#z8o=h+&LPxyY40T&B^VR2_CpT0Pqr|pGLodk(0r=_)8Vs=M z2}Jja58u+0(5D8B8`2tE$fZ&C!~(K@Dj7V#5HbTwn6CJ>RFe6z;&F^yeZzOTGE7i0 z-V|`|cK*AT-jN9|pU>w`jP$m=%fEdtV3V;50ajk_d!w3*jTav?CsEhNcL6z#8MN^H zpya7FWbs7S=mOc+NN+FPCu9p`wj!&SGAxteR!37>hd+JN_}{h9Ylplh4E9=laFJjZ)={D=2S zr)+uhh4(6B{l$9?n{KfoMa}VE#~c+N0^aKrzO5Fk9`Ig|(3>g<33I&H&Dd%u)YxB7 z;%EwE5(xyeou9&uLdwsb6B($>3DwLlyQ`Ei@ql1>U*HbZW$;&daj zZ!=ozOw~n?`qHEumUO=TfxUmu!?SZgqn;w+K#>);4qf zpg?hf>dM}>C5t;+bXRj85l+e6v_RQ$i>>J|vv81Kyp_8I%;VStBIHJF^Kwa$GLYM* za!ptAG66yI05J>j+tUHR{eYa`{s>EB7(B!ZOuz8kJ*u$QKy{*B<`;hZ#<+Spzdd4( z-)^wZCn62-+Xp8BaZnVd6TmB$x; zyJ-RT(14MKJ(Ba=4*-69I^efw0e*WVoXJ~h%1s5zVC5jm^3B*rK;yJWO+?$7*g8jS ztu5xZRB(pc@Rl4db=K^tRdx|&5P*}0OCU1h$(qNU50E;5*}LI-qY5@Yd( zfcT93+sN2!K)q%}6k{)t{^ICS=R1N{oV zCkeQE2pJ+5<=PHSXK6V3Z3Fd;V5u8syB2d?hcUM=kO7%HP%zo9MtyW9Z^A`AYCF&{ zV&23`!;V%%K~-#^`8NjW>)Xb11?m`&272DHnH90|Gr{>wVO4A%610!*7v z4w=zl)isBIc{?P+B$M6M`5E!2Lk|AniNW56JBR$Y-+(!pi9_UmoMfKa3hrk=&Rvlg@v|SNe1XP;fBZNf z<$fHuN{kh(LA3pKLRxpi82E86$^AGlBDWX<4yyiIual{H@BZb-d6$=Z(rc|g;HVmI ziF_gV<6HuM9ODyp(urSfi5kHytkO;H$NBh=ALoI_g7VLP9HN)#?q@#^cZC@*S?wNd&>J zofk~BEJyDzCH>GLeaM8qz0I4s??6`ccr+zrC2UlJw6so`g5S{Q3D6b1vo>nQN6C;Os{^}TJeGOB<#uVc0dBTJ=-9MI@vvA!nPXh zk6l#8fjYGRDwC+_l-`SSUZ1jNfP#G2#lm*33>A#BD)Ta~MTdt51qOwNM_03=}wA7=GA_*?f`?j>kq7E8^QRVtj+=2*24`%9J<+_@JZ1PCsjoneE6Kdu?8>Kb7$5^)AMB=Z*i6hlilTS$; z^!m!E`_O2hKR*suo*N`*Hft5_Dq67Ms{~2C1GJ?UeQpI=Jrs>zUJ4nX@5Xu)Z54TXmlU7tWVED 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 218/511] 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 219/511] 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 2d60b73b140b0ab8d6a85a941b0319c86be32d14 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 25 Nov 2021 13:28:35 +0100 Subject: [PATCH 220/511] Rename size to count, add DebugGuard --- .../Formats/Webp/Lossless/Vp8LHistogram.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs index ac8cc0f65..bfb8f40d4 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LHistogram.cs @@ -511,8 +511,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless return cost; } - private static void AddVector(Span a, Span b, Span output, int size) + private static void AddVector(Span a, Span b, Span output, int count) { + DebugGuard.MustBeGreaterThanOrEqualTo(a.Length, count, nameof(a.Length)); + DebugGuard.MustBeGreaterThanOrEqualTo(b.Length, count, nameof(b.Length)); + DebugGuard.MustBeGreaterThanOrEqualTo(output.Length, count, nameof(output.Length)); + #if SUPPORTS_RUNTIME_INTRINSICS if (Avx2.IsSupported) { @@ -521,7 +525,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless ref uint outputRef = ref MemoryMarshal.GetReference(output); int i; - for (i = 0; i + 32 <= size; i += 32) + for (i = 0; i + 32 <= count; i += 32) { // Load values. Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref aRef, i)); @@ -534,14 +538,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless Vector256 b3 = Unsafe.As>(ref Unsafe.Add(ref bRef, i + 24)); // Note we are adding uint32_t's as *signed* int32's (using _mm_add_epi32). But - // that's ok since the histogram values are less than 1<<28 (max picture size). + // that's ok since the histogram values are less than 1<<28 (max picture count). Unsafe.As>(ref Unsafe.Add(ref outputRef, i)) = Avx2.Add(a0, b0); Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 8)) = Avx2.Add(a1, b1); Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 16)) = Avx2.Add(a2, b2); Unsafe.As>(ref Unsafe.Add(ref outputRef, i + 24)) = Avx2.Add(a3, b3); } - for (; i < size; i++) + for (; i < count; i++) { output[i] = a[i] + b[i]; } @@ -549,7 +553,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless else #endif { - for (int i = 0; i < size; i++) + for (int i = 0; i < count; i++) { output[i] = a[i] + b[i]; } From d045df2f52c626d54764920fb3154755edc09ca2 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 13:42:41 +0100 Subject: [PATCH 221/511] 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 7775c343049e1640dfd699aff0d005355081f042 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 25 Nov 2021 14:18:53 +0100 Subject: [PATCH 222/511] Group loading y, u, v together --- .../Formats/Webp/Lossy/YuvConversion.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs index cf211c16e..16d458ed8 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/YuvConversion.cs @@ -739,9 +739,13 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Convert 32 samples of YUV444 to B/G/R private static void ConvertYuv444ToBgrSse41(ref byte y, ref byte u, ref byte v, out Vector128 r, out Vector128 g, out Vector128 b) { - Vector128 y0 = LoadHigh(ref y); - Vector128 u0 = LoadHigh(ref u); - Vector128 v0 = LoadHigh(ref v); + // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. + Vector128 y0 = Unsafe.As>(ref y); + Vector128 u0 = Unsafe.As>(ref u); + Vector128 v0 = Unsafe.As>(ref v); + y0 = Sse2.UnpackLow(Vector128.Zero, y0); + u0 = Sse2.UnpackLow(Vector128.Zero, u0); + v0 = Sse2.UnpackLow(Vector128.Zero, v0); Vector128 y1 = Sse2.MultiplyHigh(y0.AsUInt16(), K19077.AsUInt16()); Vector128 r0 = Sse2.MultiplyHigh(v0.AsUInt16(), K26149.AsUInt16()); @@ -765,13 +769,6 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy b = Sse2.ShiftRightLogical(b2.AsInt16(), 6); // range: [0, 34238] } - // Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. - [MethodImpl(InliningOptions.ShortMethod)] - private static Vector128 LoadHigh(ref byte src) - { - Vector128 tmp = Unsafe.As>(ref src); - return Sse2.UnpackLow(Vector128.Zero, tmp); - } #endif [MethodImpl(InliningOptions.ShortMethod)] From a8273901c196c33f500d60532c85292ef5412418 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 25 Nov 2021 14:17:19 +0100 Subject: [PATCH 223/511] 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 224/511] 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 225/511] 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 226/511] 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 227/511] 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 228/511] 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 229/511] 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 230/511] 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 231/511] 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 232/511] 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 233/511] 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 234/511] 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 235/511] 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 236/511] 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 237/511] 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 238/511] 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 239/511] 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 240/511] 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 241/511] 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 242/511] 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 243/511] 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 244/511] 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 245/511] 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 246/511] 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 247/511] 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 248/511] 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 249/511] 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 250/511] 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 251/511] 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 252/511] 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 253/511] 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 254/511] 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 255/511] 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 0f4bc33c60958f22700491de1d94977cf4b3d75c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 27 Nov 2021 12:05:32 +0300 Subject: [PATCH 256/511] Add exif tag tests --- .../Formats/Tiff/BigTiffMetadataTests.cs | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index 8e90aeea1..b81ddce4d 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -1,10 +1,15 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Collections.Generic; +using System.IO; using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; - +using SixLabors.ImageSharp.PixelFormats; using Xunit; +using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { @@ -101,5 +106,64 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(new[] { -1L, 2L, long.MinValue, 4L }, long8.GetValue()); Assert.Equal(ExifDataType.SignedLong8, long8.DataType); } + + [Theory] + [WithFile(RgbUncompressed, PixelTypes.Rgb24)] + public void ExifTags(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image input = provider.GetImage(); + + var testTags = new Dictionary + { + { new ExifTag((ExifTagValue)0xdd01), (ExifDataType.SingleFloat, new float[] { 1.2f, 2.3f, 4.5f }) }, + { new ExifTag((ExifTagValue)0xdd02), (ExifDataType.DoubleFloat, new double[] { 4.5, 6.7 }) }, + { new ExifTag((ExifTagValue)0xdd03), (ExifDataType.DoubleFloat, 8.903) }, + { new ExifTag((ExifTagValue)0xdd04), (ExifDataType.SignedByte, (sbyte)-3) }, + { new ExifTag((ExifTagValue)0xdd05), (ExifDataType.SignedByte, new sbyte[] { -3, 0, 5 }) }, + { new ExifTag((ExifTagValue)0xdd06), (ExifDataType.SignedLong, new int[] { int.MinValue, 1, int.MaxValue }) }, + { new ExifTag((ExifTagValue)0xdd07), (ExifDataType.Long, new uint[] { 0, 1, uint.MaxValue }) }, + { new ExifTag((ExifTagValue)0xdd08), (ExifDataType.Short, (ushort)1234) }, + //{ new ExifTag((ExifTagValue)0xdd09), (ExifDataType.Long8, ulong.MaxValue) }, + }; + + // arrange + var values = new List(); + foreach (KeyValuePair tag in testTags) + { + ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); + + Assert.True(newExifValue.TrySetValue(tag.Value.Value)); + values.Add(newExifValue); + } + + input.Frames.RootFrame.Metadata.ExifProfile = new ExifProfile(values, Array.Empty()); + + // act + var encoder = new TiffEncoder(); + using var memStream = new MemoryStream(); + input.Save(memStream, encoder); + + // assert + memStream.Position = 0; + using var output = Image.Load(memStream); + ImageFrameMetadata loadedFrameMetadata = output.Frames.RootFrame.Metadata; + foreach (KeyValuePair tag in testTags) + { + IExifValue exifValue = loadedFrameMetadata.ExifProfile.GetValueInternal(tag.Key); + Assert.NotNull(exifValue); + object value = exifValue.GetValue(); + + Assert.Equal(tag.Value.DataType, exifValue.DataType); + if (value is Array array) + { + Assert.Equal(array, tag.Value.Value as Array); + } + else + { + Assert.Equal(value, tag.Value.Value); + } + } + } } } From 494ca166cb1833ee139f2bc7968d8f87b0b4ee26 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 27 Nov 2021 12:33:43 +0300 Subject: [PATCH 257/511] cleanup --- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs index ff70eaf8a..aeadae255 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs @@ -8,8 +8,6 @@ using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; -using Xunit; - namespace SixLabors.ImageSharp.Tests.Formats.Tiff { public abstract class TiffDecoderBaseTester From 6aee72308a1cbb88c3f5e9f112996db399cf5f9c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 27 Nov 2021 14:21:52 +0300 Subject: [PATCH 258/511] Enable long8 read/write methods, add tests --- .../Metadata/Profiles/Exif/ExifReader.cs | 32 ++--- .../Metadata/Profiles/Exif/ExifWriter.cs | 18 +++ .../Formats/Tiff/BigTiffMetadataTests.cs | 132 +++++++++++++++--- .../Profiles/Exif/Values/ExifValuesTests.cs | 3 + 4 files changed, 150 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 651ebe422..4fcac5299 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -358,13 +358,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } return ToArray(dataType, buffer, this.ConvertToUInt64); - ////case ExifDataType.SignedLong8: - //// if (!isArray) - //// { - //// return this.ConvertToInt64(buffer); - //// } + case ExifDataType.SignedLong8: + if (!isArray) + { + return this.ConvertToInt64(buffer); + } - //// return ToArray(dataType, buffer, this.ConvertToUInt64); + return ToArray(dataType, buffer, this.ConvertToUInt64); case ExifDataType.Undefined: if (!isArray) { @@ -593,17 +593,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ? this.ConvertToShort(this.buf2) : default; - ////private long ConvertToInt64(ReadOnlySpan buffer) - ////{ - //// if (buffer.Length < 8) - //// { - //// return default; - //// } + private long ConvertToInt64(ReadOnlySpan buffer) + { + if (buffer.Length < 8) + { + return default; + } - //// return this.IsBigEndian - //// ? BinaryPrimitives.ReadInt64BigEndian(buffer) - //// : BinaryPrimitives.ReadInt64LittleEndian(buffer); - ////} + return this.IsBigEndian + ? BinaryPrimitives.ReadInt64BigEndian(buffer) + : BinaryPrimitives.ReadInt64LittleEndian(buffer); + } private ulong ConvertToUInt64(ReadOnlySpan buffer) { diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index e7a01b070..498eec952 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -150,6 +150,20 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return offset + 4; } + private static int WriteInt64(long value, Span destination, int offset) + { + BinaryPrimitives.WriteInt64LittleEndian(destination.Slice(offset, 8), value); + + return offset + 8; + } + + private static int WriteUInt64(ulong value, Span destination, int offset) + { + BinaryPrimitives.WriteUInt64LittleEndian(destination.Slice(offset, 8), value); + + return offset + 8; + } + private static int WriteInt32(int value, Span destination, int offset) { BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(offset, 4), value); @@ -390,6 +404,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } return WriteUInt32((uint)value, destination, offset); + case ExifDataType.Long8: + return WriteUInt64((ulong)value, destination, offset); + case ExifDataType.SignedLong8: + return WriteInt64((long)value, destination, offset); case ExifDataType.Rational: WriteRational(destination.Slice(offset, 8), (Rational)value); return offset + 8; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index b81ddce4d..cdeb789de 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -2,22 +2,21 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Tiff.Writers; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using Xunit; -using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [Trait("Format", "Tiff")] public class BigTiffMetadataTests { - private static TiffDecoder TiffDecoder => new TiffDecoder(); - [Fact] public void ExifLong8() { @@ -107,24 +106,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(ExifDataType.SignedLong8, long8.DataType); } - [Theory] - [WithFile(RgbUncompressed, PixelTypes.Rgb24)] - public void ExifTags(TestImageProvider provider) - where TPixel : unmanaged, IPixel + [Fact] + public void NotCoveredTags() { - using Image input = provider.GetImage(); + using var input = new Image(10, 10); var testTags = new Dictionary { { new ExifTag((ExifTagValue)0xdd01), (ExifDataType.SingleFloat, new float[] { 1.2f, 2.3f, 4.5f }) }, - { new ExifTag((ExifTagValue)0xdd02), (ExifDataType.DoubleFloat, new double[] { 4.5, 6.7 }) }, - { new ExifTag((ExifTagValue)0xdd03), (ExifDataType.DoubleFloat, 8.903) }, - { new ExifTag((ExifTagValue)0xdd04), (ExifDataType.SignedByte, (sbyte)-3) }, - { new ExifTag((ExifTagValue)0xdd05), (ExifDataType.SignedByte, new sbyte[] { -3, 0, 5 }) }, - { new ExifTag((ExifTagValue)0xdd06), (ExifDataType.SignedLong, new int[] { int.MinValue, 1, int.MaxValue }) }, - { new ExifTag((ExifTagValue)0xdd07), (ExifDataType.Long, new uint[] { 0, 1, uint.MaxValue }) }, - { new ExifTag((ExifTagValue)0xdd08), (ExifDataType.Short, (ushort)1234) }, - //{ new ExifTag((ExifTagValue)0xdd09), (ExifDataType.Long8, ulong.MaxValue) }, + { new ExifTag((ExifTagValue)0xdd02), (ExifDataType.SingleFloat, 2.345f) }, + { new ExifTag((ExifTagValue)0xdd03), (ExifDataType.DoubleFloat, new double[] { 4.5, 6.7 }) }, + { new ExifTag((ExifTagValue)0xdd04), (ExifDataType.DoubleFloat, 8.903) }, + { new ExifTag((ExifTagValue)0xdd05), (ExifDataType.SignedByte, (sbyte)-3) }, + { new ExifTag((ExifTagValue)0xdd06), (ExifDataType.SignedByte, new sbyte[] { -3, 0, 5 }) }, + { new ExifTag((ExifTagValue)0xdd07), (ExifDataType.SignedLong, new int[] { int.MinValue, 1, int.MaxValue }) }, + { new ExifTag((ExifTagValue)0xdd08), (ExifDataType.Long, new uint[] { 0, 1, uint.MaxValue }) }, + { new ExifTag((ExifTagValue)0xdd09), (ExifDataType.SignedShort, (short)-1234) }, + { new ExifTag((ExifTagValue)0xdd10), (ExifDataType.Short, (ushort)1234) }, + ////{ new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, + ////{ new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, + ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, + ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, }; // arrange @@ -155,15 +157,107 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff object value = exifValue.GetValue(); Assert.Equal(tag.Value.DataType, exifValue.DataType); - if (value is Array array) { - Assert.Equal(array, tag.Value.Value as Array); + Assert.Equal(value, tag.Value.Value); } - else + } + } + + [Fact] + public void NotCoveredTags64() + { + var testTags = new Dictionary + { + { new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, + { new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, + ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, + ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, + }; + + var values = new List(); + foreach (KeyValuePair tag in testTags) + { + ExifValue newExifValue = ExifValues.Create((ExifTagValue)(ushort)tag.Key, tag.Value.DataType, tag.Value.Value is Array); + + Assert.True(newExifValue.TrySetValue(tag.Value.Value)); + values.Add(newExifValue); + } + + // act + byte[] inputBytes = WriteIfd64(values); + Configuration config = Configuration.Default; + var reader = new EntryReader( + new MemoryStream(inputBytes), + BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian, + config.MemoryAllocator); + + reader.ReadTags(true, 0); + + List outputTags = reader.Values; + + // assert + foreach (KeyValuePair tag in testTags) + { + IExifValue exifValue = outputTags.Find(t => t.Tag == tag.Key); + Assert.NotNull(exifValue); + object value = exifValue.GetValue(); + + Assert.Equal(tag.Value.DataType, exifValue.DataType); { Assert.Equal(value, tag.Value.Value); } } } + + private static byte[] WriteIfd64(List values) + { + byte[] buffer = new byte[8]; + var ms = new MemoryStream(); + var writer = new TiffStreamWriter(ms); + WriteLong8(writer, buffer, (ulong)values.Count); + + foreach (IExifValue entry in values) + { + writer.Write((ushort)entry.Tag); + writer.Write((ushort)entry.DataType); + WriteLong8(writer, buffer, ExifWriter.GetNumberOfComponents(entry)); + + uint length = ExifWriter.GetLength(entry); + + Assert.True(length <= 8); + + if (length <= 8) + { + int sz = ExifWriter.WriteValue(entry, buffer, 0); + DebugGuard.IsTrue(sz == length, "Incorrect number of bytes written"); + + // write padded + writer.BaseStream.Write(buffer.AsSpan(0, sz)); + int d = sz % 8; + if (d != 0) + { + writer.BaseStream.Write(new byte[d]); + } + } + } + + WriteLong8(writer, buffer, 0); + + return ms.ToArray(); + } + + private static void WriteLong8(TiffStreamWriter writer, byte[] buffer, ulong value) + { + if (writer.IsLittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(buffer, value); + } + + writer.BaseStream.Write(buffer); + } } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs index 341beca5c..a5ca115d4 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/Values/ExifValuesTests.cs @@ -436,6 +436,9 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif.Values var typed = (ExifNumber)value; Assert.Equal(expected, typed.Value); + + typed.Value = ushort.MaxValue + 1; + Assert.True(expected < typed.Value); } [Theory] From 8c61c8064c7219aa24b48d3397d87d3077259b2b Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 27 Nov 2021 14:55:19 +0300 Subject: [PATCH 259/511] cleanup --- .../Formats/Tiff/BigTiffMetadataTests.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index cdeb789de..1493060d5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -123,10 +123,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { new ExifTag((ExifTagValue)0xdd08), (ExifDataType.Long, new uint[] { 0, 1, uint.MaxValue }) }, { new ExifTag((ExifTagValue)0xdd09), (ExifDataType.SignedShort, (short)-1234) }, { new ExifTag((ExifTagValue)0xdd10), (ExifDataType.Short, (ushort)1234) }, - ////{ new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, - ////{ new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, - ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, - ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, }; // arrange @@ -164,12 +160,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } [Fact] - public void NotCoveredTags64() + public void NotCoveredTags64bit() { var testTags = new Dictionary { { new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, { new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, + //// WriteIfdTags64Bit: arrays aren't support ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, }; @@ -184,7 +181,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } // act - byte[] inputBytes = WriteIfd64(values); + byte[] inputBytes = WriteIfdTags64Bit(values); Configuration config = Configuration.Default; var reader = new EntryReader( new MemoryStream(inputBytes), @@ -209,7 +206,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } } - private static byte[] WriteIfd64(List values) + private static byte[] WriteIfdTags64Bit(List values) { byte[] buffer = new byte[8]; var ms = new MemoryStream(); From 83da0e069459d716bd3df4fcd7d53282419d295d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 27 Nov 2021 15:46:13 +0100 Subject: [PATCH 260/511] 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 261/511] 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 262/511] 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 263/511] 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 264/511] 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 265/511] 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 266/511] 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 267/511] 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 268/511] 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 269/511] 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 270/511] 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 271/511] 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 272/511] 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 zcmeI5Q4;L9Z2+|a;(KP3M@S=Zod zuI1@yUC)p(fPvpJpuZx=e1882OV8%Gnq=p5TwT5L*Q(|+#{dRa7+Cp++OLR?<~r3R zJFjys$47OpR9d2}GGYJ&_b{;j4Yfoa&Fxr|?7Ysk93R!WTcxM%@f?$C4E(BrmA}z; zhgZCzeWt9|pPdl;W0l*d$!e9l)d`~=XQbyDQ?Z@vE6g$Q zDFbUyrKS03ZjYX3<2u`NeDoylRlROb>g=u3TixS1Ce;|2GqC=7DhYHP|rHP zf_nv1j9kT@<5G=*x&b-c?O0{DB)?K6Z*`t!<7>z>)U%G~xLftQ-S53vo{whw^(-6D zkg3H$)qp*PKDrlm@5c9N<7&re>^bIEZF`hxo-2fzzn=?3M(M5{C8IXBJiSt5`l^*4 zJe&AV239?3eFockb;m79uT-g0r&)F`88hR0;@-_w+?l=hYHgo7>Sf2*G^w4(IsHsa z?B}rh4F=XeX-o4_)tBekxJs2eOVg2L%#W-7eDR%J#r5}@iM7#VK0_S+0zT+7UNq&H z&-F|79Ss~$SI_F5YD#}zW%p^@YL%$#`gEPae9nM1JjdFrEzw8yJ9yF&*O^q_(ZKO! z{jA=tC)xN4Zk=f>S9NNsMB2cbKdpA6eO^A&cg@W`>4m4V~Q`q`_> z@pVtK@wo2w6-$!nbzLrlvzhbtxokNB{ju6tpC+qSs?-T*A7z#C69(+F@KLoR&#`fp zDs^jVLS=lW0ec#BRNs%LwDUT5^e>h(L~3UYv`<&B)XAD+=XIv7hXWboEM|tSNS0XHtoQo&kHlBh5pw?F#~d^GFU@@8q&W8mDtz26G^Hu&g# zXIvEn)|;$WM@#cjm1^r&ok~k3!oXDq{Ci;I>Rm0xM^)-C)p=)oJY%3wl+_tBwHSD= z0eJ)3vAU<0XKi1bF>noa zrXy-G@OcCM_hP26K2cU@$iGl4p8DgAf%U|hZm7k;T@2VaW40?x^U*B*+v~kA8_({3 zEp4_N>M?L11MB|#oqfx6bf4YcwG(Ztov-P7-IHF^4-^c1lY#cVm6dPW z;NO?8cg!6y@QVhn`Bk-!YQKFF{o<2i<eKIs@`8*~eAfwNy*f(W-voU;qP8FtF}7 z)gOQ8FUc{VwQ!L1Y=){ic0NNrYF9cvsLU{cfprE}zxkG@qnVyH$Ih!%OYu>ay4P9S z9E)lUVBi`9_C1wo?p{J^#~E%*la(q}>ic7rdeyJrdOuWR;JXa0`g>LX&EHn*zQ)WM z?iJK4n4_=X%--wIf=Ud0mjV0E&hE|9bW|s6N;|J|TasR>P<4HOUZL{+t68H81FsCM zd`q``^?9=Ts+-Yusqc@Ys#oiMnykL6?q8Sc{@Aa1wc2ON>Z@w5V*mqv1M&{FV?Y1( zYD>~9uj_jmn$Vy3s$Q-2NqY4ap?6)^xAR`b>$R4nS6MXeyZQGjcfb23?J2)y2=vu73LVYmw}aM)v|n4*|%r3afSLVf2+P?@0;No zcdu%n&@6GL8)`8yHn94v_Ia{8?&Iu=RQJcT%Cl>Inyj8xo?Vmr{&-e-cCAm_R?n)= zu8oN^)a{OA@>xGz!@&CutUs$`V*S0kg1NaKb7!8!JE`?P$6an$>fEcW&U8dA2KolZ zZ$Ur*)#{ihS6&f%*QK^U_9|j)ecrYjtM6xBL*4FW$^E-WS1rp&QA(TVNj7ftS;4@k z4A`^jqgq#!*`{VAAZQT&aP;Jou5@juC1j-?)d#VtDRb&kE%XB=ZLFB>Yiudco<}j^mp1D=kT{oYzLKt~slbJCTEdi~)Vl+j)j0xh+MnNETz=fc9zURi>r* zs7j>{1NS%JPx}#9dOxa1vkH}(!@$VvzJBE8W@BoOLpBDUWnlHmAM>^=&vJ9eZm;n> zrryy3Jz-#-f&SUA^7}Mdtx~BI17fm2N>+Yc&C7Q{1qME4z@Gk@E-cAMGh}N0e^2#x zw|K^q<;{?&#lZ6n%)LFbU;7s6NHQ4f2DHz1UgsW_YfizzV}^lWGSI%2vhqt7uG*`( z?2lL76)pxa5H-NN88wy9>${cJJdX+tU|_ugc_a66J^y!!)~t?x*9kkvz#R?nZ%ON) zivHr+`TBc7!~g~`a4!S)t0&RDdZZf+U;qPG8IZ5uK3;VfxER0y2JUKre_y!klc6sR zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~Bl=2Cn$8R97&+`5CY`3}7H)VC9$P zivKO8E0~*?mHWPe7kPH%U;qPE18cu5mL^fPx4lj%cAR7S|Ep@VwDr9Cjv!zF17`;8 z7e%78it}qiXvg!qv#TvjR?e!1cHC^L0&V=jXMft1U;bjH=Gp zHNno$>qb{CLr0?u)-iyAyaD}g9rHYAt2pX=vkJ4vIo2w~Cv}yQhuH=d5yr(c%?>o=a71pV~ z!W;w7F;MxQ_}$re!ACy(RqX5A_!XhO-ZJz`n;%)xgmxYw*qoN(S8V>P$XB=VE5hh{ zOWIaO*{oyWjt1;0p6Sq%e3U19f{pXMEB>x}1=H@+NBMnwf{pXMy~0v-R3%b}f%h8F zr}mgvdpHQaS)SUX9BX;r72m2Wn8zM_v;4k$lw&Q=+ba%&pH+#}Vc?wx{JA~iN*71< zXqKgpIQx2*w&#zTHQrXWnmOieSMq(X=AG}k)?ZIEy+g}vS--1LL9wkHP%tJqO*gx&(<<6O(K8Kcq{}v#_MgS zrSu}3AB%dN>K8wDN3xz|VD0(n(`2>gMXKAe#@nkbZH8oPuXwjsGFR_=CC{>aWIe|s z&p6^(Jz3vv;5ZL{R@t{_wDDSMg=TA3epxK}-qGrPui>@lMXywh>Uuk`&}_|`JzFNx zdkxq#p`%(Kn$XT`%&Y#sTJ46qs|>U!Mz7@Ws;k?1o+lNSC98~2Ghm;ik1D(I3>#O7 zQnQXEM8^9WuuoY>m0opyJFgJ=v$$sMimLhF-|d;uE0vygeLJrZZO#0iEsyA42HG}!rM5p-h*D#TvdV~ocN(y#&`0@BJi*3!9u+wQ z*0+yxtS#FUY@Fx$iupgmJ&_m#83Xo2>nKCnb9Hrpo@Y{#G0?Xzs~M8yN^Ltz1|tTZ zYoI;XvQpWT)b_^;k(!KwzIR#8kR(@X+fgzYG4Nyq?aAI&YCGz)WVObmDr!JHkH;uU z=k;kigE02 z4A{5umv;W?y;}1+`gCX99|ON-fZrUSc;5P(cg#Q03HQVR1~Bk`1IKr7o4x=3+z|s9 zz`$n>@a6lgf9{R}3}65Q7{CAqFn|FJU;qOcz`!~K{5!`wmyj@k0c&9O_hlXLn*YwV zu0QLM2m=^+l7ZFVmz6xrl4#DZQ1)?-CDnXQ#RMY;o@2m%3nZ%dF9bVQd6uH1wZtlI ztJ7Wa6`V0iEe76ez`naWs`a4>cK%De+Fq%`00uG!=6`M2Z{VdKP69X8?8nEwMwp#Kn#Yd7AA3edwagJo#dLKzveDnkx$2pQ| z>vbdF}JG-S1l3ad*o-)|p}8=>{ra6}xBqcIe3Fy^eHkfBu@GGx`j@sb>g#QsrTPC&5KF6k z`8~nLpY6}~(u-F1muFFdfx8&!y&aYB!Rjwezb{$M+mp)v_-(xYE@d^p+q0)zp;3c@ zI~b5Rq8;}gXo~50=zZ*TzJ0q^8Rr+g<>6Ltcy1Jd`c~oHF4hGt@EGyaW zq;5Q}BW6i^{<_ZY&l~S`yOQ^}nx{Lc^rP#U|Ehu6le6|zucfX2eYKu9B34i1*k6VC zF4}gVN`G68?XgOs?)MC=IXRZ%qpVe5Z{sX&%eMrHDx|$e2zIRVw#w^RLay~E)$N$) zwG6+qikat7@oNVB+1ulag+57EvqY)uk6D`JTbiua$JM;OllpHUzms+SS&~Hlez91p z`(uouv-+HERcA5BfH$!2-mCtz*H`stpG3BcBj0j- zbmT>6o^ZrbLO*Ynw?3WK&l@;Sil60s^8_2OIAmXGV8xTMj9!%8L65PoWl7}!f&qJ8synt6ALXdN+QvB+ z+1D7b&qSiiy`;83t|L}R)clHp+28j4(<`evi&E7eb1bs2FwlQ8+iL!9V^)o~cVMac zuBgC3#(+I59ttSl2VhK-7RfwRRpQ?0L4f z7v*?luQt$oCdXR-Zbz-3GI~bV#cW3)9=;#i+`}H35^JwRDc4kk`+})|hz#R;*qUPb6X8(F>Tu5dB%TL$Ef+Q;A8`@MH-pNx*~-8bD~;AsZ<7oqz-G5y7} z^ZWHmM;O2W2G$y|UpUf!{T-@;kGJwfuiw^To<#))Fi(Q z+1s3dqish%%5~_gZTvabnXz0~WMcpWH3Rl*AW?-Swf!+i>@#E))++=2f_!C&>lj%3 z3q-z7{V`{|&ydv&OKQgBI$DNw=6>rn$2zj@K96-~82Cm5^*3d+Vc!-VWnJmo{ya|X z*Xz|d>uir3aUE$bEz_6eYD33dt}6iE!Wo{ zahCK8tiw)kmNnL69BWyc&d>hYrE8gEpl4v_OdY4XH|yPPwAQOX)<#w1^`q9$#x$YA;n=M~z+c8UP34Ub_F-svI1BU^BCinQb9-H+k zvKnLTSyIy$o-4~`^0pV>ql3%^BjwRwafbY zwfwnXSMB-KD;bx%uAOH{Ud3q5*V=eg z#rjEqYZnB!|N2=N}#~~X7*BfY0>9&%+Z=WKoSsMB8H6YJv zJm&i8b8-x_G4L(}_Pkc^Y6(875UF{m0sq8oTp04YY5Kth~1m?p84%&#FIGh}2-c*8R)F9ZC0gPj?u=z^4qX`!7#- zB<)t$eUm@+l(;VjFyIZ$zSs7PC6T{NB$iq`Mk;-dB}nA6qOsKPW3-Aq3}E2S23EY+ zmZ78U&ULMwe~!fe4}+|G@-grZ19$n=X1`QA%39F1cK!{d>>kL+00w#n=DrO((K2+@ zyW@CGSGMzUU3;ai)+=rPctzK>^Ko78N?R{0y=>MnfPpIw$lGVf{2gt*j&dwr+0HYp zwpy<|on-r;{q1gPL>TxD1Nw_#=ehmcS|4RtzShQ&yn(tOPa?*^^9;=W$#35t9py~u z%61-SwY7dF%JOw>{C?8tzkNt=(T*ff>g*_288Pt92Ifz_CHN@g&ez%)(r&<7=|yWy zWhmrg;CTjm?@s=gV4otZaVt_W9#^sAoRz<|#{1f?e5R+f{bf2L7Xwc-Fn%9%I~j96 z_g>Re>=~}}6uTc;7`Tgp@!8LGIHt&zI87@0W1O>P_8Hr1j&n!ScyHPLts`lxdB?h% zIR>Hz>L+d8$-g7%j$f@i_O$NV?I+gqqNkWV3}g+=o-|wUqf9rx*2Wpq$}hx-mFs8E zwmXvOlO*Y(9W!0E)qZ83mLZUffrtTrO7}QYdF1pNvU=pbo=HuAd|j_I`iyN=XT|51 z7q4qG??$ryNYwUyz9Mz~@x0bv?Gv_Dn@5C!IRh(B+L$3%GCfL7J7y?*Zp+wK@*G_; zTYs?bMY67rF5@#x@KKyI`nA0gGZdRUQ)@{?-($d^(j(67@u=#}GNkoq-*%Mi<@d5@ zz7p27kK!(UrH$hp$(*eh4&%2OuqVSu@!k4L8^<|&rmdDJOR@Pg@3kcnjSa}NYR7R+ zd!?<`D{cPSimp87XBAxg9RtUS=*{9Ddez5ToO5Iz)!Xb?XLe@;_NnP8)3vT^=W9qA z3c2?-Q2#!(r%SJ7cCG8|JVWuh^__VV@p}x|XD?Ca-cmCj<uk|phTwY*Fxka59+0Lu?pE+sOds2si z>kPE-hOAt7PsrCBkf+!mufIQcz`!#M@P<6&IpwuRVjsVIufMW8 z`%HE8D+bsn1~LZt*PV>5yXWdJo}J&lbNa&o1~4#dz<%{4nx#^Y0SsW^83yF5w~x>0 znJzJa0Sv4&z`ty)a|sCp7{CAqFn|FJU;qQ&z^dQdt2n+tVhmsa1M3Z}`uEmVoNb4E zMb|%Ph#2@j19M*r`^}OlZ%HccnCJNaD|rylZ&7S+y z!Lo=jfPqy8=H5*Em60gho7A;qj?~u6N`@mf?Uu2G$xl-bOvk-b2^gd4^;weH178N*hNxw#r9Q%9iJA zk2piIxpTkPEsF>PPc~407mhd4&oVxIt&QU(Tj!&9bNu~n9PeiJ_pjbZre|_7aDM}{ z?}Dw@QD*PDuAN6oZKYp{k$hDfui(TOTUL92y%Oh)SvdyT82APQ`Yk%p zt3KAA$vOHT&7%7n)$i+3%9b}KWhmrg;Q0ntybEK-b|vcQoU)D-?dR+}@!W2<-H%Pj z-Pp|dQ?_+P82GG#74O2Bk@-%Xy_Zkstg&-W$@n=ZWly7}Wvj`@zzPG^KY#7%^--)d zU)9Dj&bsg487bP!p4aY0R^l9~=#S6kj4a1@L?#Ai4cNCTOS9EJiqd?YjiZ#&`y{kD z=Zuu0tm4QH+A-5jTdh~(Bwf|cS8(DCGBHp$(4O9HCBy3zWHm~X{QelFY*dL>HIHSIh~v3a$pY#BtqVxT>BvJy8S75#AqC(hV2XFF-{x9Y8$ z<=CCIeZF3?1RcGuJu-A%J3sQyXKkfkIj?Q6>e^Y)EuEG78mK;Pdt!VP-LJ2+ag;Lp zTjEBGGmgw^7YE_Kvzigvk2NEaT#b!V)gNPwvnRfBqm(Ugu8+)}$i0&R``ojg*=ip} zX}-?JQOcHQD<#Shd~WV7^3~rPi`#vy?9&o-6s75!{(PPkC6M<#19N9)+i#yBt8pVz z(T;J3WLk!-GG1X|=1&LvoOBfLR9Ch06`VMO%x4*B&x@=?_aXV?ah?<<_`I1n)3b=L zG0;CZnS1pKvKl2xet(Qo$a|K7{?p!8uzRt!` z3VBa5V4ty%qC4?*HjYvxZ?0M>jL$QW`7_t<&Yl?^*}StzSGDuAO0GTCK>M8aN_1Dc zrkzJAHn09JSPs!I7-&zOtVDM&`QtH4BJVi{#!q{@65q#|6K9Z#fx3bI3C!^N1X<0H z$bE(ZdA8#*x{rB6^iJeqU}V6a!co>}-B$Z(RK@y}4fv;QG5xfobsAoKSY zeLp#~{ZVH!{}u!NXDzGQT}s_PW~<4^z-JBE^Q)uJ_Ql<=GN7MvJFnff`sZ)$Zd74l zm4WsRkd;+;fO9tk@`T&*Zk^H(2Cg>Hz6IOL)pv#eIRpJC&Qt%~6XDLEHDKR}&-TLI zuQy5nEhVZdL6~x=qfvpGqy}yD^ZSdWY7GWa8Q_G00Y+oH3$g2;^b_ z1J@d`@3%y`drDP1#yM@RUWsvZMLUl%M&B*r%~PTTpO^pj=?TOb_)Y`WcSGLlc8q&! zYxPQ$qifoEjMCQWl^ElW-#@*J>XVM{o;(aZ$-vyZVXJi%cc81| z8CynMr&r#`*zct8eavHBkcEM7HgLQTdKTZYu4?B|PFty0-bvB-ZRfF0+B&}y;rJRG zM+ke4t^!<#Tjiq|#aFfQ3eNi&_xkzRsQsC2&$EvrUHY0fj!@S8 zUfS-|-d|Q?eO%Aj`ib=-o%hEWu6|3m zo}!(c^%7^SpjeYWiuU5`Y&=hi63D~AY6JExOBBD0RP@Ifr(Y|pF~*i973~;jj5&8< zY`cs0(wfjKF^aC}&u2NYda^LE+Ccw2Z>zC8jqBw~gd@5AF+$jL`t{ptoFSQ(C9An{ zo?!_RWx9}>c8pWnTD=nE=!$k8W7w?9Q?&%5UpLU6L|KWNk^KHRPl*!tytyvgd#!lK zW*K%*ZI9w7) z`TcrXjd3KqKgJkjeT#wqDcDw{I~)@-l+o|VXcy!?#eh98xvp)kk76ueW#brwtnV;j zpS_P_d-PQ{jxi)_rdAk?PcRVw^VROgo)8_`ym6#!+WEMSE8l0Jeg1kSwqIS*&SMOl zRed)sh3Fd%v?okfV!M^>@fhQf^$iBbr(nAh-Qk!LC6I@KsDb`zi&l^Rokgf|4w+9k zAkTa}#&$ScAG-@#7!U*Y#K~%Gw6#8pG06H31ODmTIKD?)CsDkfObp}=#Q%n(*>XDj zqt0ah?FRa1Kvr|Rm#Tft)sl^Y_ZhG!RY&jZfjiy7fPVJvywYLq&*aLTsKG$ZKzq_< zrN*P`xd!CPx8rm7MCTYdH_)E;ZRNa%tKVp#e-bzi-}tQAlQv*azP`fzmkii*Aki-w zV6VSxfM0#T`|OXM%U5q7k9B4kzyJn*!vKF4{>F1=ml(hR1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? zfB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n z00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO z0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD z3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAq zFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOc zzyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6( z00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC z0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n z1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q z7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? 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 zcmZ_02{=^!|Nno^nbnMK#@b{j6(QM)P>9@ADBUD2QWBMt3T;Y@O55E+w~8bcMbwR? zP+3AMV+oTrgE0%Uo;l}#bl>;q^ZovRzu#P!>!RkGIZfyNem`H&=i_O$9mUvS`M#*l z`=hoQY&m4$yKnpExOF?D4pNpZUTlDUQI^=*QHD-v3ZsoAQM%J9ihWOkhof@?tv`T)BrgKl;umW(eMjJy2bB^>O4bd8q&Dkzw@_CdA zj${#a@9Q)w?5()EdzBSK5f1_oflO6YQlydz5CGy88CI)yU#;jBqSIgRMOlz=CQ;8P zk*>2gkVbR^(syJIA*2zlb_Z58$nWXUuF0!RRqvwL$sWURd)*BJicrU6|~&mYD~%|#CSSm-MRrNqs0_(eaA30i3D?l0Kq7huNJF-gs|R$wWXZ&aMnVR6?lzfuh}>+G zSX)yZZi)EWBWuj0duW3NK>nD%m+TC9SGZiGdC*d~Wza_C32F$ib&P~PkX1oiRaAg^9_ zFn9zmRB|0{(X||G)>tmQ3!agA>5t_D1%tFbQnNLPy&qy3E*5UGM7&(l6^_UT1O7R} zhZcoB$U1ua7mXD&kBA$k`D8y%^Sn7PWhpq%F8&_8-8k`9L_wH`stUu0UPA5X()`W{8(Ny3_;pvy|>t>3ZAo%G{%gcfU6YHz%1vN}lIF36bHK zM|}-QfP$V&*pXW$A3BF71tN)5CY6W;lS7>!N^TtqbJ0@(NCv(~Uk)R}Pv<<9AhV=q zc=Db*UULr$}C*b;(g(@>kwCox|NIGj#!|6<#!sjuh>*Q~=eSW3t|lNKhH4x8cRz zi>Z5eY~C2Qar2J7sTc3QXz1n0P|3*qtQcoCP{DF%(Fi(T6s`p>NM)kkhjnn?ujE}( zWZmoH?)9K&oA@}%u624lutQX-Y1#AL%%G#&z-F=pe*jsh-uDXpgEMy{;^dBcJEN|C za!H6iy3`wWaYt9r7ak+Gz9$}r{k6KAR0AY;anW!#Ay`nWr(YvZ0R35$+ab3`ILCk- z`$KZi0&p%6a&wmA=;7r8S!iTK@21;`>(eG2#5xsJjd5y%e<%i84o4c%UxJ(Un%RcvaRj z;1>638eZH^*|=1n0uc3lQp$$p zqLz&1%Fu$uMwpZ9iPwnhKSZh6rp3MkmJDdGmM7z$aGi9%bH zWZ9^dN#lS>-d7jG5#hI$!0HZZwjnX%EArYOubuD>?Mbr$)z%e^qU;xYY^Z>$?Y8SR z140z$|7fYmzji7qE_&;ht^B!wEW=1sBoa>`ZiltfNHbn1sfeLSf>VBr1hZR z614IDuaU12M8j-p$7*2ZH{lV2%U50`5GO5Dvtf0b&g=fB2mEX}_)b?83C9ZCS+;%1EoeK# zX=A`LgN1&-=t3qe-d{0+@*1v1SgC?Ix|vVZ$v6ys_&hwpJg$N-Z$D-Z&fW7F?cA$I zj%Y{99RdF~*-cH&JID)tjY1@Y;LH9zPrqfvmhrjQ&~EB6bVBB3IPoV?&Lp4aYQ+oZ z?&Z@mo)QMnsc?>|vM&+8l>@my8EcU^^1FUL7hW+jTy~ETGW}Y6!6#k>5XJ5C0=JEg z#}Q5QU}hv~&hZgh*(#z+ScOPfwbKw(^*Y})jUCe$f48DRprV0e(C$;WpH$R;YVTln zw129vcyjyH?jT14MG&G{{eBTU_@|rBd#QqkJFA4EiZB)8sxsNg@j0YOCY)zVj2j=@ z26}aIqkstBrv=)tr$Y$$WZ}eOo{L`pJ8~Sdmhm$eDE~&h#8y4VQ8_nA02&Jj{eO^I zcW|whK<{ABK8SQO8l@OO@)zzR4t@tBG0sp>ZUhB*mVH= z(KO&99IV>ft`dpCb;jy~R^(F+oX;d*AW!-Qm zQ3IcgcsaI!)m_oZ0VT}~!td(Fsy*P;{f# z&p)C1TJTvb`1}LVFiOmC0bjibO4{*PCm6q@>$C@6;*KEU)Hgq^^}qI8GU|D2ac7VM zHIvuy1x1r$^yOk)SSS2w3E=!#JaKI?sJE{P=9Wb20;hhflkZ()!RFD?AW#Hjw4^@Gpa=~K*Ut{a%dN4C<3yt zMBjJ-wvVKvrwl>+n{4@~L{pF+aJvTSnf_ToycmyrS{?;~!15yL&V1L&Y>;wL%#rafqQYE=5j7Vvn3R@y@mL!4dE^6se! zAVyTpnEz$OQN5OJr_UxE#4Vhn6Ag-Gn!}EgU3B#oFxjCViFjE`cWLtutMm8h%j8~|dS3@yV2aCD7N_$4C6pm{H5z`c5Fx(^ zB%$ZQD9H;o^H%;AAe=oI0t9r5FKVcyOz<-ealWtM(TkRV&hC1NuqI& zwu0nf8wLde@KiM&lSS5c_IB2bOmx(!cmSd>Y=V;u+AuZQnHOb7AS_C#5lZTJ>j0J) zN8wk#IKxc-M2d>WMR8{U5HiRP2e$BA&DCB=qQLWL5^zx-q40Xx$W@#Hz5K2cC$q)W ztp&Ueo1nZ7>B`nwBmOq>-wmfvklWwlPD@-(ewGnp(Ienxc`SGqJ&y~SYO_+pG@Fjv zE&2PU;My@Es^*|m?=yd2qzvNKE&O(--+$HEImn(8 zOwUXUrr3j>jj!&f@AR`!$Aij?e$RYAg>tG7*$|12=ep3r%q5`V@j>{7JFvJ={$(w= z^u7F+nhvH-m8{yPLU8BNvtX?JGI#`yC6xU%ah06Ly=^C-m^S=fZsU(wuaoKG2hPxW z2ekOfDkE9YyH?T>nTOF&Oyi@waSz174|y?XQYl6cYG%iP$+l`v7r7MGYgHRnh5%O+RG_xAo38Z0OPry+45d z%==A|xIB+1dg2b6T;WfiR98958{P)FG)s;XojW+8V6r?{!I%4n=E*A|Z9@;@%Z7FQ zkr>tf$G~4LxZG}hUOQMlPQLpEENKFtFo_qY^n%fF^}g4H1L$UQ#f1Ib); zguF=8;HCVkivA>dcGZrg#3*!MEQ%k74slH0MGNmSTTc*uW&3Ivih zOdVH+?@Bsx_QItLXHO*U3SZ@DszC-Jl?DEX3Oj`8WX&-*CD3sDOM&ctECaN=Jtcf# z2RIbVzl1<`Rp=vqm8s-aH+6x?d;&I= zfM4#CEw&0ZQYTf9a_K9Yv_X0gM7wf+1g z1>;=!>ju1GCU^QzTfiu_3uR@xkVp>4K1?ISrAhAEI7rPZC@HI=eV8vnP!xf&ISQWr zM#3L%ugFRYvQmR^+U`lE!^rf9V-6&eOC}5LN;Lv(|D5J#8say6g>#J+lKA~=fiO19 z8=r>cD=r=WYGTsaZDci8Mc#(SDm1iH_rWf9@D6S6QPs&*4dK@L$nq74O~CA+8)C@2 zhG`C%One;Mp6on$(v~y`eh}F)h`e!>Ks$ zzR9hi*B8k}b^0mZ#4!zp&24B~j5^*edkpC~>qN$z?7NrqCR{}-f@OQiMkD39E zGlRtY9k{%1(ghA991YNCKLjp|J?D0}YA)keD0@yc8W?^MYyx&pe|E+v3cd#e?)7My zBjEM|W*uhWmgP>#YIo@a+My2ykm<%tLB=FN*(~tjo$AhU(X12^3Amg|4p$(W6)B?e z&gwe{1I?5Hl5x^$ zW{IRF!&eJbSQwg7$r57rRd;RS)zsMV)jl4sOIvGyxr0G->%mHu$S2dtDevo1M2weTU3c>Y{ALz(~4LYEr{obd&5`_D; z=yasJsTz59Nhm0(($(myN(!@!g{)@k9(lUxD;JUUKHY1t2nR5k!6Lq71QB-?9`rGy05}yRr!_lLF6BIaQCa<_ zy7I;2oJ%P?);Jld-~ft|&%wejF)~sTZ$ThAocfB4=Xen4(Jg5EHmX`On}0@~eqP+M z5{MjU`9eozFEq@*jkwUNxSsTn112`y0`R=p(NdUBXsHGtcM`I{@Ckj; zF>4%dOfUh}*4*ipi`s64Eu;Vxla=w;U$&1-!zc<%gaRIqClE?zf0!O=e|bHAr3v<& zg<&_^MDpG{Yt%rKghqHg%MsK+F)V%JFuNII|K(u=+&G*&le-y+7S`y@`#kDGe>>sG z==iGXD?JMqk1()n)#sn0u(Am~S>(~9i!gH(u?&{mc%y;yrql8DUzDQI?Fw&(7nlz< zYT1u`HrD^h-vAzv)S4+~!gY4Is3E~=b)|$sX`vHfdUgnDiq``SVk-Ia-kdOF%mwi| zRM^ge5#dOCRsQ9p@mn{n4g4)|?S`%KM=$4BwT}o9nA2W($Om&)jKgwz<^0MR13)jn z2^l(T3OE%?2NRUkPYZ^kaCUWYrlMJ`M7J#8 z3UDowurFHz28k_lPEq(=fbMhdBM;@Z-i=>wNChD(!@?tY`+*Y~S8v?BaW&(_f$hN_ z77Qu`QVo~K-)+U7`SF|&9he(l#F4io8UU7;*^(kxz$#zX9f7mJ-p7=BPF6FYmWQ0j z2Tpy|G4Ev>T8_L^43($iUJNnTq8qi@f8blofZ9r+xsc%D56{~!(u(+H2JC*WfQ=L)ns+nFQR@Ptr^^_46~sdll7^24rQv1hwt&VNStn zRnTNd$ryHNnd=r%0J5HIA z1XNvJz=7XyS0nhA$p(_t6!#9#oL5)Nlzv)@OPFa{f{mgV@oV@s8umXws4bt)hmw&H zwVrC+x#)EGE4TP{^Y?KPg3Peq-i%qQ2@y;-WHDu;hLpuP<+Viu(Q`k7 zW@0~#3F+mvXvreNUM_15uw{zrsBjA#_BqD)G zQczOYUFaORGyUO*LG17QN>jbHL5f{cjR5|VW(}!DzJ-5gn1XK4;qI+Oqa3)^3kV&Y z*bJ;6?{QJcMoxm!m?yXiz6zwtLsi;Z6=RT9M!XE-m(tlwfAxm3-jo#EHT8>Oub@n){esP zVCew|DrmU1WJ=ziG9McNdXe{`B&}m?!3Axl6yD%Q(7Tf#3$7jOaa7Ec9{`SEacwFZ zhcDt-TJf`if>Esq#B&K8X(--qBnx)9tH4gtoKDc3K##uc_J_X?nFHVXcx#KiS@}eOw=o9kX3b%P^Y7gGIRk#;iJI-7}E0Ju5uE}@e?u)!NJ3c9GK_k?# z$f>Z8dV;5wFs+%wR#46C#TB-KPreeak7-09-X>Ey!0j1l16Bjgd%A?`%dKV1Z<$&T z?iDrJkdBHv`~jb4K`0kZ%qgBdj9FPTy*p)X5cZlR*40VeFz$RyOByS(E2IT z>;w3Bh>ntMnnZiS^;0eL8MRzrYRR-Wt$}6WJaZe!nKJQ4qBO>?6TUtHTMKc$nWl)B zCu--9x?q}1pPLE2?xuyK@x+&GE0ey@I#w*E8KY)q3lJ-8TC9*RZE^*}`-PY0P-1%F z5)a7WSRdM+WCAd@U}MB!!M24YP;1$qdrf0fR5IRGS9JSKQtXzn_3PG$ZHY}fbGxXn zYg~d#$C~c#S*8V&7H%sT#0Hlw48SC*9ql`20C|+ay)l$ImxTS{plg#fU14#PFc#P{ zQ^PQ0vaEDI_nBC+UlQZda9a0kD3hy?XYNNd=d2!X7C{9Gv-heC&qWV zyc|xTcD(?eeIwjvk+Qm=GPzM4pMHFjc4S&V4DnhxeE}*T)bWyB1jM zZp}oeGbtT@XGKcl4{pJm6~J9l6t#pNb&p!weHM~82^;^|vIB0ULe zl&CJazzn}-ke@`ddIzVIoI5Ah;Lf3$xJYR(eyjW(a8=@^GguB?m|p0Ggy~Nop-mpA z^W%)JDf9N|iMQ(u570*MfK^PzUDz?hY*tw-`vXkG%=$R^0d*;B&>MeLT4zO0o#_by zonNE1KA?G)fR}9t>S3cbJ1>w1>PI~2howD*`#p7t0GVO4IyOD0w6?8_H8lEjbcof} zR$H2r9=qCxK?aCAp8E@Xq;UU(2z`(in8(J3d_B-En)#`N__ywVG%|m_=jw*N;4BXsCOwctH3*>v;#u@?P9$>JZ0LYK73&t^!OX$8e4N4 z;$y3l4u7%=0u#qnse2|GKAk#m33s}ip7Q+%47l#2jOr8 zva*KZ;uRZ@WL5R>5!rC{xitnjqIql?2l<}qLQo2MDdF72lA(ObKs<$URnoZz_^pS( z8}uI<^de-UmvCX?rwYN694HChMtR>!i$K>g#&6;ptAVn{SwYhB%Wq%*zy0>5M#EJK zoBrdsm#QslWI zZ1>Cev0p4qBeqomPdo5AzkHt+zVi87#RgGQvjEiJRB!=T*{iWueiX``@uJs{IjL6* zf-%9Hqh!@A#C(`0*;Nhtndy=J%CrM4_?17AzO_y^RTxAEjiQQ2Q9R=1T-^nV4j4$zeM zk4>Yh0BzVLp?v*-9w~le@u!YIUcvdb-z4E_77BR-?BPcR^t4j|P}YqYT7b=gWP8 zriX<7>*OEV__hilZ&))7-K6rRMP)W`KZPcMXVBAN#7wP`_H*PC)$BF$Cqw1*^l%(S z>m*y4YX{6b#X`TOnt}8+cYh%K`qL|C^V(&*ZoK}^g|WPLob|1#_Vr({{(4>8^o=!+ z<+U*P`|BIKmd)n1D^J(+kso*0&_T1*Zz${3Jisnj$Udn>i5s3it-|;dd2L3$ggnD6CO6XBW9b$y0Id|jdCYPI9{pq z4L(I4sQ%@vwi~aEx{6<=Zrwe+oOl6wI%iG+T4YQ~;f=&8FfzEjE2f~+J)XFHy((_W z$u^m&F@1$PK(*L-xn@v|O2<2@^DiEXUhlu$)ydJxb-Dlg=tCFtt2@S}sCcmE@XS}7_pgaZtrc)^%c65?&G!(_^iZ`1f)up)ZTjU=%h|D4k5TG1@JxnGF!ISb0)T#+b ztp>K6Qju&guvPHMnvwz6E+NEEjPIu~E(s>mb@6`X((zkPI9h1&6w-6!H%&mCO1bjS$)<7(iHl?CZhY~i#bW+#;L(U?B;tWlN*J?i3U)X744Et4cbZW#Boi9O)co>SPh(O z)=z|a&V`IOizynEOTHH;`_IFCY9)O;|E)>qZ|6TNuc)jjf0lpye9~5bJAEb0r=I7Z zT>M=kA1sP7!;@XkF_E8Ht~ia@&uGgg1mRqbvoLrHJIqbW@qcKBw@f9Fb zwqD_dz>oSw8Vw}D&$SFI;jQdmjLw(!F4RpvAZQg@2Y?~=kj*+H6s?)ECt zOz9d0rHBUf%N7OgbRPQv#V{YKG^d<}D-E@t!%0B2^a<5Z@Iqm|EEBjQb~hY*1l(oM zi9lSPkhK=_)%K_(;qgbH?iEn~8hFMe9~W6UOHblnjGEYSpAk31H=u3OLPghsemBxZ zq(o0G3#qZiZD9$o=#n;lLF?9$RW{eb6ZcnA@z%-jXXLEHT{g;qg0XKx)|(!V6q}o- zCx-jGnH|jT{^5yfY_5`WdfsFu_!=t!$~L4wecBzfz0gp#@eBqPegXv{jZ7egx_F*TLB3U9BI@#@kOAvGi0)B09t|@+f z3tAUMRF7@L5HM`N3A{Imaw-lwVXDb8{6aXl=3T#GpE~Wj)?B7jE+d0=oqXIFIhhP%u$e zq~bK(ZfM48(^@wD06Yo%>Wn@H@+0k|Sf}Kbg3K4*cz5)x9!%Tk$E} zt3f??;h*3k^f0#2PXIe5Wtyh%MSG!@J-mGct5@hNJJ7ElZCFRt*w=w}95Dg(VqQ&#;0aUB4w;RXh2(RbOy|kTcmn}BY(WI-YsM0lcOcpO&yEEblDn~v?kk$8fqHXaEa1q9l@&1a#=mgX7TeBS5z}xJ^AbW%V z3rA*PAC#E=c3_qxyA^JV5fE{I@i2~T5y*&dLp%4WlOsMO2}0vwbd?G38dyAP z=q9=ioIql57&D6j4VMXiKYQH?$D|+a2zxoBJBjn|N&8}zRAM{OmKY73!(|pal(SkL z(C|H5&q0OV&(lI}=a4~=MAI{K@bFs`vMywepNE5)9*qQoFy+8d3moc0HV~KGw$O+)K$jxxH z7`(rue7nAH zYWXbRejel77anYaC+~aUHR3zax;3+WJL|uE`(sU`w}12Pgx@8(fAj79C6@e~vwZs= zsiiOK`8VILae9z=kBQ6eA!TsphM|Fa>;fQDyqwY1rs+0Ssp2u-WMJ@K5DDxMv^(Pu z2zo<+WpyZa3%i!WtRoDZ+kH%{#u@-x>+^nKz+q;v2?gVd9YW8NbUo`!lel_pc^oHQyc53J~*?vwlK3|0_h z(F0JnES!)7#{-FQk=p$C;v}4Fn>bxb=Z@^#dYoz2%tVF>=#V`rdl$_B45G_9=*Y7; zTO~lzEac?V&uj@QpJDg5zI$2pF!z4$!=jh(T6@_ua#X_p{PbjqnIfQM8~1Dk<&;Mo z0F3B&vdL@*&?IAGCd(AJ{+sNMj#9d~%@vmSK;scuk)aFkV3MJCYZFXC?dUpq?D8UD$$in! zWSVN~)Yxu<)obLYs!5eB1zO8#nWxt{?nPASHyS=g-=bGxoPf$23Wx13*fc( z=vfy2Ni$g92o`*&921(m!-=H&E{)%?^FJNg1^s~}V2pf$vUNLeD-bfw#weV6q|gi( z`WdOhT(e^=qzOY@79=%F#f@ppQ~;XGzJhOjRK{*Axq2XMh0Q#MnlhE5K%pwDG3ME< z2s?1Kq>U{@`QHlmxzGTWWoeCKX=Bm?47O=PSjRADuL}7biZd%j>M^p2JsbjT<+WQY z7hqk11mq8B7m|+YrQbBV>Pd;PySeZf<$D3xSPnKn!Lz!qDorJ62@=&NE)v_Tz#Cjs zH_16LPvD}%?9jLT@zunjiL)A)CV$RgJVo;u^h{|-5V!=R(6>`nW7^S5`pGHoIbD3m2;f5n@1efDrdq$Y}TKnTZ-JI={!JcoWM10NT~~8 zeQ%nV;{<6%*Ggw9V@x1L=j19jBI+(mU2RUo0Ytj7olj`={*<({=gy|3?2iuhu`{L< z0UXVIb!t(!2w_(xJ1asaF_kmY+Gs7vDTgG@hnBdX>;#0p_rE1CRt_ z^?;$nR4F-B@_@94|8}lP12&XJa=!b5n?^VZWTU&Xo^52!A6`GUBhXG4^HS7oHeG!`iSj?D zdnkh@yKBV4^7TsO)qlZ*$(rPCJ+iw-(E;7`Sds$-??LBb;gR zuRe}=nlpm+ak5f2x*GiI<0Q}aaau2|_@|GP=ZRO3`-Z*``lpYxs0@9gPrHRYo$cf7 z!1kIuSRdyB*2g&p-sjsa;@t*{S*mfe#XjhAXVh~!Vr~-8RXI08$zap=i_N`|DAk^7 z!XYG7>BA3G^MMu}i{9QP^b%PTxMp-P5QrEZk0mTBMjC&E%(7=VSIhwO^FL5#nl(<* zBPCI909 zR`q~|QzL!M_STlxc4ptmlo0z9!NAA-lpu2@fMOn*(9e8UXDJPqg{aBq}nmZe8@TScrXLmh0C9I(`8|wG`!qYaGuaw0la8|iar5v z%1C3$+QOZBqCFZDnfRvn_|sxXGfn|^TWuy4Px~sCh{B+AXcooc8+RMFfp8OuX3x<2 z6@Yya%sQw=@O>tneio#Hvr7Ibg1fTA<`E$cmmN21`msSuG|}5q{VXr*+NFz^u4Uyt zt8VF?z;fs5{+b&*Tr?o!ys+#pSoAU33a1kEY+Crtm!Nf!1sB-^E9%i_X2b+8wmoj+ zesiE?q3575QHi4ObTMf&c4HNA%pAq1@$Z}PnG+g&M0z`g+R0-UsOzf#!Iu#J;Y(`% zzsr@Bc4f5~m9o?5g^I@g-X}tPS`rVC{x2|1Z9zYL@|^8~0zn zWWVx%`4XjtzxWc~M+4)wfBBM{fA|u@|KLkjxuQ`5+Sy@+t3%W@o=!O8Yr?$^k4$}fR?ksJ76-bWMdQh7Sbu1R`E$skYOxDs$Ek$3GopT{IveEu>m&;XC07x{1q zWj_j`LAII0^7?S9%C0VXi36~*QJ!a@bVk}_M@pBMYMXrI1>)XJJ?G zLgSuxZFk`vAXDUIF35!5eZpTGQ%&Kj9UZ6Mp5AJX_&TFLzxsn0@pbLgSgb$T+M(?s zc#8E03n@Mmzxsp6@yj{{`~U3^cEbPk2cP^$UqKa zRpYyqrG29T;GXGkp5W$T?jb7U^32avIyOcrg@1C?A3y&RFthTdG; z;c1NVdpL@^zM1V(Pj4S@&!x6z`sx^ug;O#1+;Q#AkW4=E@{o%XsJE$T68U!A4A93a z$Bp3%>d8N6E;FcyxWk)4&u`Ovz<|-drIfo^>G~j&ff*+IaD`m!MclhU-WXOM^0Ja` z(-XxQNJDLf^s_^xtX|^vQPl$yD>pbnp}9?YBXS7J75XVxaP3$O-Q>2MYrZbz7_HT ztYo(EWG6a$f3+IG2)fZOLdNT^ZE@5lVurM_gMVbgiA%Tc<~_)}d+XAPgh+n}W6ZuL zYCCSZRyU4_+HV9g0JYWkN72rc3$g0kfV{#Nns-YyxQk@+5UE~FOc6I(V+Zg}^75bU zCPrUbW+r`~)cj=_2RdQoEsvoM=7RM#09NjLTMkt~T{jg*E>Z^W;2PgSe@q#B!O8gY zp9`$Us}uu;MU<7C21C8KqF7-4*uYv~9gCk#)wv=55vNE?=tbU!QWXN8@?{?r&2bc` zlo}4oZ^_=_tW5w&S{B}$4rcuM^sl!cnwpzFy#4FxpBV=?d0S|a0D`vjj_ejb%Be|l zqTtLEKg#$|0~DyCZ;{>vnqu4!@eLhnGM}{$SUWJb9*7ma)iZ41tfUkP0~M>rtrq+& z$0x&H##4Vl?;3G;1}GOt6mE7wS5fj}Gn-U`WY@ui=xJ;) zen53)4SLOT0G6{HKrra?8hz)DaRAf2Y+F!2@iW5C#W(q`NTT7Y8uccJ=Nv zcVE=Cb^jQ~IDp|F-EDO*?w;Ab+IH?Q4j`ehTLSmzZ!*9*fLu26IZ+?9&E`#G9Dw&b z^tB5ZJj(%C&T;@=gDh8!1BeGsqX)t3vLKZvrg|_EtJqNmW{=YM3-zNUdhtA!-KKIs ztiin$4Kx!cD)y9vS7vN1rV6oqHy*kpi^N^W8sCvKA57IAp!YNwc}Ud~+{W+HocYkI zH%L$79LOb`EpJP;#8F+-K29TJRcQg32~C=7zwW@*r?p>yu(>lLi9{lr;j({xt$lj+ zz&iW6m@i};kXAK@On*#srQ$48+vMzQ7idmm5AtReG(SiBeIss36_Ur$xQ9MaGyO0V ziM8Rc<055upm;0~FPyTlnYjX%_b4Wc%>rkCC4-Bf+;o*EVi6{rw-Lr9aIgXQI{0FM z;RmOKS>oT6t2yo}FNNXIN#r4IVZ9`kXj3Q8VkjTujwjQUc4O}vL{<)aEfJK2VebFN zhXaW4bJpg?Dj-42c3sk~vgW>Vf#e@R5rHLw@xJD=TS@C|wFscf;>}r~g~-6ejacFk zRw9tR4pSxvVV}%yC0g=0_n0yxOI~L~OqJ9xq&-AVLgB)fD(;+W<=@0vU^?u_7g;2@gC>I1j_zwXZa zD~fZC<1@3JmB7-aV-Ezx2rCv45m1rP#1@J~!5+#*j6o4a5DS8eNC_$tf>==mL=cP= z4JCjGEM26tv@Nj9wwe1bn&h0E`w!gtanE7*%rGqP^FHtQ`Fvh9_H#fM0|RFtQg8+W zDtHm6zwt$Cn42jBA`g`=2s|nZM3VUNInfw@QCCQaH))c7uG! zdhCg@XeQlesA<8HjyA&;>|*Kx?2tljqrx$X)hcO#HZOuPc~Vp0ryVg$OYJ75eWIV^ z8*RgOY7IO?VkKMEn^?Ncu?p4ILrqJyUkgF%f2!RMIXczvgRBC1XIseffp{p|5MGta zmeqxUIY`%EI;FyRTnT$g=^ho!B-b{yF-mG*WjeZ8TzD3Jb}r24y_q_8{X&>3U;cGIW=_a>-!1qKohW z)qVPvx@q&6D-tfQ{6-;G)MyX6Hc7LM5$6i5`4EHR@?0=e;H3^PJeWU@jlYVt*Fp#y ztM*)YT-OgaAgFkTGd4WXKQKJTnUTli`2BT{FYH;RK|r+ZBVU1cAm%3TMPPTiWCb**A(}6~3tyha;|I&8F(FrroIzgf5EZgG*L&??TBud-#sXdfInpH+x7}1%QMwNX?OMk^LdcM66c>XDtf@cGnj~C_jR$F zKQyzt`q=VmDG8^iBI76LB?{0y^8?x4QepKOOOk^9ZMN)>00zb5ceHgMRXcvBe?QS2 z%vP)GK0&Y3)^rVSP{@`ZL=t5Iq(?lf1%pMzNQsTfObn$N?Eb3Z{C7}gE?l06v}KUm zvypcdNZPobJ9?O2)2hBzlmdr?PD=!Kjo`_t`&uO%OH*F(9J_BsKRGq_vl8RPfcWux zL_fR@>%O>>ptvciT`H)*<^kG-YD~Yl{ALi~Lq(k7?&i9;73CFg>zccVIp9V_W6k+- zeoQscc=WhdFOaq;ZBitxyx5Jkh3g~hABzW0FqD2Cn>tB1x{p0ypqz$ft9)I@@g!Ws zB0v`^98M8!Rd21KA4c8v#?z3BcSv?SDWeU^D~H}^PzK`ZgEz=6&!I=3RQ6)KRNMZT z=Z(gI>gp(*05A>vXG`^Ti=-E*<`uGJb=51v-UAff%ahaT=8*A~0kq-xVn}1xy&-I> zEYeOLCL1`0-mV1wPfW}k?rf+kEzEnCS6Eur&^gQ#W1wGJc{|k6fDEhKMV3urL-%%R zK#PwzpaWNoA@lU<$;-Nw1HHm4s_MzI3UlfOX_2m8>Fj<0W=MdKf(k1J^`7@fo9ZrX zuga%K(DN4ZjX`?+043u+(sgaFPaX56PaPt2((NxHAHk0Q-KP$d=Bh2NngwYn@HhLF zZu(rGI_-b;se?eDnr`eF^|6=qoYCA{na@e(noueqw|(cSdqo5|^S9~D(K_i@A^VAAn2!OwyL zQ%&|}^~k2*9Qztp^?D)X=WHP8Mh=Rzm&4Ar=>5gilhf^+;iLS96^fZ43%*bEkme@L zAs)g`5OUdzY{aL@byb9v5ypw>`5}DO=s_ww{NMTZZ|3std;ZS1$ANtN>VN0kpZ=3? zcc06*&&u=d3+M9fsc%00oo~Mj^6haT-+uS+eEX+2sdM@Eh4Os+>|DOx{hxgM(|_mN zSA%?e+~4{3J#+c?Z~mQc4`H7VC>#UhhY2f+uu^Ntx=_{;s6ufs5^?TrrIVP^4PsH#4G#kXLB}p6OCw(M%G3>phOB$ep4R z*dZ$fpIwR-tsop89{{A0v99%$>sTVmgI}V&fm5WgTY3??!(U-OlYo>pQDV8;2gHkh z5b1?Z&f^5l@k{pU@z2q~(MTfS)Kbnbk;wTa)+%LOS8}qPU-A)@p5AhP2^f8S3HT+& zDC@8?ZTl7a#7ANNpR6chTB+IW?tNZ_5g@D?wWiDbaS6N+#|)(kN>$eUJ$2dF0G}|9Pds@ zN_Wtd4z;2sc90c#;!MPUbi@bRG16;Kx`L&UzvI4ET{lrWZ<{0m#95i&@RHz{EZSMV z{$9y4FNskg7cb8PFW(Qm{J+A>`hl1A953}1Ug}-E>_70bAH>W4954M6y!5Z|(oe)o ze-|(PcD$T_;N?6DFXw}JIj_ab`8i(l40y?x;3e;am;4o8@^E;`C*mb9ikJK@Uh>p< z$+zQW-T*K2A9$I^!OMIUUgp*CGCzoyc}~2{*WzW~887qac;Nxyh0lN&UIt$HC3xY< z;Dzsl7v2(H_*Z!0k^NOC{=f6#@WSiE3qKJrJWIUrMe)LW#S4EIFFa(t@Tu{_3&#t; z9WOk6y!Z|9;+w#W{{t^R7QFaz@Zu}Ni$4l4J}9P77r!%JeA{^OpX0?x?;=Bb5qwT_6*98f>CzC1Wiv_ZG0TG?56kGy*} zSJ8T)lL@4ZcNhx`(Tw?wIAQl5;-WOc=s8`2L#~jQvxP>nx!#F#%YL>}gcz%jX4H*K z(V4#5XUUO=cRATG%}u{CHD}s5?K&Kp{H(Tb29=K2WgK12fE2BMF5{w|*KH`YEjc`4 zt^+~$+^8UJ5phqqFpe=l11((0xC2y_%+3L8#oXE52E7YupJ}lKP1Bf=?sg8}v zk;wsHgjs^U-T+zL7Z1l6!ORr4?9bzXQ{7C>fwx1e?*-u`>P>$=vcjPGqnUOU*Mo2Y%~4%cD!npqQ7-nvoZ!_az5gw==Hcx4 z4RcukKebX$9?w6b$+8V5dP(Hq_zLihs|${&puoOeS2Tp=0z}nf#oimcSTydNHqt&K=9yJ zX`6j)Hdrz8n(mFBB)sU;+5zB#$A!Cs-bt!;D)DgMHO;;MAHY`rPlR^ zau@*qeiORTp9{7Vldwy~t;4;JNa!?c6SSjCa79BgY)F^^tW|fo!e_2@ifwW)#?B2` zLL@EPd9`v>Dw}F8Oggr6y@{R*olHbv7)FR>x{993`klv;3R|aS($UJRI~S3NO9FD) zSZ}f|h3@)H$h`y1S{cHjFhz|kg03CVCf0N);@CU9m3RqDB5oHvr@IJ$131LPls8>! zyD%Tkk=saB1^l!De)bNkhH7$1A8%57ZxI?wpv)o7?=e56rUqqCX*?8-9RtFNSj1<# za)IVk^eX*pz(CSgislanDQHBog;`7h+53pKMw-AXZg+Z7{@d6sa$k|n;hV2I0d7e; zGybWyuClE7WpP<$UF)ZD;46}FI$zy9Y$Nv-ZHax$M|-Hr*cD+k{|ZH=1?Y7(m=S?OTGD#lvDGV z)PG57@{BS&f!tCEWsYcjpn=r#kNRu5xnytP_S`;Qs3(p7IFex`OKu!rG8RR9D^64BM8#uoTmZ- z&~4z&kVGL5r!0-S-)ma{Z^q)!-i!z82KneM4UH5D@Mc&QqD{V3)sQyqeXtH}0=yZ# z;y@D;aj|!b+?&x~o_^eOmC^t3W*DvVJf2?OFZX7ocrPZBOahB}z?)$L>jb~Y+Co&R zzD;PM74T+AQZzJfq4@^t4?cS{76WgFh4%YyBg?6WNR$M4Gj0RD67@VsGf1El!ehkE z;{?y~Wc>Ab=cp|uNFv|NLe7(s%6T$2DrG!Z@=ZBUre_pTYUDf_i&t1N;K{s1Sx1y< zo+aYREICi+f}AIF)fHHq^&G-7Ylde5du*_|;(1m|eC)Zi=VIeivYuBo4+2Kn>~KwH zn1dcLLAzd+^JFf_c`{j(;u24q@(~vL67Xb-u~!yyo(v%IjP}TRGB?StyfPIVIZp=o zxqg|pFykj8B`wr*JU#z8o=h+&LPxyY40T&B^VR2_CpT0Pqr|pGLodk(0r=_)8Vs=M z2}Jja58u+0(5D8B8`2tE$fZ&C!~(K@Dj7V#5HbTwn6CJ>RFe6z;&F^yeZzOTGE7i0 z-V|`|cK*AT-jN9|pU>w`jP$m=%fEdtV3V;50ajk_d!w3*jTav?CsEhNcL6z#8MN^H zpya7FWbs7S=mOc+NN+FPCu9p`wj!&SGAxteR!37>hd+JN_}{h9Ylplh4E9=laFJjZ)={D=2S zr)+uhh4(6B{l$9?n{KfoMa}VE#~c+N0^aKrzO5Fk9`Ig|(3>g<33I&H&Dd%u)YxB7 z;%EwE5(xyeou9&uLdwsb6B($>3DwLlyQ`Ei@ql1>U*HbZW$;&daj zZ!=ozOw~n?`qHEumUO=TfxUmu!?SZgqn;w+K#>);4qf zpg?hf>dM}>C5t;+bXRj85l+e6v_RQ$i>>J|vv81Kyp_8I%;VStBIHJF^Kwa$GLYM* za!ptAG66yI05J>j+tUHR{eYa`{s>EB7(B!ZOuz8kJ*u$QKy{*B<`;hZ#<+Spzdd4( z-)^wZCn62-+Xp8BaZnVd6TmB$x; zyJ-RT(14MKJ(Ba=4*-69I^efw0e*WVoXJ~h%1s5zVC5jm^3B*rK;yJWO+?$7*g8jS ztu5xZRB(pc@Rl4db=K^tRdx|&5P*}0OCU1h$(qNU50E;5*}LI-qY5@Yd( zfcT93+sN2!K)q%}6k{)t{^ICS=R1N{oV zCkeQE2pJ+5<=PHSXK6V3Z3Fd;V5u8syB2d?hcUM=kO7%HP%zo9MtyW9Z^A`AYCF&{ zV&23`!;V%%K~-#^`8NjW>)Xb11?m`&272DHnH90|Gr{>wVO4A%610!*7v z4w=zl)isBIc{?P+B$M6M`5E!2Lk|AniNW56JBR$Y-+(!pi9_UmoMfKa3hrk=&Rvlg@v|SNe1XP;fBZNf z<$fHuN{kh(LA3pKLRxpi82E86$^AGlBDWX<4yyiIual{H@BZb-d6$=Z(rc|g;HVmI ziF_gV<6HuM9ODyp(urSfi5kHytkO;H$NBh=ALoI_g7VLP9HN)#?q@#^cZC@*S?wNd&>J zofk~BEJyDzCH>GLeaM8qz0I4s??6`ccr+zrC2UlJw6so`g5S{Q3D6b1vo>nQN6C;Os{^}TJeGOB<#uVc0dBTJ=-9MI@vvA!nPXh zk6l#8fjYGRDwC+_l-`SSUZ1jNfP#G2#lm*33>A#BD)Ta~MTdt51qOwNM_03=}wA7=GA_*?f`?j>kq7E8^QRVtj+=2*24`%9J<+_@JZ1PCsjoneE6Kdu?8>Kb7$5^)AMB=Z*i6hlilTS$; z^!m!E`_O2hKR*suo*N`*Hft5_Dq67Ms{~2C1GJ?UeQpI=Jrs>zUJ4nX@5Xu)Z54TXmlU7tWVED 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 273/511] 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 zcmeI5Q4;L9Z2+|a;(KP3M@S=Zod zuI1@yUC)p(fPvpJpuZx=e1882OV8%Gnq=p5TwT5L*Q(|+#{dRa7+Cp++OLR?<~r3R zJFjys$47OpR9d2}GGYJ&_b{;j4Yfoa&Fxr|?7Ysk93R!WTcxM%@f?$C4E(BrmA}z; zhgZCzeWt9|pPdl;W0l*d$!e9l)d`~=XQbyDQ?Z@vE6g$Q zDFbUyrKS03ZjYX3<2u`NeDoylRlROb>g=u3TixS1Ce;|2GqC=7DhYHP|rHP zf_nv1j9kT@<5G=*x&b-c?O0{DB)?K6Z*`t!<7>z>)U%G~xLftQ-S53vo{whw^(-6D zkg3H$)qp*PKDrlm@5c9N<7&re>^bIEZF`hxo-2fzzn=?3M(M5{C8IXBJiSt5`l^*4 zJe&AV239?3eFockb;m79uT-g0r&)F`88hR0;@-_w+?l=hYHgo7>Sf2*G^w4(IsHsa z?B}rh4F=XeX-o4_)tBekxJs2eOVg2L%#W-7eDR%J#r5}@iM7#VK0_S+0zT+7UNq&H z&-F|79Ss~$SI_F5YD#}zW%p^@YL%$#`gEPae9nM1JjdFrEzw8yJ9yF&*O^q_(ZKO! z{jA=tC)xN4Zk=f>S9NNsMB2cbKdpA6eO^A&cg@W`>4m4V~Q`q`_> z@pVtK@wo2w6-$!nbzLrlvzhbtxokNB{ju6tpC+qSs?-T*A7z#C69(+F@KLoR&#`fp zDs^jVLS=lW0ec#BRNs%LwDUT5^e>h(L~3UYv`<&B)XAD+=XIv7hXWboEM|tSNS0XHtoQo&kHlBh5pw?F#~d^GFU@@8q&W8mDtz26G^Hu&g# zXIvEn)|;$WM@#cjm1^r&ok~k3!oXDq{Ci;I>Rm0xM^)-C)p=)oJY%3wl+_tBwHSD= z0eJ)3vAU<0XKi1bF>noa zrXy-G@OcCM_hP26K2cU@$iGl4p8DgAf%U|hZm7k;T@2VaW40?x^U*B*+v~kA8_({3 zEp4_N>M?L11MB|#oqfx6bf4YcwG(Ztov-P7-IHF^4-^c1lY#cVm6dPW z;NO?8cg!6y@QVhn`Bk-!YQKFF{o<2i<eKIs@`8*~eAfwNy*f(W-voU;qP8FtF}7 z)gOQ8FUc{VwQ!L1Y=){ic0NNrYF9cvsLU{cfprE}zxkG@qnVyH$Ih!%OYu>ay4P9S z9E)lUVBi`9_C1wo?p{J^#~E%*la(q}>ic7rdeyJrdOuWR;JXa0`g>LX&EHn*zQ)WM z?iJK4n4_=X%--wIf=Ud0mjV0E&hE|9bW|s6N;|J|TasR>P<4HOUZL{+t68H81FsCM zd`q``^?9=Ts+-Yusqc@Ys#oiMnykL6?q8Sc{@Aa1wc2ON>Z@w5V*mqv1M&{FV?Y1( zYD>~9uj_jmn$Vy3s$Q-2NqY4ap?6)^xAR`b>$R4nS6MXeyZQGjcfb23?J2)y2=vu73LVYmw}aM)v|n4*|%r3afSLVf2+P?@0;No zcdu%n&@6GL8)`8yHn94v_Ia{8?&Iu=RQJcT%Cl>Inyj8xo?Vmr{&-e-cCAm_R?n)= zu8oN^)a{OA@>xGz!@&CutUs$`V*S0kg1NaKb7!8!JE`?P$6an$>fEcW&U8dA2KolZ zZ$Ur*)#{ihS6&f%*QK^U_9|j)ecrYjtM6xBL*4FW$^E-WS1rp&QA(TVNj7ftS;4@k z4A`^jqgq#!*`{VAAZQT&aP;Jou5@juC1j-?)d#VtDRb&kE%XB=ZLFB>Yiudco<}j^mp1D=kT{oYzLKt~slbJCTEdi~)Vl+j)j0xh+MnNETz=fc9zURi>r* zs7j>{1NS%JPx}#9dOxa1vkH}(!@$VvzJBE8W@BoOLpBDUWnlHmAM>^=&vJ9eZm;n> zrryy3Jz-#-f&SUA^7}Mdtx~BI17fm2N>+Yc&C7Q{1qME4z@Gk@E-cAMGh}N0e^2#x zw|K^q<;{?&#lZ6n%)LFbU;7s6NHQ4f2DHz1UgsW_YfizzV}^lWGSI%2vhqt7uG*`( z?2lL76)pxa5H-NN88wy9>${cJJdX+tU|_ugc_a66J^y!!)~t?x*9kkvz#R?nZ%ON) zivHr+`TBc7!~g~`a4!S)t0&RDdZZf+U;qPG8IZ5uK3;VfxER0y2JUKre_y!klc6sR zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~Bl=2Cn$8R97&+`5CY`3}7H)VC9$P zivKO8E0~*?mHWPe7kPH%U;qPE18cu5mL^fPx4lj%cAR7S|Ep@VwDr9Cjv!zF17`;8 z7e%78it}qiXvg!qv#TvjR?e!1cHC^L0&V=jXMft1U;bjH=Gp zHNno$>qb{CLr0?u)-iyAyaD}g9rHYAt2pX=vkJ4vIo2w~Cv}yQhuH=d5yr(c%?>o=a71pV~ z!W;w7F;MxQ_}$re!ACy(RqX5A_!XhO-ZJz`n;%)xgmxYw*qoN(S8V>P$XB=VE5hh{ zOWIaO*{oyWjt1;0p6Sq%e3U19f{pXMEB>x}1=H@+NBMnwf{pXMy~0v-R3%b}f%h8F zr}mgvdpHQaS)SUX9BX;r72m2Wn8zM_v;4k$lw&Q=+ba%&pH+#}Vc?wx{JA~iN*71< zXqKgpIQx2*w&#zTHQrXWnmOieSMq(X=AG}k)?ZIEy+g}vS--1LL9wkHP%tJqO*gx&(<<6O(K8Kcq{}v#_MgS zrSu}3AB%dN>K8wDN3xz|VD0(n(`2>gMXKAe#@nkbZH8oPuXwjsGFR_=CC{>aWIe|s z&p6^(Jz3vv;5ZL{R@t{_wDDSMg=TA3epxK}-qGrPui>@lMXywh>Uuk`&}_|`JzFNx zdkxq#p`%(Kn$XT`%&Y#sTJ46qs|>U!Mz7@Ws;k?1o+lNSC98~2Ghm;ik1D(I3>#O7 zQnQXEM8^9WuuoY>m0opyJFgJ=v$$sMimLhF-|d;uE0vygeLJrZZO#0iEsyA42HG}!rM5p-h*D#TvdV~ocN(y#&`0@BJi*3!9u+wQ z*0+yxtS#FUY@Fx$iupgmJ&_m#83Xo2>nKCnb9Hrpo@Y{#G0?Xzs~M8yN^Ltz1|tTZ zYoI;XvQpWT)b_^;k(!KwzIR#8kR(@X+fgzYG4Nyq?aAI&YCGz)WVObmDr!JHkH;uU z=k;kigE02 z4A{5umv;W?y;}1+`gCX99|ON-fZrUSc;5P(cg#Q03HQVR1~Bk`1IKr7o4x=3+z|s9 zz`$n>@a6lgf9{R}3}65Q7{CAqFn|FJU;qOcz`!~K{5!`wmyj@k0c&9O_hlXLn*YwV zu0QLM2m=^+l7ZFVmz6xrl4#DZQ1)?-CDnXQ#RMY;o@2m%3nZ%dF9bVQd6uH1wZtlI ztJ7Wa6`V0iEe76ez`naWs`a4>cK%De+Fq%`00uG!=6`M2Z{VdKP69X8?8nEwMwp#Kn#Yd7AA3edwagJo#dLKzveDnkx$2pQ| z>vbdF}JG-S1l3ad*o-)|p}8=>{ra6}xBqcIe3Fy^eHkfBu@GGx`j@sb>g#QsrTPC&5KF6k z`8~nLpY6}~(u-F1muFFdfx8&!y&aYB!Rjwezb{$M+mp)v_-(xYE@d^p+q0)zp;3c@ zI~b5Rq8;}gXo~50=zZ*TzJ0q^8Rr+g<>6Ltcy1Jd`c~oHF4hGt@EGyaW zq;5Q}BW6i^{<_ZY&l~S`yOQ^}nx{Lc^rP#U|Ehu6le6|zucfX2eYKu9B34i1*k6VC zF4}gVN`G68?XgOs?)MC=IXRZ%qpVe5Z{sX&%eMrHDx|$e2zIRVw#w^RLay~E)$N$) zwG6+qikat7@oNVB+1ulag+57EvqY)uk6D`JTbiua$JM;OllpHUzms+SS&~Hlez91p z`(uouv-+HERcA5BfH$!2-mCtz*H`stpG3BcBj0j- zbmT>6o^ZrbLO*Ynw?3WK&l@;Sil60s^8_2OIAmXGV8xTMj9!%8L65PoWl7}!f&qJ8synt6ALXdN+QvB+ z+1D7b&qSiiy`;83t|L}R)clHp+28j4(<`evi&E7eb1bs2FwlQ8+iL!9V^)o~cVMac zuBgC3#(+I59ttSl2VhK-7RfwRRpQ?0L4f z7v*?luQt$oCdXR-Zbz-3GI~bV#cW3)9=;#i+`}H35^JwRDc4kk`+})|hz#R;*qUPb6X8(F>Tu5dB%TL$Ef+Q;A8`@MH-pNx*~-8bD~;AsZ<7oqz-G5y7} z^ZWHmM;O2W2G$y|UpUf!{T-@;kGJwfuiw^To<#))Fi(Q z+1s3dqish%%5~_gZTvabnXz0~WMcpWH3Rl*AW?-Swf!+i>@#E))++=2f_!C&>lj%3 z3q-z7{V`{|&ydv&OKQgBI$DNw=6>rn$2zj@K96-~82Cm5^*3d+Vc!-VWnJmo{ya|X z*Xz|d>uir3aUE$bEz_6eYD33dt}6iE!Wo{ zahCK8tiw)kmNnL69BWyc&d>hYrE8gEpl4v_OdY4XH|yPPwAQOX)<#w1^`q9$#x$YA;n=M~z+c8UP34Ub_F-svI1BU^BCinQb9-H+k zvKnLTSyIy$o-4~`^0pV>ql3%^BjwRwafbY zwfwnXSMB-KD;bx%uAOH{Ud3q5*V=eg z#rjEqYZnB!|N2=N}#~~X7*BfY0>9&%+Z=WKoSsMB8H6YJv zJm&i8b8-x_G4L(}_Pkc^Y6(875UF{m0sq8oTp04YY5Kth~1m?p84%&#FIGh}2-c*8R)F9ZC0gPj?u=z^4qX`!7#- zB<)t$eUm@+l(;VjFyIZ$zSs7PC6T{NB$iq`Mk;-dB}nA6qOsKPW3-Aq3}E2S23EY+ zmZ78U&ULMwe~!fe4}+|G@-grZ19$n=X1`QA%39F1cK!{d>>kL+00w#n=DrO((K2+@ zyW@CGSGMzUU3;ai)+=rPctzK>^Ko78N?R{0y=>MnfPpIw$lGVf{2gt*j&dwr+0HYp zwpy<|on-r;{q1gPL>TxD1Nw_#=ehmcS|4RtzShQ&yn(tOPa?*^^9;=W$#35t9py~u z%61-SwY7dF%JOw>{C?8tzkNt=(T*ff>g*_288Pt92Ifz_CHN@g&ez%)(r&<7=|yWy zWhmrg;CTjm?@s=gV4otZaVt_W9#^sAoRz<|#{1f?e5R+f{bf2L7Xwc-Fn%9%I~j96 z_g>Re>=~}}6uTc;7`Tgp@!8LGIHt&zI87@0W1O>P_8Hr1j&n!ScyHPLts`lxdB?h% zIR>Hz>L+d8$-g7%j$f@i_O$NV?I+gqqNkWV3}g+=o-|wUqf9rx*2Wpq$}hx-mFs8E zwmXvOlO*Y(9W!0E)qZ83mLZUffrtTrO7}QYdF1pNvU=pbo=HuAd|j_I`iyN=XT|51 z7q4qG??$ryNYwUyz9Mz~@x0bv?Gv_Dn@5C!IRh(B+L$3%GCfL7J7y?*Zp+wK@*G_; zTYs?bMY67rF5@#x@KKyI`nA0gGZdRUQ)@{?-($d^(j(67@u=#}GNkoq-*%Mi<@d5@ zz7p27kK!(UrH$hp$(*eh4&%2OuqVSu@!k4L8^<|&rmdDJOR@Pg@3kcnjSa}NYR7R+ zd!?<`D{cPSimp87XBAxg9RtUS=*{9Ddez5ToO5Iz)!Xb?XLe@;_NnP8)3vT^=W9qA z3c2?-Q2#!(r%SJ7cCG8|JVWuh^__VV@p}x|XD?Ca-cmCj<uk|phTwY*Fxka59+0Lu?pE+sOds2si z>kPE-hOAt7PsrCBkf+!mufIQcz`!#M@P<6&IpwuRVjsVIufMW8 z`%HE8D+bsn1~LZt*PV>5yXWdJo}J&lbNa&o1~4#dz<%{4nx#^Y0SsW^83yF5w~x>0 znJzJa0Sv4&z`ty)a|sCp7{CAqFn|FJU;qQ&z^dQdt2n+tVhmsa1M3Z}`uEmVoNb4E zMb|%Ph#2@j19M*r`^}OlZ%HccnCJNaD|rylZ&7S+y z!Lo=jfPqy8=H5*Em60gho7A;qj?~u6N`@mf?Uu2G$xl-bOvk-b2^gd4^;weH178N*hNxw#r9Q%9iJA zk2piIxpTkPEsF>PPc~407mhd4&oVxIt&QU(Tj!&9bNu~n9PeiJ_pjbZre|_7aDM}{ z?}Dw@QD*PDuAN6oZKYp{k$hDfui(TOTUL92y%Oh)SvdyT82APQ`Yk%p zt3KAA$vOHT&7%7n)$i+3%9b}KWhmrg;Q0ntybEK-b|vcQoU)D-?dR+}@!W2<-H%Pj z-Pp|dQ?_+P82GG#74O2Bk@-%Xy_Zkstg&-W$@n=ZWly7}Wvj`@zzPG^KY#7%^--)d zU)9Dj&bsg487bP!p4aY0R^l9~=#S6kj4a1@L?#Ai4cNCTOS9EJiqd?YjiZ#&`y{kD z=Zuu0tm4QH+A-5jTdh~(Bwf|cS8(DCGBHp$(4O9HCBy3zWHm~X{QelFY*dL>HIHSIh~v3a$pY#BtqVxT>BvJy8S75#AqC(hV2XFF-{x9Y8$ z<=CCIeZF3?1RcGuJu-A%J3sQyXKkfkIj?Q6>e^Y)EuEG78mK;Pdt!VP-LJ2+ag;Lp zTjEBGGmgw^7YE_Kvzigvk2NEaT#b!V)gNPwvnRfBqm(Ugu8+)}$i0&R``ojg*=ip} zX}-?JQOcHQD<#Shd~WV7^3~rPi`#vy?9&o-6s75!{(PPkC6M<#19N9)+i#yBt8pVz z(T;J3WLk!-GG1X|=1&LvoOBfLR9Ch06`VMO%x4*B&x@=?_aXV?ah?<<_`I1n)3b=L zG0;CZnS1pKvKl2xet(Qo$a|K7{?p!8uzRt!` z3VBa5V4ty%qC4?*HjYvxZ?0M>jL$QW`7_t<&Yl?^*}StzSGDuAO0GTCK>M8aN_1Dc zrkzJAHn09JSPs!I7-&zOtVDM&`QtH4BJVi{#!q{@65q#|6K9Z#fx3bI3C!^N1X<0H z$bE(ZdA8#*x{rB6^iJeqU}V6a!co>}-B$Z(RK@y}4fv;QG5xfobsAoKSY zeLp#~{ZVH!{}u!NXDzGQT}s_PW~<4^z-JBE^Q)uJ_Ql<=GN7MvJFnff`sZ)$Zd74l zm4WsRkd;+;fO9tk@`T&*Zk^H(2Cg>Hz6IOL)pv#eIRpJC&Qt%~6XDLEHDKR}&-TLI zuQy5nEhVZdL6~x=qfvpGqy}yD^ZSdWY7GWa8Q_G00Y+oH3$g2;^b_ z1J@d`@3%y`drDP1#yM@RUWsvZMLUl%M&B*r%~PTTpO^pj=?TOb_)Y`WcSGLlc8q&! zYxPQ$qifoEjMCQWl^ElW-#@*J>XVM{o;(aZ$-vyZVXJi%cc81| z8CynMr&r#`*zct8eavHBkcEM7HgLQTdKTZYu4?B|PFty0-bvB-ZRfF0+B&}y;rJRG zM+ke4t^!<#Tjiq|#aFfQ3eNi&_xkzRsQsC2&$EvrUHY0fj!@S8 zUfS-|-d|Q?eO%Aj`ib=-o%hEWu6|3m zo}!(c^%7^SpjeYWiuU5`Y&=hi63D~AY6JExOBBD0RP@Ifr(Y|pF~*i973~;jj5&8< zY`cs0(wfjKF^aC}&u2NYda^LE+Ccw2Z>zC8jqBw~gd@5AF+$jL`t{ptoFSQ(C9An{ zo?!_RWx9}>c8pWnTD=nE=!$k8W7w?9Q?&%5UpLU6L|KWNk^KHRPl*!tytyvgd#!lK zW*K%*ZI9w7) z`TcrXjd3KqKgJkjeT#wqDcDw{I~)@-l+o|VXcy!?#eh98xvp)kk76ueW#brwtnV;j zpS_P_d-PQ{jxi)_rdAk?PcRVw^VROgo)8_`ym6#!+WEMSE8l0Jeg1kSwqIS*&SMOl zRed)sh3Fd%v?okfV!M^>@fhQf^$iBbr(nAh-Qk!LC6I@KsDb`zi&l^Rokgf|4w+9k zAkTa}#&$ScAG-@#7!U*Y#K~%Gw6#8pG06H31ODmTIKD?)CsDkfObp}=#Q%n(*>XDj zqt0ah?FRa1Kvr|Rm#Tft)sl^Y_ZhG!RY&jZfjiy7fPVJvywYLq&*aLTsKG$ZKzq_< zrN*P`xd!CPx8rm7MCTYdH_)E;ZRNa%tKVp#e-bzi-}tQAlQv*azP`fzmkii*Aki-w zV6VSxfM0#T`|OXM%U5q7k9B4kzyJn*!vKF4{>F1=ml(hR1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? zfB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n z00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO z0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD z3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAq zFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOc zzyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6( z00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC z0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n z1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q z7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? 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 zcmZ_02{=^!|Nno^nbnMK#@b{j6(QM)P>9@ADBUD2QWBMt3T;Y@O55E+w~8bcMbwR? zP+3AMV+oTrgE0%Uo;l}#bl>;q^ZovRzu#P!>!RkGIZfyNem`H&=i_O$9mUvS`M#*l z`=hoQY&m4$yKnpExOF?D4pNpZUTlDUQI^=*QHD-v3ZsoAQM%J9ihWOkhof@?tv`T)BrgKl;umW(eMjJy2bB^>O4bd8q&Dkzw@_CdA zj${#a@9Q)w?5()EdzBSK5f1_oflO6YQlydz5CGy88CI)yU#;jBqSIgRMOlz=CQ;8P zk*>2gkVbR^(syJIA*2zlb_Z58$nWXUuF0!RRqvwL$sWURd)*BJicrU6|~&mYD~%|#CSSm-MRrNqs0_(eaA30i3D?l0Kq7huNJF-gs|R$wWXZ&aMnVR6?lzfuh}>+G zSX)yZZi)EWBWuj0duW3NK>nD%m+TC9SGZiGdC*d~Wza_C32F$ib&P~PkX1oiRaAg^9_ zFn9zmRB|0{(X||G)>tmQ3!agA>5t_D1%tFbQnNLPy&qy3E*5UGM7&(l6^_UT1O7R} zhZcoB$U1ua7mXD&kBA$k`D8y%^Sn7PWhpq%F8&_8-8k`9L_wH`stUu0UPA5X()`W{8(Ny3_;pvy|>t>3ZAo%G{%gcfU6YHz%1vN}lIF36bHK zM|}-QfP$V&*pXW$A3BF71tN)5CY6W;lS7>!N^TtqbJ0@(NCv(~Uk)R}Pv<<9AhV=q zc=Db*UULr$}C*b;(g(@>kwCox|NIGj#!|6<#!sjuh>*Q~=eSW3t|lNKhH4x8cRz zi>Z5eY~C2Qar2J7sTc3QXz1n0P|3*qtQcoCP{DF%(Fi(T6s`p>NM)kkhjnn?ujE}( zWZmoH?)9K&oA@}%u624lutQX-Y1#AL%%G#&z-F=pe*jsh-uDXpgEMy{;^dBcJEN|C za!H6iy3`wWaYt9r7ak+Gz9$}r{k6KAR0AY;anW!#Ay`nWr(YvZ0R35$+ab3`ILCk- z`$KZi0&p%6a&wmA=;7r8S!iTK@21;`>(eG2#5xsJjd5y%e<%i84o4c%UxJ(Un%RcvaRj z;1>638eZH^*|=1n0uc3lQp$$p zqLz&1%Fu$uMwpZ9iPwnhKSZh6rp3MkmJDdGmM7z$aGi9%bH zWZ9^dN#lS>-d7jG5#hI$!0HZZwjnX%EArYOubuD>?Mbr$)z%e^qU;xYY^Z>$?Y8SR z140z$|7fYmzji7qE_&;ht^B!wEW=1sBoa>`ZiltfNHbn1sfeLSf>VBr1hZR z614IDuaU12M8j-p$7*2ZH{lV2%U50`5GO5Dvtf0b&g=fB2mEX}_)b?83C9ZCS+;%1EoeK# zX=A`LgN1&-=t3qe-d{0+@*1v1SgC?Ix|vVZ$v6ys_&hwpJg$N-Z$D-Z&fW7F?cA$I zj%Y{99RdF~*-cH&JID)tjY1@Y;LH9zPrqfvmhrjQ&~EB6bVBB3IPoV?&Lp4aYQ+oZ z?&Z@mo)QMnsc?>|vM&+8l>@my8EcU^^1FUL7hW+jTy~ETGW}Y6!6#k>5XJ5C0=JEg z#}Q5QU}hv~&hZgh*(#z+ScOPfwbKw(^*Y})jUCe$f48DRprV0e(C$;WpH$R;YVTln zw129vcyjyH?jT14MG&G{{eBTU_@|rBd#QqkJFA4EiZB)8sxsNg@j0YOCY)zVj2j=@ z26}aIqkstBrv=)tr$Y$$WZ}eOo{L`pJ8~Sdmhm$eDE~&h#8y4VQ8_nA02&Jj{eO^I zcW|whK<{ABK8SQO8l@OO@)zzR4t@tBG0sp>ZUhB*mVH= z(KO&99IV>ft`dpCb;jy~R^(F+oX;d*AW!-Qm zQ3IcgcsaI!)m_oZ0VT}~!td(Fsy*P;{f# z&p)C1TJTvb`1}LVFiOmC0bjibO4{*PCm6q@>$C@6;*KEU)Hgq^^}qI8GU|D2ac7VM zHIvuy1x1r$^yOk)SSS2w3E=!#JaKI?sJE{P=9Wb20;hhflkZ()!RFD?AW#Hjw4^@Gpa=~K*Ut{a%dN4C<3yt zMBjJ-wvVKvrwl>+n{4@~L{pF+aJvTSnf_ToycmyrS{?;~!15yL&V1L&Y>;wL%#rafqQYE=5j7Vvn3R@y@mL!4dE^6se! zAVyTpnEz$OQN5OJr_UxE#4Vhn6Ag-Gn!}EgU3B#oFxjCViFjE`cWLtutMm8h%j8~|dS3@yV2aCD7N_$4C6pm{H5z`c5Fx(^ zB%$ZQD9H;o^H%;AAe=oI0t9r5FKVcyOz<-ealWtM(TkRV&hC1NuqI& zwu0nf8wLde@KiM&lSS5c_IB2bOmx(!cmSd>Y=V;u+AuZQnHOb7AS_C#5lZTJ>j0J) zN8wk#IKxc-M2d>WMR8{U5HiRP2e$BA&DCB=qQLWL5^zx-q40Xx$W@#Hz5K2cC$q)W ztp&Ueo1nZ7>B`nwBmOq>-wmfvklWwlPD@-(ewGnp(Ienxc`SGqJ&y~SYO_+pG@Fjv zE&2PU;My@Es^*|m?=yd2qzvNKE&O(--+$HEImn(8 zOwUXUrr3j>jj!&f@AR`!$Aij?e$RYAg>tG7*$|12=ep3r%q5`V@j>{7JFvJ={$(w= z^u7F+nhvH-m8{yPLU8BNvtX?JGI#`yC6xU%ah06Ly=^C-m^S=fZsU(wuaoKG2hPxW z2ekOfDkE9YyH?T>nTOF&Oyi@waSz174|y?XQYl6cYG%iP$+l`v7r7MGYgHRnh5%O+RG_xAo38Z0OPry+45d z%==A|xIB+1dg2b6T;WfiR98958{P)FG)s;XojW+8V6r?{!I%4n=E*A|Z9@;@%Z7FQ zkr>tf$G~4LxZG}hUOQMlPQLpEENKFtFo_qY^n%fF^}g4H1L$UQ#f1Ib); zguF=8;HCVkivA>dcGZrg#3*!MEQ%k74slH0MGNmSTTc*uW&3Ivih zOdVH+?@Bsx_QItLXHO*U3SZ@DszC-Jl?DEX3Oj`8WX&-*CD3sDOM&ctECaN=Jtcf# z2RIbVzl1<`Rp=vqm8s-aH+6x?d;&I= zfM4#CEw&0ZQYTf9a_K9Yv_X0gM7wf+1g z1>;=!>ju1GCU^QzTfiu_3uR@xkVp>4K1?ISrAhAEI7rPZC@HI=eV8vnP!xf&ISQWr zM#3L%ugFRYvQmR^+U`lE!^rf9V-6&eOC}5LN;Lv(|D5J#8say6g>#J+lKA~=fiO19 z8=r>cD=r=WYGTsaZDci8Mc#(SDm1iH_rWf9@D6S6QPs&*4dK@L$nq74O~CA+8)C@2 zhG`C%One;Mp6on$(v~y`eh}F)h`e!>Ks$ zzR9hi*B8k}b^0mZ#4!zp&24B~j5^*edkpC~>qN$z?7NrqCR{}-f@OQiMkD39E zGlRtY9k{%1(ghA991YNCKLjp|J?D0}YA)keD0@yc8W?^MYyx&pe|E+v3cd#e?)7My zBjEM|W*uhWmgP>#YIo@a+My2ykm<%tLB=FN*(~tjo$AhU(X12^3Amg|4p$(W6)B?e z&gwe{1I?5Hl5x^$ zW{IRF!&eJbSQwg7$r57rRd;RS)zsMV)jl4sOIvGyxr0G->%mHu$S2dtDevo1M2weTU3c>Y{ALz(~4LYEr{obd&5`_D; z=yasJsTz59Nhm0(($(myN(!@!g{)@k9(lUxD;JUUKHY1t2nR5k!6Lq71QB-?9`rGy05}yRr!_lLF6BIaQCa<_ zy7I;2oJ%P?);Jld-~ft|&%wejF)~sTZ$ThAocfB4=Xen4(Jg5EHmX`On}0@~eqP+M z5{MjU`9eozFEq@*jkwUNxSsTn112`y0`R=p(NdUBXsHGtcM`I{@Ckj; zF>4%dOfUh}*4*ipi`s64Eu;Vxla=w;U$&1-!zc<%gaRIqClE?zf0!O=e|bHAr3v<& zg<&_^MDpG{Yt%rKghqHg%MsK+F)V%JFuNII|K(u=+&G*&le-y+7S`y@`#kDGe>>sG z==iGXD?JMqk1()n)#sn0u(Am~S>(~9i!gH(u?&{mc%y;yrql8DUzDQI?Fw&(7nlz< zYT1u`HrD^h-vAzv)S4+~!gY4Is3E~=b)|$sX`vHfdUgnDiq``SVk-Ia-kdOF%mwi| zRM^ge5#dOCRsQ9p@mn{n4g4)|?S`%KM=$4BwT}o9nA2W($Om&)jKgwz<^0MR13)jn z2^l(T3OE%?2NRUkPYZ^kaCUWYrlMJ`M7J#8 z3UDowurFHz28k_lPEq(=fbMhdBM;@Z-i=>wNChD(!@?tY`+*Y~S8v?BaW&(_f$hN_ z77Qu`QVo~K-)+U7`SF|&9he(l#F4io8UU7;*^(kxz$#zX9f7mJ-p7=BPF6FYmWQ0j z2Tpy|G4Ev>T8_L^43($iUJNnTq8qi@f8blofZ9r+xsc%D56{~!(u(+H2JC*WfQ=L)ns+nFQR@Ptr^^_46~sdll7^24rQv1hwt&VNStn zRnTNd$ryHNnd=r%0J5HIA z1XNvJz=7XyS0nhA$p(_t6!#9#oL5)Nlzv)@OPFa{f{mgV@oV@s8umXws4bt)hmw&H zwVrC+x#)EGE4TP{^Y?KPg3Peq-i%qQ2@y;-WHDu;hLpuP<+Viu(Q`k7 zW@0~#3F+mvXvreNUM_15uw{zrsBjA#_BqD)G zQczOYUFaORGyUO*LG17QN>jbHL5f{cjR5|VW(}!DzJ-5gn1XK4;qI+Oqa3)^3kV&Y z*bJ;6?{QJcMoxm!m?yXiz6zwtLsi;Z6=RT9M!XE-m(tlwfAxm3-jo#EHT8>Oub@n){esP zVCew|DrmU1WJ=ziG9McNdXe{`B&}m?!3Axl6yD%Q(7Tf#3$7jOaa7Ec9{`SEacwFZ zhcDt-TJf`if>Esq#B&K8X(--qBnx)9tH4gtoKDc3K##uc_J_X?nFHVXcx#KiS@}eOw=o9kX3b%P^Y7gGIRk#;iJI-7}E0Ju5uE}@e?u)!NJ3c9GK_k?# z$f>Z8dV;5wFs+%wR#46C#TB-KPreeak7-09-X>Ey!0j1l16Bjgd%A?`%dKV1Z<$&T z?iDrJkdBHv`~jb4K`0kZ%qgBdj9FPTy*p)X5cZlR*40VeFz$RyOByS(E2IT z>;w3Bh>ntMnnZiS^;0eL8MRzrYRR-Wt$}6WJaZe!nKJQ4qBO>?6TUtHTMKc$nWl)B zCu--9x?q}1pPLE2?xuyK@x+&GE0ey@I#w*E8KY)q3lJ-8TC9*RZE^*}`-PY0P-1%F z5)a7WSRdM+WCAd@U}MB!!M24YP;1$qdrf0fR5IRGS9JSKQtXzn_3PG$ZHY}fbGxXn zYg~d#$C~c#S*8V&7H%sT#0Hlw48SC*9ql`20C|+ay)l$ImxTS{plg#fU14#PFc#P{ zQ^PQ0vaEDI_nBC+UlQZda9a0kD3hy?XYNNd=d2!X7C{9Gv-heC&qWV zyc|xTcD(?eeIwjvk+Qm=GPzM4pMHFjc4S&V4DnhxeE}*T)bWyB1jM zZp}oeGbtT@XGKcl4{pJm6~J9l6t#pNb&p!weHM~82^;^|vIB0ULe zl&CJazzn}-ke@`ddIzVIoI5Ah;Lf3$xJYR(eyjW(a8=@^GguB?m|p0Ggy~Nop-mpA z^W%)JDf9N|iMQ(u570*MfK^PzUDz?hY*tw-`vXkG%=$R^0d*;B&>MeLT4zO0o#_by zonNE1KA?G)fR}9t>S3cbJ1>w1>PI~2howD*`#p7t0GVO4IyOD0w6?8_H8lEjbcof} zR$H2r9=qCxK?aCAp8E@Xq;UU(2z`(in8(J3d_B-En)#`N__ywVG%|m_=jw*N;4BXsCOwctH3*>v;#u@?P9$>JZ0LYK73&t^!OX$8e4N4 z;$y3l4u7%=0u#qnse2|GKAk#m33s}ip7Q+%47l#2jOr8 zva*KZ;uRZ@WL5R>5!rC{xitnjqIql?2l<}qLQo2MDdF72lA(ObKs<$URnoZz_^pS( z8}uI<^de-UmvCX?rwYN694HChMtR>!i$K>g#&6;ptAVn{SwYhB%Wq%*zy0>5M#EJK zoBrdsm#QslWI zZ1>Cev0p4qBeqomPdo5AzkHt+zVi87#RgGQvjEiJRB!=T*{iWueiX``@uJs{IjL6* zf-%9Hqh!@A#C(`0*;Nhtndy=J%CrM4_?17AzO_y^RTxAEjiQQ2Q9R=1T-^nV4j4$zeM zk4>Yh0BzVLp?v*-9w~le@u!YIUcvdb-z4E_77BR-?BPcR^t4j|P}YqYT7b=gWP8 zriX<7>*OEV__hilZ&))7-K6rRMP)W`KZPcMXVBAN#7wP`_H*PC)$BF$Cqw1*^l%(S z>m*y4YX{6b#X`TOnt}8+cYh%K`qL|C^V(&*ZoK}^g|WPLob|1#_Vr({{(4>8^o=!+ z<+U*P`|BIKmd)n1D^J(+kso*0&_T1*Zz${3Jisnj$Udn>i5s3it-|;dd2L3$ggnD6CO6XBW9b$y0Id|jdCYPI9{pq z4L(I4sQ%@vwi~aEx{6<=Zrwe+oOl6wI%iG+T4YQ~;f=&8FfzEjE2f~+J)XFHy((_W z$u^m&F@1$PK(*L-xn@v|O2<2@^DiEXUhlu$)ydJxb-Dlg=tCFtt2@S}sCcmE@XS}7_pgaZtrc)^%c65?&G!(_^iZ`1f)up)ZTjU=%h|D4k5TG1@JxnGF!ISb0)T#+b ztp>K6Qju&guvPHMnvwz6E+NEEjPIu~E(s>mb@6`X((zkPI9h1&6w-6!H%&mCO1bjS$)<7(iHl?CZhY~i#bW+#;L(U?B;tWlN*J?i3U)X744Et4cbZW#Boi9O)co>SPh(O z)=z|a&V`IOizynEOTHH;`_IFCY9)O;|E)>qZ|6TNuc)jjf0lpye9~5bJAEb0r=I7Z zT>M=kA1sP7!;@XkF_E8Ht~ia@&uGgg1mRqbvoLrHJIqbW@qcKBw@f9Fb zwqD_dz>oSw8Vw}D&$SFI;jQdmjLw(!F4RpvAZQg@2Y?~=kj*+H6s?)ECt zOz9d0rHBUf%N7OgbRPQv#V{YKG^d<}D-E@t!%0B2^a<5Z@Iqm|EEBjQb~hY*1l(oM zi9lSPkhK=_)%K_(;qgbH?iEn~8hFMe9~W6UOHblnjGEYSpAk31H=u3OLPghsemBxZ zq(o0G3#qZiZD9$o=#n;lLF?9$RW{eb6ZcnA@z%-jXXLEHT{g;qg0XKx)|(!V6q}o- zCx-jGnH|jT{^5yfY_5`WdfsFu_!=t!$~L4wecBzfz0gp#@eBqPegXv{jZ7egx_F*TLB3U9BI@#@kOAvGi0)B09t|@+f z3tAUMRF7@L5HM`N3A{Imaw-lwVXDb8{6aXl=3T#GpE~Wj)?B7jE+d0=oqXIFIhhP%u$e zq~bK(ZfM48(^@wD06Yo%>Wn@H@+0k|Sf}Kbg3K4*cz5)x9!%Tk$E} zt3f??;h*3k^f0#2PXIe5Wtyh%MSG!@J-mGct5@hNJJ7ElZCFRt*w=w}95Dg(VqQ&#;0aUB4w;RXh2(RbOy|kTcmn}BY(WI-YsM0lcOcpO&yEEblDn~v?kk$8fqHXaEa1q9l@&1a#=mgX7TeBS5z}xJ^AbW%V z3rA*PAC#E=c3_qxyA^JV5fE{I@i2~T5y*&dLp%4WlOsMO2}0vwbd?G38dyAP z=q9=ioIql57&D6j4VMXiKYQH?$D|+a2zxoBJBjn|N&8}zRAM{OmKY73!(|pal(SkL z(C|H5&q0OV&(lI}=a4~=MAI{K@bFs`vMywepNE5)9*qQoFy+8d3moc0HV~KGw$O+)K$jxxH z7`(rue7nAH zYWXbRejel77anYaC+~aUHR3zax;3+WJL|uE`(sU`w}12Pgx@8(fAj79C6@e~vwZs= zsiiOK`8VILae9z=kBQ6eA!TsphM|Fa>;fQDyqwY1rs+0Ssp2u-WMJ@K5DDxMv^(Pu z2zo<+WpyZa3%i!WtRoDZ+kH%{#u@-x>+^nKz+q;v2?gVd9YW8NbUo`!lel_pc^oHQyc53J~*?vwlK3|0_h z(F0JnES!)7#{-FQk=p$C;v}4Fn>bxb=Z@^#dYoz2%tVF>=#V`rdl$_B45G_9=*Y7; zTO~lzEac?V&uj@QpJDg5zI$2pF!z4$!=jh(T6@_ua#X_p{PbjqnIfQM8~1Dk<&;Mo z0F3B&vdL@*&?IAGCd(AJ{+sNMj#9d~%@vmSK;scuk)aFkV3MJCYZFXC?dUpq?D8UD$$in! zWSVN~)Yxu<)obLYs!5eB1zO8#nWxt{?nPASHyS=g-=bGxoPf$23Wx13*fc( z=vfy2Ni$g92o`*&921(m!-=H&E{)%?^FJNg1^s~}V2pf$vUNLeD-bfw#weV6q|gi( z`WdOhT(e^=qzOY@79=%F#f@ppQ~;XGzJhOjRK{*Axq2XMh0Q#MnlhE5K%pwDG3ME< z2s?1Kq>U{@`QHlmxzGTWWoeCKX=Bm?47O=PSjRADuL}7biZd%j>M^p2JsbjT<+WQY z7hqk11mq8B7m|+YrQbBV>Pd;PySeZf<$D3xSPnKn!Lz!qDorJ62@=&NE)v_Tz#Cjs zH_16LPvD}%?9jLT@zunjiL)A)CV$RgJVo;u^h{|-5V!=R(6>`nW7^S5`pGHoIbD3m2;f5n@1efDrdq$Y}TKnTZ-JI={!JcoWM10NT~~8 zeQ%nV;{<6%*Ggw9V@x1L=j19jBI+(mU2RUo0Ytj7olj`={*<({=gy|3?2iuhu`{L< z0UXVIb!t(!2w_(xJ1asaF_kmY+Gs7vDTgG@hnBdX>;#0p_rE1CRt_ z^?;$nR4F-B@_@94|8}lP12&XJa=!b5n?^VZWTU&Xo^52!A6`GUBhXG4^HS7oHeG!`iSj?D zdnkh@yKBV4^7TsO)qlZ*$(rPCJ+iw-(E;7`Sds$-??LBb;gR zuRe}=nlpm+ak5f2x*GiI<0Q}aaau2|_@|GP=ZRO3`-Z*``lpYxs0@9gPrHRYo$cf7 z!1kIuSRdyB*2g&p-sjsa;@t*{S*mfe#XjhAXVh~!Vr~-8RXI08$zap=i_N`|DAk^7 z!XYG7>BA3G^MMu}i{9QP^b%PTxMp-P5QrEZk0mTBMjC&E%(7=VSIhwO^FL5#nl(<* zBPCI909 zR`q~|QzL!M_STlxc4ptmlo0z9!NAA-lpu2@fMOn*(9e8UXDJPqg{aBq}nmZe8@TScrXLmh0C9I(`8|wG`!qYaGuaw0la8|iar5v z%1C3$+QOZBqCFZDnfRvn_|sxXGfn|^TWuy4Px~sCh{B+AXcooc8+RMFfp8OuX3x<2 z6@Yya%sQw=@O>tneio#Hvr7Ibg1fTA<`E$cmmN21`msSuG|}5q{VXr*+NFz^u4Uyt zt8VF?z;fs5{+b&*Tr?o!ys+#pSoAU33a1kEY+Crtm!Nf!1sB-^E9%i_X2b+8wmoj+ zesiE?q3575QHi4ObTMf&c4HNA%pAq1@$Z}PnG+g&M0z`g+R0-UsOzf#!Iu#J;Y(`% zzsr@Bc4f5~m9o?5g^I@g-X}tPS`rVC{x2|1Z9zYL@|^8~0zn zWWVx%`4XjtzxWc~M+4)wfBBM{fA|u@|KLkjxuQ`5+Sy@+t3%W@o=!O8Yr?$^k4$}fR?ksJ76-bWMdQh7Sbu1R`E$skYOxDs$Ek$3GopT{IveEu>m&;XC07x{1q zWj_j`LAII0^7?S9%C0VXi36~*QJ!a@bVk}_M@pBMYMXrI1>)XJJ?G zLgSuxZFk`vAXDUIF35!5eZpTGQ%&Kj9UZ6Mp5AJX_&TFLzxsn0@pbLgSgb$T+M(?s zc#8E03n@Mmzxsp6@yj{{`~U3^cEbPk2cP^$UqKa zRpYyqrG29T;GXGkp5W$T?jb7U^32avIyOcrg@1C?A3y&RFthTdG; z;c1NVdpL@^zM1V(Pj4S@&!x6z`sx^ug;O#1+;Q#AkW4=E@{o%XsJE$T68U!A4A93a z$Bp3%>d8N6E;FcyxWk)4&u`Ovz<|-drIfo^>G~j&ff*+IaD`m!MclhU-WXOM^0Ja` z(-XxQNJDLf^s_^xtX|^vQPl$yD>pbnp}9?YBXS7J75XVxaP3$O-Q>2MYrZbz7_HT ztYo(EWG6a$f3+IG2)fZOLdNT^ZE@5lVurM_gMVbgiA%Tc<~_)}d+XAPgh+n}W6ZuL zYCCSZRyU4_+HV9g0JYWkN72rc3$g0kfV{#Nns-YyxQk@+5UE~FOc6I(V+Zg}^75bU zCPrUbW+r`~)cj=_2RdQoEsvoM=7RM#09NjLTMkt~T{jg*E>Z^W;2PgSe@q#B!O8gY zp9`$Us}uu;MU<7C21C8KqF7-4*uYv~9gCk#)wv=55vNE?=tbU!QWXN8@?{?r&2bc` zlo}4oZ^_=_tW5w&S{B}$4rcuM^sl!cnwpzFy#4FxpBV=?d0S|a0D`vjj_ejb%Be|l zqTtLEKg#$|0~DyCZ;{>vnqu4!@eLhnGM}{$SUWJb9*7ma)iZ41tfUkP0~M>rtrq+& z$0x&H##4Vl?;3G;1}GOt6mE7wS5fj}Gn-U`WY@ui=xJ;) zen53)4SLOT0G6{HKrra?8hz)DaRAf2Y+F!2@iW5C#W(q`NTT7Y8uccJ=Nv zcVE=Cb^jQ~IDp|F-EDO*?w;Ab+IH?Q4j`ehTLSmzZ!*9*fLu26IZ+?9&E`#G9Dw&b z^tB5ZJj(%C&T;@=gDh8!1BeGsqX)t3vLKZvrg|_EtJqNmW{=YM3-zNUdhtA!-KKIs ztiin$4Kx!cD)y9vS7vN1rV6oqHy*kpi^N^W8sCvKA57IAp!YNwc}Ud~+{W+HocYkI zH%L$79LOb`EpJP;#8F+-K29TJRcQg32~C=7zwW@*r?p>yu(>lLi9{lr;j({xt$lj+ zz&iW6m@i};kXAK@On*#srQ$48+vMzQ7idmm5AtReG(SiBeIss36_Ur$xQ9MaGyO0V ziM8Rc<055upm;0~FPyTlnYjX%_b4Wc%>rkCC4-Bf+;o*EVi6{rw-Lr9aIgXQI{0FM z;RmOKS>oT6t2yo}FNNXIN#r4IVZ9`kXj3Q8VkjTujwjQUc4O}vL{<)aEfJK2VebFN zhXaW4bJpg?Dj-42c3sk~vgW>Vf#e@R5rHLw@xJD=TS@C|wFscf;>}r~g~-6ejacFk zRw9tR4pSxvVV}%yC0g=0_n0yxOI~L~OqJ9xq&-AVLgB)fD(;+W<=@0vU^?u_7g;2@gC>I1j_zwXZa zD~fZC<1@3JmB7-aV-Ezx2rCv45m1rP#1@J~!5+#*j6o4a5DS8eNC_$tf>==mL=cP= z4JCjGEM26tv@Nj9wwe1bn&h0E`w!gtanE7*%rGqP^FHtQ`Fvh9_H#fM0|RFtQg8+W zDtHm6zwt$Cn42jBA`g`=2s|nZM3VUNInfw@QCCQaH))c7uG! zdhCg@XeQlesA<8HjyA&;>|*Kx?2tljqrx$X)hcO#HZOuPc~Vp0ryVg$OYJ75eWIV^ z8*RgOY7IO?VkKMEn^?Ncu?p4ILrqJyUkgF%f2!RMIXczvgRBC1XIseffp{p|5MGta zmeqxUIY`%EI;FyRTnT$g=^ho!B-b{yF-mG*WjeZ8TzD3Jb}r24y_q_8{X&>3U;cGIW=_a>-!1qKohW z)qVPvx@q&6D-tfQ{6-;G)MyX6Hc7LM5$6i5`4EHR@?0=e;H3^PJeWU@jlYVt*Fp#y ztM*)YT-OgaAgFkTGd4WXKQKJTnUTli`2BT{FYH;RK|r+ZBVU1cAm%3TMPPTiWCb**A(}6~3tyha;|I&8F(FrroIzgf5EZgG*L&??TBud-#sXdfInpH+x7}1%QMwNX?OMk^LdcM66c>XDtf@cGnj~C_jR$F zKQyzt`q=VmDG8^iBI76LB?{0y^8?x4QepKOOOk^9ZMN)>00zb5ceHgMRXcvBe?QS2 z%vP)GK0&Y3)^rVSP{@`ZL=t5Iq(?lf1%pMzNQsTfObn$N?Eb3Z{C7}gE?l06v}KUm zvypcdNZPobJ9?O2)2hBzlmdr?PD=!Kjo`_t`&uO%OH*F(9J_BsKRGq_vl8RPfcWux zL_fR@>%O>>ptvciT`H)*<^kG-YD~Yl{ALi~Lq(k7?&i9;73CFg>zccVIp9V_W6k+- zeoQscc=WhdFOaq;ZBitxyx5Jkh3g~hABzW0FqD2Cn>tB1x{p0ypqz$ft9)I@@g!Ws zB0v`^98M8!Rd21KA4c8v#?z3BcSv?SDWeU^D~H}^PzK`ZgEz=6&!I=3RQ6)KRNMZT z=Z(gI>gp(*05A>vXG`^Ti=-E*<`uGJb=51v-UAff%ahaT=8*A~0kq-xVn}1xy&-I> zEYeOLCL1`0-mV1wPfW}k?rf+kEzEnCS6Eur&^gQ#W1wGJc{|k6fDEhKMV3urL-%%R zK#PwzpaWNoA@lU<$;-Nw1HHm4s_MzI3UlfOX_2m8>Fj<0W=MdKf(k1J^`7@fo9ZrX zuga%K(DN4ZjX`?+043u+(sgaFPaX56PaPt2((NxHAHk0Q-KP$d=Bh2NngwYn@HhLF zZu(rGI_-b;se?eDnr`eF^|6=qoYCA{na@e(noueqw|(cSdqo5|^S9~D(K_i@A^VAAn2!OwyL zQ%&|}^~k2*9Qztp^?D)X=WHP8Mh=Rzm&4Ar=>5gilhf^+;iLS96^fZ43%*bEkme@L zAs)g`5OUdzY{aL@byb9v5ypw>`5}DO=s_ww{NMTZZ|3std;ZS1$ANtN>VN0kpZ=3? zcc06*&&u=d3+M9fsc%00oo~Mj^6haT-+uS+eEX+2sdM@Eh4Os+>|DOx{hxgM(|_mN zSA%?e+~4{3J#+c?Z~mQc4`H7VC>#UhhY2f+uu^Ntx=_{;s6ufs5^?TrrIVP^4PsH#4G#kXLB}p6OCw(M%G3>phOB$ep4R z*dZ$fpIwR-tsop89{{A0v99%$>sTVmgI}V&fm5WgTY3??!(U-OlYo>pQDV8;2gHkh z5b1?Z&f^5l@k{pU@z2q~(MTfS)Kbnbk;wTa)+%LOS8}qPU-A)@p5AhP2^f8S3HT+& zDC@8?ZTl7a#7ANNpR6chTB+IW?tNZ_5g@D?wWiDbaS6N+#|)(kN>$eUJ$2dF0G}|9Pds@ zN_Wtd4z;2sc90c#;!MPUbi@bRG16;Kx`L&UzvI4ET{lrWZ<{0m#95i&@RHz{EZSMV z{$9y4FNskg7cb8PFW(Qm{J+A>`hl1A953}1Ug}-E>_70bAH>W4954M6y!5Z|(oe)o ze-|(PcD$T_;N?6DFXw}JIj_ab`8i(l40y?x;3e;am;4o8@^E;`C*mb9ikJK@Uh>p< z$+zQW-T*K2A9$I^!OMIUUgp*CGCzoyc}~2{*WzW~887qac;Nxyh0lN&UIt$HC3xY< z;Dzsl7v2(H_*Z!0k^NOC{=f6#@WSiE3qKJrJWIUrMe)LW#S4EIFFa(t@Tu{_3&#t; z9WOk6y!Z|9;+w#W{{t^R7QFaz@Zu}Ni$4l4J}9P77r!%JeA{^OpX0?x?;=Bb5qwT_6*98f>CzC1Wiv_ZG0TG?56kGy*} zSJ8T)lL@4ZcNhx`(Tw?wIAQl5;-WOc=s8`2L#~jQvxP>nx!#F#%YL>}gcz%jX4H*K z(V4#5XUUO=cRATG%}u{CHD}s5?K&Kp{H(Tb29=K2WgK12fE2BMF5{w|*KH`YEjc`4 zt^+~$+^8UJ5phqqFpe=l11((0xC2y_%+3L8#oXE52E7YupJ}lKP1Bf=?sg8}v zk;wsHgjs^U-T+zL7Z1l6!ORr4?9bzXQ{7C>fwx1e?*-u`>P>$=vcjPGqnUOU*Mo2Y%~4%cD!npqQ7-nvoZ!_az5gw==Hcx4 z4RcukKebX$9?w6b$+8V5dP(Hq_zLihs|${&puoOeS2Tp=0z}nf#oimcSTydNHqt&K=9yJ zX`6j)Hdrz8n(mFBB)sU;+5zB#$A!Cs-bt!;D)DgMHO;;MAHY`rPlR^ zau@*qeiORTp9{7Vldwy~t;4;JNa!?c6SSjCa79BgY)F^^tW|fo!e_2@ifwW)#?B2` zLL@EPd9`v>Dw}F8Oggr6y@{R*olHbv7)FR>x{993`klv;3R|aS($UJRI~S3NO9FD) zSZ}f|h3@)H$h`y1S{cHjFhz|kg03CVCf0N);@CU9m3RqDB5oHvr@IJ$131LPls8>! zyD%Tkk=saB1^l!De)bNkhH7$1A8%57ZxI?wpv)o7?=e56rUqqCX*?8-9RtFNSj1<# za)IVk^eX*pz(CSgislanDQHBog;`7h+53pKMw-AXZg+Z7{@d6sa$k|n;hV2I0d7e; zGybWyuClE7WpP<$UF)ZD;46}FI$zy9Y$Nv-ZHax$M|-Hr*cD+k{|ZH=1?Y7(m=S?OTGD#lvDGV z)PG57@{BS&f!tCEWsYcjpn=r#kNRu5xnytP_S`;Qs3(p7IFex`OKu!rG8RR9D^64BM8#uoTmZ- z&~4z&kVGL5r!0-S-)ma{Z^q)!-i!z82KneM4UH5D@Mc&QqD{V3)sQyqeXtH}0=yZ# z;y@D;aj|!b+?&x~o_^eOmC^t3W*DvVJf2?OFZX7ocrPZBOahB}z?)$L>jb~Y+Co&R zzD;PM74T+AQZzJfq4@^t4?cS{76WgFh4%YyBg?6WNR$M4Gj0RD67@VsGf1El!ehkE z;{?y~Wc>Ab=cp|uNFv|NLe7(s%6T$2DrG!Z@=ZBUre_pTYUDf_i&t1N;K{s1Sx1y< zo+aYREICi+f}AIF)fHHq^&G-7Ylde5du*_|;(1m|eC)Zi=VIeivYuBo4+2Kn>~KwH zn1dcLLAzd+^JFf_c`{j(;u24q@(~vL67Xb-u~!yyo(v%IjP}TRGB?StyfPIVIZp=o zxqg|pFykj8B`wr*JU#z8o=h+&LPxyY40T&B^VR2_CpT0Pqr|pGLodk(0r=_)8Vs=M z2}Jja58u+0(5D8B8`2tE$fZ&C!~(K@Dj7V#5HbTwn6CJ>RFe6z;&F^yeZzOTGE7i0 z-V|`|cK*AT-jN9|pU>w`jP$m=%fEdtV3V;50ajk_d!w3*jTav?CsEhNcL6z#8MN^H zpya7FWbs7S=mOc+NN+FPCu9p`wj!&SGAxteR!37>hd+JN_}{h9Ylplh4E9=laFJjZ)={D=2S zr)+uhh4(6B{l$9?n{KfoMa}VE#~c+N0^aKrzO5Fk9`Ig|(3>g<33I&H&Dd%u)YxB7 z;%EwE5(xyeou9&uLdwsb6B($>3DwLlyQ`Ei@ql1>U*HbZW$;&daj zZ!=ozOw~n?`qHEumUO=TfxUmu!?SZgqn;w+K#>);4qf zpg?hf>dM}>C5t;+bXRj85l+e6v_RQ$i>>J|vv81Kyp_8I%;VStBIHJF^Kwa$GLYM* za!ptAG66yI05J>j+tUHR{eYa`{s>EB7(B!ZOuz8kJ*u$QKy{*B<`;hZ#<+Spzdd4( z-)^wZCn62-+Xp8BaZnVd6TmB$x; zyJ-RT(14MKJ(Ba=4*-69I^efw0e*WVoXJ~h%1s5zVC5jm^3B*rK;yJWO+?$7*g8jS ztu5xZRB(pc@Rl4db=K^tRdx|&5P*}0OCU1h$(qNU50E;5*}LI-qY5@Yd( zfcT93+sN2!K)q%}6k{)t{^ICS=R1N{oV zCkeQE2pJ+5<=PHSXK6V3Z3Fd;V5u8syB2d?hcUM=kO7%HP%zo9MtyW9Z^A`AYCF&{ zV&23`!;V%%K~-#^`8NjW>)Xb11?m`&272DHnH90|Gr{>wVO4A%610!*7v z4w=zl)isBIc{?P+B$M6M`5E!2Lk|AniNW56JBR$Y-+(!pi9_UmoMfKa3hrk=&Rvlg@v|SNe1XP;fBZNf z<$fHuN{kh(LA3pKLRxpi82E86$^AGlBDWX<4yyiIual{H@BZb-d6$=Z(rc|g;HVmI ziF_gV<6HuM9ODyp(urSfi5kHytkO;H$NBh=ALoI_g7VLP9HN)#?q@#^cZC@*S?wNd&>J zofk~BEJyDzCH>GLeaM8qz0I4s??6`ccr+zrC2UlJw6so`g5S{Q3D6b1vo>nQN6C;Os{^}TJeGOB<#uVc0dBTJ=-9MI@vvA!nPXh zk6l#8fjYGRDwC+_l-`SSUZ1jNfP#G2#lm*33>A#BD)Ta~MTdt51qOwNM_03=}wA7=GA_*?f`?j>kq7E8^QRVtj+=2*24`%9J<+_@JZ1PCsjoneE6Kdu?8>Kb7$5^)AMB=Z*i6hlilTS$; z^!m!E`_O2hKR*suo*N`*Hft5_Dq67Ms{~2C1GJ?UeQpI=Jrs>zUJ4nX@5Xu)Z54TXmlU7tWVED 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 274/511] 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 275/511] 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 276/511] 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 277/511] 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 278/511] 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 279/511] 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 280/511] 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 281/511] 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 282/511] 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 283/511] 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 284/511] 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 285/511] 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 286/511] 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 287/511] 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 288/511] 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 289/511] 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 290/511] 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 291/511] 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 292/511] 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 293/511] 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 294/511] 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 295/511] 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 296/511] 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 297/511] 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 298/511] 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 299/511] 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 300/511] 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 301/511] 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 302/511] 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 303/511] 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 304/511] 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 305/511] 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 306/511] 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 307/511] 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 308/511] 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 309/511] 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 310/511] 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 311/511] 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 312/511] 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 313/511] 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 314/511] 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 315/511] 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 316/511] 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 317/511] 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 318/511] 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 319/511] 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 320/511] 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 321/511] 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 322/511] 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 323/511] 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 324/511] 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 325/511] 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 326/511] 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 327/511] 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 328/511] 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 329/511] 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 330/511] 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 331/511] 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 332/511] 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 333/511] 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 334/511] 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 335/511] 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 336/511] 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 337/511] 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 338/511] 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 339/511] 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 340/511] 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 341/511] 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 zcmeI5Q4;L9Z2+|a;(KP3M@S=Zod zuI1@yUC)p(fPvpJpuZx=e1882OV8%Gnq=p5TwT5L*Q(|+#{dRa7+Cp++OLR?<~r3R zJFjys$47OpR9d2}GGYJ&_b{;j4Yfoa&Fxr|?7Ysk93R!WTcxM%@f?$C4E(BrmA}z; zhgZCzeWt9|pPdl;W0l*d$!e9l)d`~=XQbyDQ?Z@vE6g$Q zDFbUyrKS03ZjYX3<2u`NeDoylRlROb>g=u3TixS1Ce;|2GqC=7DhYHP|rHP zf_nv1j9kT@<5G=*x&b-c?O0{DB)?K6Z*`t!<7>z>)U%G~xLftQ-S53vo{whw^(-6D zkg3H$)qp*PKDrlm@5c9N<7&re>^bIEZF`hxo-2fzzn=?3M(M5{C8IXBJiSt5`l^*4 zJe&AV239?3eFockb;m79uT-g0r&)F`88hR0;@-_w+?l=hYHgo7>Sf2*G^w4(IsHsa z?B}rh4F=XeX-o4_)tBekxJs2eOVg2L%#W-7eDR%J#r5}@iM7#VK0_S+0zT+7UNq&H z&-F|79Ss~$SI_F5YD#}zW%p^@YL%$#`gEPae9nM1JjdFrEzw8yJ9yF&*O^q_(ZKO! z{jA=tC)xN4Zk=f>S9NNsMB2cbKdpA6eO^A&cg@W`>4m4V~Q`q`_> z@pVtK@wo2w6-$!nbzLrlvzhbtxokNB{ju6tpC+qSs?-T*A7z#C69(+F@KLoR&#`fp zDs^jVLS=lW0ec#BRNs%LwDUT5^e>h(L~3UYv`<&B)XAD+=XIv7hXWboEM|tSNS0XHtoQo&kHlBh5pw?F#~d^GFU@@8q&W8mDtz26G^Hu&g# zXIvEn)|;$WM@#cjm1^r&ok~k3!oXDq{Ci;I>Rm0xM^)-C)p=)oJY%3wl+_tBwHSD= z0eJ)3vAU<0XKi1bF>noa zrXy-G@OcCM_hP26K2cU@$iGl4p8DgAf%U|hZm7k;T@2VaW40?x^U*B*+v~kA8_({3 zEp4_N>M?L11MB|#oqfx6bf4YcwG(Ztov-P7-IHF^4-^c1lY#cVm6dPW z;NO?8cg!6y@QVhn`Bk-!YQKFF{o<2i<eKIs@`8*~eAfwNy*f(W-voU;qP8FtF}7 z)gOQ8FUc{VwQ!L1Y=){ic0NNrYF9cvsLU{cfprE}zxkG@qnVyH$Ih!%OYu>ay4P9S z9E)lUVBi`9_C1wo?p{J^#~E%*la(q}>ic7rdeyJrdOuWR;JXa0`g>LX&EHn*zQ)WM z?iJK4n4_=X%--wIf=Ud0mjV0E&hE|9bW|s6N;|J|TasR>P<4HOUZL{+t68H81FsCM zd`q``^?9=Ts+-Yusqc@Ys#oiMnykL6?q8Sc{@Aa1wc2ON>Z@w5V*mqv1M&{FV?Y1( zYD>~9uj_jmn$Vy3s$Q-2NqY4ap?6)^xAR`b>$R4nS6MXeyZQGjcfb23?J2)y2=vu73LVYmw}aM)v|n4*|%r3afSLVf2+P?@0;No zcdu%n&@6GL8)`8yHn94v_Ia{8?&Iu=RQJcT%Cl>Inyj8xo?Vmr{&-e-cCAm_R?n)= zu8oN^)a{OA@>xGz!@&CutUs$`V*S0kg1NaKb7!8!JE`?P$6an$>fEcW&U8dA2KolZ zZ$Ur*)#{ihS6&f%*QK^U_9|j)ecrYjtM6xBL*4FW$^E-WS1rp&QA(TVNj7ftS;4@k z4A`^jqgq#!*`{VAAZQT&aP;Jou5@juC1j-?)d#VtDRb&kE%XB=ZLFB>Yiudco<}j^mp1D=kT{oYzLKt~slbJCTEdi~)Vl+j)j0xh+MnNETz=fc9zURi>r* zs7j>{1NS%JPx}#9dOxa1vkH}(!@$VvzJBE8W@BoOLpBDUWnlHmAM>^=&vJ9eZm;n> zrryy3Jz-#-f&SUA^7}Mdtx~BI17fm2N>+Yc&C7Q{1qME4z@Gk@E-cAMGh}N0e^2#x zw|K^q<;{?&#lZ6n%)LFbU;7s6NHQ4f2DHz1UgsW_YfizzV}^lWGSI%2vhqt7uG*`( z?2lL76)pxa5H-NN88wy9>${cJJdX+tU|_ugc_a66J^y!!)~t?x*9kkvz#R?nZ%ON) zivHr+`TBc7!~g~`a4!S)t0&RDdZZf+U;qPG8IZ5uK3;VfxER0y2JUKre_y!klc6sR zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~Bl=2Cn$8R97&+`5CY`3}7H)VC9$P zivKO8E0~*?mHWPe7kPH%U;qPE18cu5mL^fPx4lj%cAR7S|Ep@VwDr9Cjv!zF17`;8 z7e%78it}qiXvg!qv#TvjR?e!1cHC^L0&V=jXMft1U;bjH=Gp zHNno$>qb{CLr0?u)-iyAyaD}g9rHYAt2pX=vkJ4vIo2w~Cv}yQhuH=d5yr(c%?>o=a71pV~ z!W;w7F;MxQ_}$re!ACy(RqX5A_!XhO-ZJz`n;%)xgmxYw*qoN(S8V>P$XB=VE5hh{ zOWIaO*{oyWjt1;0p6Sq%e3U19f{pXMEB>x}1=H@+NBMnwf{pXMy~0v-R3%b}f%h8F zr}mgvdpHQaS)SUX9BX;r72m2Wn8zM_v;4k$lw&Q=+ba%&pH+#}Vc?wx{JA~iN*71< zXqKgpIQx2*w&#zTHQrXWnmOieSMq(X=AG}k)?ZIEy+g}vS--1LL9wkHP%tJqO*gx&(<<6O(K8Kcq{}v#_MgS zrSu}3AB%dN>K8wDN3xz|VD0(n(`2>gMXKAe#@nkbZH8oPuXwjsGFR_=CC{>aWIe|s z&p6^(Jz3vv;5ZL{R@t{_wDDSMg=TA3epxK}-qGrPui>@lMXywh>Uuk`&}_|`JzFNx zdkxq#p`%(Kn$XT`%&Y#sTJ46qs|>U!Mz7@Ws;k?1o+lNSC98~2Ghm;ik1D(I3>#O7 zQnQXEM8^9WuuoY>m0opyJFgJ=v$$sMimLhF-|d;uE0vygeLJrZZO#0iEsyA42HG}!rM5p-h*D#TvdV~ocN(y#&`0@BJi*3!9u+wQ z*0+yxtS#FUY@Fx$iupgmJ&_m#83Xo2>nKCnb9Hrpo@Y{#G0?Xzs~M8yN^Ltz1|tTZ zYoI;XvQpWT)b_^;k(!KwzIR#8kR(@X+fgzYG4Nyq?aAI&YCGz)WVObmDr!JHkH;uU z=k;kigE02 z4A{5umv;W?y;}1+`gCX99|ON-fZrUSc;5P(cg#Q03HQVR1~Bk`1IKr7o4x=3+z|s9 zz`$n>@a6lgf9{R}3}65Q7{CAqFn|FJU;qOcz`!~K{5!`wmyj@k0c&9O_hlXLn*YwV zu0QLM2m=^+l7ZFVmz6xrl4#DZQ1)?-CDnXQ#RMY;o@2m%3nZ%dF9bVQd6uH1wZtlI ztJ7Wa6`V0iEe76ez`naWs`a4>cK%De+Fq%`00uG!=6`M2Z{VdKP69X8?8nEwMwp#Kn#Yd7AA3edwagJo#dLKzveDnkx$2pQ| z>vbdF}JG-S1l3ad*o-)|p}8=>{ra6}xBqcIe3Fy^eHkfBu@GGx`j@sb>g#QsrTPC&5KF6k z`8~nLpY6}~(u-F1muFFdfx8&!y&aYB!Rjwezb{$M+mp)v_-(xYE@d^p+q0)zp;3c@ zI~b5Rq8;}gXo~50=zZ*TzJ0q^8Rr+g<>6Ltcy1Jd`c~oHF4hGt@EGyaW zq;5Q}BW6i^{<_ZY&l~S`yOQ^}nx{Lc^rP#U|Ehu6le6|zucfX2eYKu9B34i1*k6VC zF4}gVN`G68?XgOs?)MC=IXRZ%qpVe5Z{sX&%eMrHDx|$e2zIRVw#w^RLay~E)$N$) zwG6+qikat7@oNVB+1ulag+57EvqY)uk6D`JTbiua$JM;OllpHUzms+SS&~Hlez91p z`(uouv-+HERcA5BfH$!2-mCtz*H`stpG3BcBj0j- zbmT>6o^ZrbLO*Ynw?3WK&l@;Sil60s^8_2OIAmXGV8xTMj9!%8L65PoWl7}!f&qJ8synt6ALXdN+QvB+ z+1D7b&qSiiy`;83t|L}R)clHp+28j4(<`evi&E7eb1bs2FwlQ8+iL!9V^)o~cVMac zuBgC3#(+I59ttSl2VhK-7RfwRRpQ?0L4f z7v*?luQt$oCdXR-Zbz-3GI~bV#cW3)9=;#i+`}H35^JwRDc4kk`+})|hz#R;*qUPb6X8(F>Tu5dB%TL$Ef+Q;A8`@MH-pNx*~-8bD~;AsZ<7oqz-G5y7} z^ZWHmM;O2W2G$y|UpUf!{T-@;kGJwfuiw^To<#))Fi(Q z+1s3dqish%%5~_gZTvabnXz0~WMcpWH3Rl*AW?-Swf!+i>@#E))++=2f_!C&>lj%3 z3q-z7{V`{|&ydv&OKQgBI$DNw=6>rn$2zj@K96-~82Cm5^*3d+Vc!-VWnJmo{ya|X z*Xz|d>uir3aUE$bEz_6eYD33dt}6iE!Wo{ zahCK8tiw)kmNnL69BWyc&d>hYrE8gEpl4v_OdY4XH|yPPwAQOX)<#w1^`q9$#x$YA;n=M~z+c8UP34Ub_F-svI1BU^BCinQb9-H+k zvKnLTSyIy$o-4~`^0pV>ql3%^BjwRwafbY zwfwnXSMB-KD;bx%uAOH{Ud3q5*V=eg z#rjEqYZnB!|N2=N}#~~X7*BfY0>9&%+Z=WKoSsMB8H6YJv zJm&i8b8-x_G4L(}_Pkc^Y6(875UF{m0sq8oTp04YY5Kth~1m?p84%&#FIGh}2-c*8R)F9ZC0gPj?u=z^4qX`!7#- zB<)t$eUm@+l(;VjFyIZ$zSs7PC6T{NB$iq`Mk;-dB}nA6qOsKPW3-Aq3}E2S23EY+ zmZ78U&ULMwe~!fe4}+|G@-grZ19$n=X1`QA%39F1cK!{d>>kL+00w#n=DrO((K2+@ zyW@CGSGMzUU3;ai)+=rPctzK>^Ko78N?R{0y=>MnfPpIw$lGVf{2gt*j&dwr+0HYp zwpy<|on-r;{q1gPL>TxD1Nw_#=ehmcS|4RtzShQ&yn(tOPa?*^^9;=W$#35t9py~u z%61-SwY7dF%JOw>{C?8tzkNt=(T*ff>g*_288Pt92Ifz_CHN@g&ez%)(r&<7=|yWy zWhmrg;CTjm?@s=gV4otZaVt_W9#^sAoRz<|#{1f?e5R+f{bf2L7Xwc-Fn%9%I~j96 z_g>Re>=~}}6uTc;7`Tgp@!8LGIHt&zI87@0W1O>P_8Hr1j&n!ScyHPLts`lxdB?h% zIR>Hz>L+d8$-g7%j$f@i_O$NV?I+gqqNkWV3}g+=o-|wUqf9rx*2Wpq$}hx-mFs8E zwmXvOlO*Y(9W!0E)qZ83mLZUffrtTrO7}QYdF1pNvU=pbo=HuAd|j_I`iyN=XT|51 z7q4qG??$ryNYwUyz9Mz~@x0bv?Gv_Dn@5C!IRh(B+L$3%GCfL7J7y?*Zp+wK@*G_; zTYs?bMY67rF5@#x@KKyI`nA0gGZdRUQ)@{?-($d^(j(67@u=#}GNkoq-*%Mi<@d5@ zz7p27kK!(UrH$hp$(*eh4&%2OuqVSu@!k4L8^<|&rmdDJOR@Pg@3kcnjSa}NYR7R+ zd!?<`D{cPSimp87XBAxg9RtUS=*{9Ddez5ToO5Iz)!Xb?XLe@;_NnP8)3vT^=W9qA z3c2?-Q2#!(r%SJ7cCG8|JVWuh^__VV@p}x|XD?Ca-cmCj<uk|phTwY*Fxka59+0Lu?pE+sOds2si z>kPE-hOAt7PsrCBkf+!mufIQcz`!#M@P<6&IpwuRVjsVIufMW8 z`%HE8D+bsn1~LZt*PV>5yXWdJo}J&lbNa&o1~4#dz<%{4nx#^Y0SsW^83yF5w~x>0 znJzJa0Sv4&z`ty)a|sCp7{CAqFn|FJU;qQ&z^dQdt2n+tVhmsa1M3Z}`uEmVoNb4E zMb|%Ph#2@j19M*r`^}OlZ%HccnCJNaD|rylZ&7S+y z!Lo=jfPqy8=H5*Em60gho7A;qj?~u6N`@mf?Uu2G$xl-bOvk-b2^gd4^;weH178N*hNxw#r9Q%9iJA zk2piIxpTkPEsF>PPc~407mhd4&oVxIt&QU(Tj!&9bNu~n9PeiJ_pjbZre|_7aDM}{ z?}Dw@QD*PDuAN6oZKYp{k$hDfui(TOTUL92y%Oh)SvdyT82APQ`Yk%p zt3KAA$vOHT&7%7n)$i+3%9b}KWhmrg;Q0ntybEK-b|vcQoU)D-?dR+}@!W2<-H%Pj z-Pp|dQ?_+P82GG#74O2Bk@-%Xy_Zkstg&-W$@n=ZWly7}Wvj`@zzPG^KY#7%^--)d zU)9Dj&bsg487bP!p4aY0R^l9~=#S6kj4a1@L?#Ai4cNCTOS9EJiqd?YjiZ#&`y{kD z=Zuu0tm4QH+A-5jTdh~(Bwf|cS8(DCGBHp$(4O9HCBy3zWHm~X{QelFY*dL>HIHSIh~v3a$pY#BtqVxT>BvJy8S75#AqC(hV2XFF-{x9Y8$ z<=CCIeZF3?1RcGuJu-A%J3sQyXKkfkIj?Q6>e^Y)EuEG78mK;Pdt!VP-LJ2+ag;Lp zTjEBGGmgw^7YE_Kvzigvk2NEaT#b!V)gNPwvnRfBqm(Ugu8+)}$i0&R``ojg*=ip} zX}-?JQOcHQD<#Shd~WV7^3~rPi`#vy?9&o-6s75!{(PPkC6M<#19N9)+i#yBt8pVz z(T;J3WLk!-GG1X|=1&LvoOBfLR9Ch06`VMO%x4*B&x@=?_aXV?ah?<<_`I1n)3b=L zG0;CZnS1pKvKl2xet(Qo$a|K7{?p!8uzRt!` z3VBa5V4ty%qC4?*HjYvxZ?0M>jL$QW`7_t<&Yl?^*}StzSGDuAO0GTCK>M8aN_1Dc zrkzJAHn09JSPs!I7-&zOtVDM&`QtH4BJVi{#!q{@65q#|6K9Z#fx3bI3C!^N1X<0H z$bE(ZdA8#*x{rB6^iJeqU}V6a!co>}-B$Z(RK@y}4fv;QG5xfobsAoKSY zeLp#~{ZVH!{}u!NXDzGQT}s_PW~<4^z-JBE^Q)uJ_Ql<=GN7MvJFnff`sZ)$Zd74l zm4WsRkd;+;fO9tk@`T&*Zk^H(2Cg>Hz6IOL)pv#eIRpJC&Qt%~6XDLEHDKR}&-TLI zuQy5nEhVZdL6~x=qfvpGqy}yD^ZSdWY7GWa8Q_G00Y+oH3$g2;^b_ z1J@d`@3%y`drDP1#yM@RUWsvZMLUl%M&B*r%~PTTpO^pj=?TOb_)Y`WcSGLlc8q&! zYxPQ$qifoEjMCQWl^ElW-#@*J>XVM{o;(aZ$-vyZVXJi%cc81| z8CynMr&r#`*zct8eavHBkcEM7HgLQTdKTZYu4?B|PFty0-bvB-ZRfF0+B&}y;rJRG zM+ke4t^!<#Tjiq|#aFfQ3eNi&_xkzRsQsC2&$EvrUHY0fj!@S8 zUfS-|-d|Q?eO%Aj`ib=-o%hEWu6|3m zo}!(c^%7^SpjeYWiuU5`Y&=hi63D~AY6JExOBBD0RP@Ifr(Y|pF~*i973~;jj5&8< zY`cs0(wfjKF^aC}&u2NYda^LE+Ccw2Z>zC8jqBw~gd@5AF+$jL`t{ptoFSQ(C9An{ zo?!_RWx9}>c8pWnTD=nE=!$k8W7w?9Q?&%5UpLU6L|KWNk^KHRPl*!tytyvgd#!lK zW*K%*ZI9w7) z`TcrXjd3KqKgJkjeT#wqDcDw{I~)@-l+o|VXcy!?#eh98xvp)kk76ueW#brwtnV;j zpS_P_d-PQ{jxi)_rdAk?PcRVw^VROgo)8_`ym6#!+WEMSE8l0Jeg1kSwqIS*&SMOl zRed)sh3Fd%v?okfV!M^>@fhQf^$iBbr(nAh-Qk!LC6I@KsDb`zi&l^Rokgf|4w+9k zAkTa}#&$ScAG-@#7!U*Y#K~%Gw6#8pG06H31ODmTIKD?)CsDkfObp}=#Q%n(*>XDj zqt0ah?FRa1Kvr|Rm#Tft)sl^Y_ZhG!RY&jZfjiy7fPVJvywYLq&*aLTsKG$ZKzq_< zrN*P`xd!CPx8rm7MCTYdH_)E;ZRNa%tKVp#e-bzi-}tQAlQv*azP`fzmkii*Aki-w zV6VSxfM0#T`|OXM%U5q7k9B4kzyJn*!vKF4{>F1=ml(hR1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? zfB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n z00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO z0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD z3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAq zFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOc zzyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6( z00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC z0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n z1~7mD3}65Q7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q z7{CAqFn|FJU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJ zU;qOczyJm?fB_6(00S7n00uCC0SsUO0~o*n1~7mD3}65Q7{CAqFn|FJU;qOczyJm? 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 zcmWN?NfN>!5CFhCuiyiQWd;WG8-_)wQb`HP!PjeF`YNB<$6L0wj=3xK=9@ADBUD2QWBMt3T;Y@O55E+w~8bcMbwR? zP+3AMV+oTrgE0%Uo;l}#bl>;q^ZovRzu#P!>!RkGIZfyNem`H&=i_O$9mUvS`M#*l z`=hoQY&m4$yKnpExOF?D4pNpZUTlDUQI^=*QHD-v3ZsoAQM%J9ihWOkhof@?tv`T)BrgKl;umW(eMjJy2bB^>O4bd8q&Dkzw@_CdA zj${#a@9Q)w?5()EdzBSK5f1_oflO6YQlydz5CGy88CI)yU#;jBqSIgRMOlz=CQ;8P zk*>2gkVbR^(syJIA*2zlb_Z58$nWXUuF0!RRqvwL$sWURd)*BJicrU6|~&mYD~%|#CSSm-MRrNqs0_(eaA30i3D?l0Kq7huNJF-gs|R$wWXZ&aMnVR6?lzfuh}>+G zSX)yZZi)EWBWuj0duW3NK>nD%m+TC9SGZiGdC*d~Wza_C32F$ib&P~PkX1oiRaAg^9_ zFn9zmRB|0{(X||G)>tmQ3!agA>5t_D1%tFbQnNLPy&qy3E*5UGM7&(l6^_UT1O7R} zhZcoB$U1ua7mXD&kBA$k`D8y%^Sn7PWhpq%F8&_8-8k`9L_wH`stUu0UPA5X()`W{8(Ny3_;pvy|>t>3ZAo%G{%gcfU6YHz%1vN}lIF36bHK zM|}-QfP$V&*pXW$A3BF71tN)5CY6W;lS7>!N^TtqbJ0@(NCv(~Uk)R}Pv<<9AhV=q zc=Db*UULr$}C*b;(g(@>kwCox|NIGj#!|6<#!sjuh>*Q~=eSW3t|lNKhH4x8cRz zi>Z5eY~C2Qar2J7sTc3QXz1n0P|3*qtQcoCP{DF%(Fi(T6s`p>NM)kkhjnn?ujE}( zWZmoH?)9K&oA@}%u624lutQX-Y1#AL%%G#&z-F=pe*jsh-uDXpgEMy{;^dBcJEN|C za!H6iy3`wWaYt9r7ak+Gz9$}r{k6KAR0AY;anW!#Ay`nWr(YvZ0R35$+ab3`ILCk- z`$KZi0&p%6a&wmA=;7r8S!iTK@21;`>(eG2#5xsJjd5y%e<%i84o4c%UxJ(Un%RcvaRj z;1>638eZH^*|=1n0uc3lQp$$p zqLz&1%Fu$uMwpZ9iPwnhKSZh6rp3MkmJDdGmM7z$aGi9%bH zWZ9^dN#lS>-d7jG5#hI$!0HZZwjnX%EArYOubuD>?Mbr$)z%e^qU;xYY^Z>$?Y8SR z140z$|7fYmzji7qE_&;ht^B!wEW=1sBoa>`ZiltfNHbn1sfeLSf>VBr1hZR z614IDuaU12M8j-p$7*2ZH{lV2%U50`5GO5Dvtf0b&g=fB2mEX}_)b?83C9ZCS+;%1EoeK# zX=A`LgN1&-=t3qe-d{0+@*1v1SgC?Ix|vVZ$v6ys_&hwpJg$N-Z$D-Z&fW7F?cA$I zj%Y{99RdF~*-cH&JID)tjY1@Y;LH9zPrqfvmhrjQ&~EB6bVBB3IPoV?&Lp4aYQ+oZ z?&Z@mo)QMnsc?>|vM&+8l>@my8EcU^^1FUL7hW+jTy~ETGW}Y6!6#k>5XJ5C0=JEg z#}Q5QU}hv~&hZgh*(#z+ScOPfwbKw(^*Y})jUCe$f48DRprV0e(C$;WpH$R;YVTln zw129vcyjyH?jT14MG&G{{eBTU_@|rBd#QqkJFA4EiZB)8sxsNg@j0YOCY)zVj2j=@ z26}aIqkstBrv=)tr$Y$$WZ}eOo{L`pJ8~Sdmhm$eDE~&h#8y4VQ8_nA02&Jj{eO^I zcW|whK<{ABK8SQO8l@OO@)zzR4t@tBG0sp>ZUhB*mVH= z(KO&99IV>ft`dpCb;jy~R^(F+oX;d*AW!-Qm zQ3IcgcsaI!)m_oZ0VT}~!td(Fsy*P;{f# z&p)C1TJTvb`1}LVFiOmC0bjibO4{*PCm6q@>$C@6;*KEU)Hgq^^}qI8GU|D2ac7VM zHIvuy1x1r$^yOk)SSS2w3E=!#JaKI?sJE{P=9Wb20;hhflkZ()!RFD?AW#Hjw4^@Gpa=~K*Ut{a%dN4C<3yt zMBjJ-wvVKvrwl>+n{4@~L{pF+aJvTSnf_ToycmyrS{?;~!15yL&V1L&Y>;wL%#rafqQYE=5j7Vvn3R@y@mL!4dE^6se! zAVyTpnEz$OQN5OJr_UxE#4Vhn6Ag-Gn!}EgU3B#oFxjCViFjE`cWLtutMm8h%j8~|dS3@yV2aCD7N_$4C6pm{H5z`c5Fx(^ zB%$ZQD9H;o^H%;AAe=oI0t9r5FKVcyOz<-ealWtM(TkRV&hC1NuqI& zwu0nf8wLde@KiM&lSS5c_IB2bOmx(!cmSd>Y=V;u+AuZQnHOb7AS_C#5lZTJ>j0J) zN8wk#IKxc-M2d>WMR8{U5HiRP2e$BA&DCB=qQLWL5^zx-q40Xx$W@#Hz5K2cC$q)W ztp&Ueo1nZ7>B`nwBmOq>-wmfvklWwlPD@-(ewGnp(Ienxc`SGqJ&y~SYO_+pG@Fjv zE&2PU;My@Es^*|m?=yd2qzvNKE&O(--+$HEImn(8 zOwUXUrr3j>jj!&f@AR`!$Aij?e$RYAg>tG7*$|12=ep3r%q5`V@j>{7JFvJ={$(w= z^u7F+nhvH-m8{yPLU8BNvtX?JGI#`yC6xU%ah06Ly=^C-m^S=fZsU(wuaoKG2hPxW z2ekOfDkE9YyH?T>nTOF&Oyi@waSz174|y?XQYl6cYG%iP$+l`v7r7MGYgHRnh5%O+RG_xAo38Z0OPry+45d z%==A|xIB+1dg2b6T;WfiR98958{P)FG)s;XojW+8V6r?{!I%4n=E*A|Z9@;@%Z7FQ zkr>tf$G~4LxZG}hUOQMlPQLpEENKFtFo_qY^n%fF^}g4H1L$UQ#f1Ib); zguF=8;HCVkivA>dcGZrg#3*!MEQ%k74slH0MGNmSTTc*uW&3Ivih zOdVH+?@Bsx_QItLXHO*U3SZ@DszC-Jl?DEX3Oj`8WX&-*CD3sDOM&ctECaN=Jtcf# z2RIbVzl1<`Rp=vqm8s-aH+6x?d;&I= zfM4#CEw&0ZQYTf9a_K9Yv_X0gM7wf+1g z1>;=!>ju1GCU^QzTfiu_3uR@xkVp>4K1?ISrAhAEI7rPZC@HI=eV8vnP!xf&ISQWr zM#3L%ugFRYvQmR^+U`lE!^rf9V-6&eOC}5LN;Lv(|D5J#8say6g>#J+lKA~=fiO19 z8=r>cD=r=WYGTsaZDci8Mc#(SDm1iH_rWf9@D6S6QPs&*4dK@L$nq74O~CA+8)C@2 zhG`C%One;Mp6on$(v~y`eh}F)h`e!>Ks$ zzR9hi*B8k}b^0mZ#4!zp&24B~j5^*edkpC~>qN$z?7NrqCR{}-f@OQiMkD39E zGlRtY9k{%1(ghA991YNCKLjp|J?D0}YA)keD0@yc8W?^MYyx&pe|E+v3cd#e?)7My zBjEM|W*uhWmgP>#YIo@a+My2ykm<%tLB=FN*(~tjo$AhU(X12^3Amg|4p$(W6)B?e z&gwe{1I?5Hl5x^$ zW{IRF!&eJbSQwg7$r57rRd;RS)zsMV)jl4sOIvGyxr0G->%mHu$S2dtDevo1M2weTU3c>Y{ALz(~4LYEr{obd&5`_D; z=yasJsTz59Nhm0(($(myN(!@!g{)@k9(lUxD;JUUKHY1t2nR5k!6Lq71QB-?9`rGy05}yRr!_lLF6BIaQCa<_ zy7I;2oJ%P?);Jld-~ft|&%wejF)~sTZ$ThAocfB4=Xen4(Jg5EHmX`On}0@~eqP+M z5{MjU`9eozFEq@*jkwUNxSsTn112`y0`R=p(NdUBXsHGtcM`I{@Ckj; zF>4%dOfUh}*4*ipi`s64Eu;Vxla=w;U$&1-!zc<%gaRIqClE?zf0!O=e|bHAr3v<& zg<&_^MDpG{Yt%rKghqHg%MsK+F)V%JFuNII|K(u=+&G*&le-y+7S`y@`#kDGe>>sG z==iGXD?JMqk1()n)#sn0u(Am~S>(~9i!gH(u?&{mc%y;yrql8DUzDQI?Fw&(7nlz< zYT1u`HrD^h-vAzv)S4+~!gY4Is3E~=b)|$sX`vHfdUgnDiq``SVk-Ia-kdOF%mwi| zRM^ge5#dOCRsQ9p@mn{n4g4)|?S`%KM=$4BwT}o9nA2W($Om&)jKgwz<^0MR13)jn z2^l(T3OE%?2NRUkPYZ^kaCUWYrlMJ`M7J#8 z3UDowurFHz28k_lPEq(=fbMhdBM;@Z-i=>wNChD(!@?tY`+*Y~S8v?BaW&(_f$hN_ z77Qu`QVo~K-)+U7`SF|&9he(l#F4io8UU7;*^(kxz$#zX9f7mJ-p7=BPF6FYmWQ0j z2Tpy|G4Ev>T8_L^43($iUJNnTq8qi@f8blofZ9r+xsc%D56{~!(u(+H2JC*WfQ=L)ns+nFQR@Ptr^^_46~sdll7^24rQv1hwt&VNStn zRnTNd$ryHNnd=r%0J5HIA z1XNvJz=7XyS0nhA$p(_t6!#9#oL5)Nlzv)@OPFa{f{mgV@oV@s8umXws4bt)hmw&H zwVrC+x#)EGE4TP{^Y?KPg3Peq-i%qQ2@y;-WHDu;hLpuP<+Viu(Q`k7 zW@0~#3F+mvXvreNUM_15uw{zrsBjA#_BqD)G zQczOYUFaORGyUO*LG17QN>jbHL5f{cjR5|VW(}!DzJ-5gn1XK4;qI+Oqa3)^3kV&Y z*bJ;6?{QJcMoxm!m?yXiz6zwtLsi;Z6=RT9M!XE-m(tlwfAxm3-jo#EHT8>Oub@n){esP zVCew|DrmU1WJ=ziG9McNdXe{`B&}m?!3Axl6yD%Q(7Tf#3$7jOaa7Ec9{`SEacwFZ zhcDt-TJf`if>Esq#B&K8X(--qBnx)9tH4gtoKDc3K##uc_J_X?nFHVXcx#KiS@}eOw=o9kX3b%P^Y7gGIRk#;iJI-7}E0Ju5uE}@e?u)!NJ3c9GK_k?# z$f>Z8dV;5wFs+%wR#46C#TB-KPreeak7-09-X>Ey!0j1l16Bjgd%A?`%dKV1Z<$&T z?iDrJkdBHv`~jb4K`0kZ%qgBdj9FPTy*p)X5cZlR*40VeFz$RyOByS(E2IT z>;w3Bh>ntMnnZiS^;0eL8MRzrYRR-Wt$}6WJaZe!nKJQ4qBO>?6TUtHTMKc$nWl)B zCu--9x?q}1pPLE2?xuyK@x+&GE0ey@I#w*E8KY)q3lJ-8TC9*RZE^*}`-PY0P-1%F z5)a7WSRdM+WCAd@U}MB!!M24YP;1$qdrf0fR5IRGS9JSKQtXzn_3PG$ZHY}fbGxXn zYg~d#$C~c#S*8V&7H%sT#0Hlw48SC*9ql`20C|+ay)l$ImxTS{plg#fU14#PFc#P{ zQ^PQ0vaEDI_nBC+UlQZda9a0kD3hy?XYNNd=d2!X7C{9Gv-heC&qWV zyc|xTcD(?eeIwjvk+Qm=GPzM4pMHFjc4S&V4DnhxeE}*T)bWyB1jM zZp}oeGbtT@XGKcl4{pJm6~J9l6t#pNb&p!weHM~82^;^|vIB0ULe zl&CJazzn}-ke@`ddIzVIoI5Ah;Lf3$xJYR(eyjW(a8=@^GguB?m|p0Ggy~Nop-mpA z^W%)JDf9N|iMQ(u570*MfK^PzUDz?hY*tw-`vXkG%=$R^0d*;B&>MeLT4zO0o#_by zonNE1KA?G)fR}9t>S3cbJ1>w1>PI~2howD*`#p7t0GVO4IyOD0w6?8_H8lEjbcof} zR$H2r9=qCxK?aCAp8E@Xq;UU(2z`(in8(J3d_B-En)#`N__ywVG%|m_=jw*N;4BXsCOwctH3*>v;#u@?P9$>JZ0LYK73&t^!OX$8e4N4 z;$y3l4u7%=0u#qnse2|GKAk#m33s}ip7Q+%47l#2jOr8 zva*KZ;uRZ@WL5R>5!rC{xitnjqIql?2l<}qLQo2MDdF72lA(ObKs<$URnoZz_^pS( z8}uI<^de-UmvCX?rwYN694HChMtR>!i$K>g#&6;ptAVn{SwYhB%Wq%*zy0>5M#EJK zoBrdsm#QslWI zZ1>Cev0p4qBeqomPdo5AzkHt+zVi87#RgGQvjEiJRB!=T*{iWueiX``@uJs{IjL6* zf-%9Hqh!@A#C(`0*;Nhtndy=J%CrM4_?17AzO_y^RTxAEjiQQ2Q9R=1T-^nV4j4$zeM zk4>Yh0BzVLp?v*-9w~le@u!YIUcvdb-z4E_77BR-?BPcR^t4j|P}YqYT7b=gWP8 zriX<7>*OEV__hilZ&))7-K6rRMP)W`KZPcMXVBAN#7wP`_H*PC)$BF$Cqw1*^l%(S z>m*y4YX{6b#X`TOnt}8+cYh%K`qL|C^V(&*ZoK}^g|WPLob|1#_Vr({{(4>8^o=!+ z<+U*P`|BIKmd)n1D^J(+kso*0&_T1*Zz${3Jisnj$Udn>i5s3it-|;dd2L3$ggnD6CO6XBW9b$y0Id|jdCYPI9{pq z4L(I4sQ%@vwi~aEx{6<=Zrwe+oOl6wI%iG+T4YQ~;f=&8FfzEjE2f~+J)XFHy((_W z$u^m&F@1$PK(*L-xn@v|O2<2@^DiEXUhlu$)ydJxb-Dlg=tCFtt2@S}sCcmE@XS}7_pgaZtrc)^%c65?&G!(_^iZ`1f)up)ZTjU=%h|D4k5TG1@JxnGF!ISb0)T#+b ztp>K6Qju&guvPHMnvwz6E+NEEjPIu~E(s>mb@6`X((zkPI9h1&6w-6!H%&mCO1bjS$)<7(iHl?CZhY~i#bW+#;L(U?B;tWlN*J?i3U)X744Et4cbZW#Boi9O)co>SPh(O z)=z|a&V`IOizynEOTHH;`_IFCY9)O;|E)>qZ|6TNuc)jjf0lpye9~5bJAEb0r=I7Z zT>M=kA1sP7!;@XkF_E8Ht~ia@&uGgg1mRqbvoLrHJIqbW@qcKBw@f9Fb zwqD_dz>oSw8Vw}D&$SFI;jQdmjLw(!F4RpvAZQg@2Y?~=kj*+H6s?)ECt zOz9d0rHBUf%N7OgbRPQv#V{YKG^d<}D-E@t!%0B2^a<5Z@Iqm|EEBjQb~hY*1l(oM zi9lSPkhK=_)%K_(;qgbH?iEn~8hFMe9~W6UOHblnjGEYSpAk31H=u3OLPghsemBxZ zq(o0G3#qZiZD9$o=#n;lLF?9$RW{eb6ZcnA@z%-jXXLEHT{g;qg0XKx)|(!V6q}o- zCx-jGnH|jT{^5yfY_5`WdfsFu_!=t!$~L4wecBzfz0gp#@eBqPegXv{jZ7egx_F*TLB3U9BI@#@kOAvGi0)B09t|@+f z3tAUMRF7@L5HM`N3A{Imaw-lwVXDb8{6aXl=3T#GpE~Wj)?B7jE+d0=oqXIFIhhP%u$e zq~bK(ZfM48(^@wD06Yo%>Wn@H@+0k|Sf}Kbg3K4*cz5)x9!%Tk$E} zt3f??;h*3k^f0#2PXIe5Wtyh%MSG!@J-mGct5@hNJJ7ElZCFRt*w=w}95Dg(VqQ&#;0aUB4w;RXh2(RbOy|kTcmn}BY(WI-YsM0lcOcpO&yEEblDn~v?kk$8fqHXaEa1q9l@&1a#=mgX7TeBS5z}xJ^AbW%V z3rA*PAC#E=c3_qxyA^JV5fE{I@i2~T5y*&dLp%4WlOsMO2}0vwbd?G38dyAP z=q9=ioIql57&D6j4VMXiKYQH?$D|+a2zxoBJBjn|N&8}zRAM{OmKY73!(|pal(SkL z(C|H5&q0OV&(lI}=a4~=MAI{K@bFs`vMywepNE5)9*qQoFy+8d3moc0HV~KGw$O+)K$jxxH z7`(rue7nAH zYWXbRejel77anYaC+~aUHR3zax;3+WJL|uE`(sU`w}12Pgx@8(fAj79C6@e~vwZs= zsiiOK`8VILae9z=kBQ6eA!TsphM|Fa>;fQDyqwY1rs+0Ssp2u-WMJ@K5DDxMv^(Pu z2zo<+WpyZa3%i!WtRoDZ+kH%{#u@-x>+^nKz+q;v2?gVd9YW8NbUo`!lel_pc^oHQyc53J~*?vwlK3|0_h z(F0JnES!)7#{-FQk=p$C;v}4Fn>bxb=Z@^#dYoz2%tVF>=#V`rdl$_B45G_9=*Y7; zTO~lzEac?V&uj@QpJDg5zI$2pF!z4$!=jh(T6@_ua#X_p{PbjqnIfQM8~1Dk<&;Mo z0F3B&vdL@*&?IAGCd(AJ{+sNMj#9d~%@vmSK;scuk)aFkV3MJCYZFXC?dUpq?D8UD$$in! zWSVN~)Yxu<)obLYs!5eB1zO8#nWxt{?nPASHyS=g-=bGxoPf$23Wx13*fc( z=vfy2Ni$g92o`*&921(m!-=H&E{)%?^FJNg1^s~}V2pf$vUNLeD-bfw#weV6q|gi( z`WdOhT(e^=qzOY@79=%F#f@ppQ~;XGzJhOjRK{*Axq2XMh0Q#MnlhE5K%pwDG3ME< z2s?1Kq>U{@`QHlmxzGTWWoeCKX=Bm?47O=PSjRADuL}7biZd%j>M^p2JsbjT<+WQY z7hqk11mq8B7m|+YrQbBV>Pd;PySeZf<$D3xSPnKn!Lz!qDorJ62@=&NE)v_Tz#Cjs zH_16LPvD}%?9jLT@zunjiL)A)CV$RgJVo;u^h{|-5V!=R(6>`nW7^S5`pGHoIbD3m2;f5n@1efDrdq$Y}TKnTZ-JI={!JcoWM10NT~~8 zeQ%nV;{<6%*Ggw9V@x1L=j19jBI+(mU2RUo0Ytj7olj`={*<({=gy|3?2iuhu`{L< z0UXVIb!t(!2w_(xJ1asaF_kmY+Gs7vDTgG@hnBdX>;#0p_rE1CRt_ z^?;$nR4F-B@_@94|8}lP12&XJa=!b5n?^VZWTU&Xo^52!A6`GUBhXG4^HS7oHeG!`iSj?D zdnkh@yKBV4^7TsO)qlZ*$(rPCJ+iw-(E;7`Sds$-??LBb;gR zuRe}=nlpm+ak5f2x*GiI<0Q}aaau2|_@|GP=ZRO3`-Z*``lpYxs0@9gPrHRYo$cf7 z!1kIuSRdyB*2g&p-sjsa;@t*{S*mfe#XjhAXVh~!Vr~-8RXI08$zap=i_N`|DAk^7 z!XYG7>BA3G^MMu}i{9QP^b%PTxMp-P5QrEZk0mTBMjC&E%(7=VSIhwO^FL5#nl(<* zBPCI909 zR`q~|QzL!M_STlxc4ptmlo0z9!NAA-lpu2@fMOn*(9e8UXDJPqg{aBq}nmZe8@TScrXLmh0C9I(`8|wG`!qYaGuaw0la8|iar5v z%1C3$+QOZBqCFZDnfRvn_|sxXGfn|^TWuy4Px~sCh{B+AXcooc8+RMFfp8OuX3x<2 z6@Yya%sQw=@O>tneio#Hvr7Ibg1fTA<`E$cmmN21`msSuG|}5q{VXr*+NFz^u4Uyt zt8VF?z;fs5{+b&*Tr?o!ys+#pSoAU33a1kEY+Crtm!Nf!1sB-^E9%i_X2b+8wmoj+ zesiE?q3575QHi4ObTMf&c4HNA%pAq1@$Z}PnG+g&M0z`g+R0-UsOzf#!Iu#J;Y(`% zzsr@Bc4f5~m9o?5g^I@g-X}tPS`rVC{x2|1Z9zYL@|^8~0zn zWWVx%`4XjtzxWc~M+4)wfBBM{fA|u@|KLkjxuQ`5+Sy@+t3%W@o=!O8Yr?$^k4$}fR?ksJ76-bWMdQh7Sbu1R`E$skYOxDs$Ek$3GopT{IveEu>m&;XC07x{1q zWj_j`LAII0^7?S9%C0VXi36~*QJ!a@bVk}_M@pBMYMXrI1>)XJJ?G zLgSuxZFk`vAXDUIF35!5eZpTGQ%&Kj9UZ6Mp5AJX_&TFLzxsn0@pbLgSgb$T+M(?s zc#8E03n@Mmzxsp6@yj{{`~U3^cEbPk2cP^$UqKa zRpYyqrG29T;GXGkp5W$T?jb7U^32avIyOcrg@1C?A3y&RFthTdG; z;c1NVdpL@^zM1V(Pj4S@&!x6z`sx^ug;O#1+;Q#AkW4=E@{o%XsJE$T68U!A4A93a z$Bp3%>d8N6E;FcyxWk)4&u`Ovz<|-drIfo^>G~j&ff*+IaD`m!MclhU-WXOM^0Ja` z(-XxQNJDLf^s_^xtX|^vQPl$yD>pbnp}9?YBXS7J75XVxaP3$O-Q>2MYrZbz7_HT ztYo(EWG6a$f3+IG2)fZOLdNT^ZE@5lVurM_gMVbgiA%Tc<~_)}d+XAPgh+n}W6ZuL zYCCSZRyU4_+HV9g0JYWkN72rc3$g0kfV{#Nns-YyxQk@+5UE~FOc6I(V+Zg}^75bU zCPrUbW+r`~)cj=_2RdQoEsvoM=7RM#09NjLTMkt~T{jg*E>Z^W;2PgSe@q#B!O8gY zp9`$Us}uu;MU<7C21C8KqF7-4*uYv~9gCk#)wv=55vNE?=tbU!QWXN8@?{?r&2bc` zlo}4oZ^_=_tW5w&S{B}$4rcuM^sl!cnwpzFy#4FxpBV=?d0S|a0D`vjj_ejb%Be|l zqTtLEKg#$|0~DyCZ;{>vnqu4!@eLhnGM}{$SUWJb9*7ma)iZ41tfUkP0~M>rtrq+& z$0x&H##4Vl?;3G;1}GOt6mE7wS5fj}Gn-U`WY@ui=xJ;) zen53)4SLOT0G6{HKrra?8hz)DaRAf2Y+F!2@iW5C#W(q`NTT7Y8uccJ=Nv zcVE=Cb^jQ~IDp|F-EDO*?w;Ab+IH?Q4j`ehTLSmzZ!*9*fLu26IZ+?9&E`#G9Dw&b z^tB5ZJj(%C&T;@=gDh8!1BeGsqX)t3vLKZvrg|_EtJqNmW{=YM3-zNUdhtA!-KKIs ztiin$4Kx!cD)y9vS7vN1rV6oqHy*kpi^N^W8sCvKA57IAp!YNwc}Ud~+{W+HocYkI zH%L$79LOb`EpJP;#8F+-K29TJRcQg32~C=7zwW@*r?p>yu(>lLi9{lr;j({xt$lj+ zz&iW6m@i};kXAK@On*#srQ$48+vMzQ7idmm5AtReG(SiBeIss36_Ur$xQ9MaGyO0V ziM8Rc<055upm;0~FPyTlnYjX%_b4Wc%>rkCC4-Bf+;o*EVi6{rw-Lr9aIgXQI{0FM z;RmOKS>oT6t2yo}FNNXIN#r4IVZ9`kXj3Q8VkjTujwjQUc4O}vL{<)aEfJK2VebFN zhXaW4bJpg?Dj-42c3sk~vgW>Vf#e@R5rHLw@xJD=TS@C|wFscf;>}r~g~-6ejacFk zRw9tR4pSxvVV}%yC0g=0_n0yxOI~L~OqJ9xq&-AVLgB)fD(;+W<=@0vU^?u_7g;2@gC>I1j_zwXZa zD~fZC<1@3JmB7-aV-Ezx2rCv45m1rP#1@J~!5+#*j6o4a5DS8eNC_$tf>==mL=cP= z4JCjGEM26tv@Nj9wwe1bn&h0E`w!gtanE7*%rGqP^FHtQ`Fvh9_H#fM0|RFtQg8+W zDtHm6zwt$Cn42jBA`g`=2s|nZM3VUNInfw@QCCQaH))c7uG! zdhCg@XeQlesA<8HjyA&;>|*Kx?2tljqrx$X)hcO#HZOuPc~Vp0ryVg$OYJ75eWIV^ z8*RgOY7IO?VkKMEn^?Ncu?p4ILrqJyUkgF%f2!RMIXczvgRBC1XIseffp{p|5MGta zmeqxUIY`%EI;FyRTnT$g=^ho!B-b{yF-mG*WjeZ8TzD3Jb}r24y_q_8{X&>3U;cGIW=_a>-!1qKohW z)qVPvx@q&6D-tfQ{6-;G)MyX6Hc7LM5$6i5`4EHR@?0=e;H3^PJeWU@jlYVt*Fp#y ztM*)YT-OgaAgFkTGd4WXKQKJTnUTli`2BT{FYH;RK|r+ZBVU1cAm%3TMPPTiWCb**A(}6~3tyha;|I&8F(FrroIzgf5EZgG*L&??TBud-#sXdfInpH+x7}1%QMwNX?OMk^LdcM66c>XDtf@cGnj~C_jR$F zKQyzt`q=VmDG8^iBI76LB?{0y^8?x4QepKOOOk^9ZMN)>00zb5ceHgMRXcvBe?QS2 z%vP)GK0&Y3)^rVSP{@`ZL=t5Iq(?lf1%pMzNQsTfObn$N?Eb3Z{C7}gE?l06v}KUm zvypcdNZPobJ9?O2)2hBzlmdr?PD=!Kjo`_t`&uO%OH*F(9J_BsKRGq_vl8RPfcWux zL_fR@>%O>>ptvciT`H)*<^kG-YD~Yl{ALi~Lq(k7?&i9;73CFg>zccVIp9V_W6k+- zeoQscc=WhdFOaq;ZBitxyx5Jkh3g~hABzW0FqD2Cn>tB1x{p0ypqz$ft9)I@@g!Ws zB0v`^98M8!Rd21KA4c8v#?z3BcSv?SDWeU^D~H}^PzK`ZgEz=6&!I=3RQ6)KRNMZT z=Z(gI>gp(*05A>vXG`^Ti=-E*<`uGJb=51v-UAff%ahaT=8*A~0kq-xVn}1xy&-I> zEYeOLCL1`0-mV1wPfW}k?rf+kEzEnCS6Eur&^gQ#W1wGJc{|k6fDEhKMV3urL-%%R zK#PwzpaWNoA@lU<$;-Nw1HHm4s_MzI3UlfOX_2m8>Fj<0W=MdKf(k1J^`7@fo9ZrX zuga%K(DN4ZjX`?+043u+(sgaFPaX56PaPt2((NxHAHk0Q-KP$d=Bh2NngwYn@HhLF zZu(rGI_-b;se?eDnr`eF^|6=qoYCA{na@e(noueqw|(cSdqo5|^S9~D(K_i@A^VAAn2!OwyL zQ%&|}^~k2*9Qztp^?D)X=WHP8Mh=Rzm&4Ar=>5gilhf^+;iLS96^fZ43%*bEkme@L zAs)g`5OUdzY{aL@byb9v5ypw>`5}DO=s_ww{NMTZZ|3std;ZS1$ANtN>VN0kpZ=3? zcc06*&&u=d3+M9fsc%00oo~Mj^6haT-+uS+eEX+2sdM@Eh4Os+>|DOx{hxgM(|_mN zSA%?e+~4{3J#+c?Z~mQc4`H7VC>#UhhY2f+uu^Ntx=_{;s6ufs5^?TrrIVP^4PsH#4G#kXLB}p6OCw(M%G3>phOB$ep4R z*dZ$fpIwR-tsop89{{A0v99%$>sTVmgI}V&fm5WgTY3??!(U-OlYo>pQDV8;2gHkh z5b1?Z&f^5l@k{pU@z2q~(MTfS)Kbnbk;wTa)+%LOS8}qPU-A)@p5AhP2^f8S3HT+& zDC@8?ZTl7a#7ANNpR6chTB+IW?tNZ_5g@D?wWiDbaS6N+#|)(kN>$eUJ$2dF0G}|9Pds@ zN_Wtd4z;2sc90c#;!MPUbi@bRG16;Kx`L&UzvI4ET{lrWZ<{0m#95i&@RHz{EZSMV z{$9y4FNskg7cb8PFW(Qm{J+A>`hl1A953}1Ug}-E>_70bAH>W4954M6y!5Z|(oe)o ze-|(PcD$T_;N?6DFXw}JIj_ab`8i(l40y?x;3e;am;4o8@^E;`C*mb9ikJK@Uh>p< z$+zQW-T*K2A9$I^!OMIUUgp*CGCzoyc}~2{*WzW~887qac;Nxyh0lN&UIt$HC3xY< z;Dzsl7v2(H_*Z!0k^NOC{=f6#@WSiE3qKJrJWIUrMe)LW#S4EIFFa(t@Tu{_3&#t; z9WOk6y!Z|9;+w#W{{t^R7QFaz@Zu}Ni$4l4J}9P77r!%JeA{^OpX0?x?;=Bb5qwT_6*98f>CzC1Wiv_ZG0TG?56kGy*} zSJ8T)lL@4ZcNhx`(Tw?wIAQl5;-WOc=s8`2L#~jQvxP>nx!#F#%YL>}gcz%jX4H*K z(V4#5XUUO=cRATG%}u{CHD}s5?K&Kp{H(Tb29=K2WgK12fE2BMF5{w|*KH`YEjc`4 zt^+~$+^8UJ5phqqFpe=l11((0xC2y_%+3L8#oXE52E7YupJ}lKP1Bf=?sg8}v zk;wsHgjs^U-T+zL7Z1l6!ORr4?9bzXQ{7C>fwx1e?*-u`>P>$=vcjPGqnUOU*Mo2Y%~4%cD!npqQ7-nvoZ!_az5gw==Hcx4 z4RcukKebX$9?w6b$+8V5dP(Hq_zLihs|${&puoOeS2Tp=0z}nf#oimcSTydNHqt&K=9yJ zX`6j)Hdrz8n(mFBB)sU;+5zB#$A!Cs-bt!;D)DgMHO;;MAHY`rPlR^ zau@*qeiORTp9{7Vldwy~t;4;JNa!?c6SSjCa79BgY)F^^tW|fo!e_2@ifwW)#?B2` zLL@EPd9`v>Dw}F8Oggr6y@{R*olHbv7)FR>x{993`klv;3R|aS($UJRI~S3NO9FD) zSZ}f|h3@)H$h`y1S{cHjFhz|kg03CVCf0N);@CU9m3RqDB5oHvr@IJ$131LPls8>! zyD%Tkk=saB1^l!De)bNkhH7$1A8%57ZxI?wpv)o7?=e56rUqqCX*?8-9RtFNSj1<# za)IVk^eX*pz(CSgislanDQHBog;`7h+53pKMw-AXZg+Z7{@d6sa$k|n;hV2I0d7e; zGybWyuClE7WpP<$UF)ZD;46}FI$zy9Y$Nv-ZHax$M|-Hr*cD+k{|ZH=1?Y7(m=S?OTGD#lvDGV z)PG57@{BS&f!tCEWsYcjpn=r#kNRu5xnytP_S`;Qs3(p7IFex`OKu!rG8RR9D^64BM8#uoTmZ- z&~4z&kVGL5r!0-S-)ma{Z^q)!-i!z82KneM4UH5D@Mc&QqD{V3)sQyqeXtH}0=yZ# z;y@D;aj|!b+?&x~o_^eOmC^t3W*DvVJf2?OFZX7ocrPZBOahB}z?)$L>jb~Y+Co&R zzD;PM74T+AQZzJfq4@^t4?cS{76WgFh4%YyBg?6WNR$M4Gj0RD67@VsGf1El!ehkE z;{?y~Wc>Ab=cp|uNFv|NLe7(s%6T$2DrG!Z@=ZBUre_pTYUDf_i&t1N;K{s1Sx1y< zo+aYREICi+f}AIF)fHHq^&G-7Ylde5du*_|;(1m|eC)Zi=VIeivYuBo4+2Kn>~KwH zn1dcLLAzd+^JFf_c`{j(;u24q@(~vL67Xb-u~!yyo(v%IjP}TRGB?StyfPIVIZp=o zxqg|pFykj8B`wr*JU#z8o=h+&LPxyY40T&B^VR2_CpT0Pqr|pGLodk(0r=_)8Vs=M z2}Jja58u+0(5D8B8`2tE$fZ&C!~(K@Dj7V#5HbTwn6CJ>RFe6z;&F^yeZzOTGE7i0 z-V|`|cK*AT-jN9|pU>w`jP$m=%fEdtV3V;50ajk_d!w3*jTav?CsEhNcL6z#8MN^H zpya7FWbs7S=mOc+NN+FPCu9p`wj!&SGAxteR!37>hd+JN_}{h9Ylplh4E9=laFJjZ)={D=2S zr)+uhh4(6B{l$9?n{KfoMa}VE#~c+N0^aKrzO5Fk9`Ig|(3>g<33I&H&Dd%u)YxB7 z;%EwE5(xyeou9&uLdwsb6B($>3DwLlyQ`Ei@ql1>U*HbZW$;&daj zZ!=ozOw~n?`qHEumUO=TfxUmu!?SZgqn;w+K#>);4qf zpg?hf>dM}>C5t;+bXRj85l+e6v_RQ$i>>J|vv81Kyp_8I%;VStBIHJF^Kwa$GLYM* za!ptAG66yI05J>j+tUHR{eYa`{s>EB7(B!ZOuz8kJ*u$QKy{*B<`;hZ#<+Spzdd4( z-)^wZCn62-+Xp8BaZnVd6TmB$x; zyJ-RT(14MKJ(Ba=4*-69I^efw0e*WVoXJ~h%1s5zVC5jm^3B*rK;yJWO+?$7*g8jS ztu5xZRB(pc@Rl4db=K^tRdx|&5P*}0OCU1h$(qNU50E;5*}LI-qY5@Yd( zfcT93+sN2!K)q%}6k{)t{^ICS=R1N{oV zCkeQE2pJ+5<=PHSXK6V3Z3Fd;V5u8syB2d?hcUM=kO7%HP%zo9MtyW9Z^A`AYCF&{ zV&23`!;V%%K~-#^`8NjW>)Xb11?m`&272DHnH90|Gr{>wVO4A%610!*7v z4w=zl)isBIc{?P+B$M6M`5E!2Lk|AniNW56JBR$Y-+(!pi9_UmoMfKa3hrk=&Rvlg@v|SNe1XP;fBZNf z<$fHuN{kh(LA3pKLRxpi82E86$^AGlBDWX<4yyiIual{H@BZb-d6$=Z(rc|g;HVmI ziF_gV<6HuM9ODyp(urSfi5kHytkO;H$NBh=ALoI_g7VLP9HN)#?q@#^cZC@*S?wNd&>J zofk~BEJyDzCH>GLeaM8qz0I4s??6`ccr+zrC2UlJw6so`g5S{Q3D6b1vo>nQN6C;Os{^}TJeGOB<#uVc0dBTJ=-9MI@vvA!nPXh zk6l#8fjYGRDwC+_l-`SSUZ1jNfP#G2#lm*33>A#BD)Ta~MTdt51qOwNM_03=}wA7=GA_*?f`?j>kq7E8^QRVtj+=2*24`%9J<+_@JZ1PCsjoneE6Kdu?8>Kb7$5^)AMB=Z*i6hlilTS$; z^!m!E`_O2hKR*suo*N`*Hft5_Dq67Ms{~2C1GJ?UeQpI=Jrs>zUJ4nX@5Xu)Z54TXmlU7tWVED 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 342/511] 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 343/511] 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 344/511] 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 345/511] 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 346/511] 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 347/511] 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 348/511] 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 349/511] 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 2c12c78e83bff820cb974302030ad5eff712fd29 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Sun, 5 Dec 2021 15:20:03 -0800 Subject: [PATCH 350/511] Added support for loading exif data from pre-2017 pngs from the "raw profile type exif" text chunk. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 104 +++++++++++++++++- .../Formats/Png/PngDecoderTests.cs | 19 ++++ tests/ImageSharp.Tests/TestImages.cs | 3 + .../Input/Png/raw-profile-type-exif.png | 3 + 4 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 tests/Images/Input/Png/raw-profile-type-exif.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cf3cd7eb1..82fc5e815 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -112,6 +112,11 @@ namespace SixLabors.ImageSharp.Formats.Png /// private PngChunk? nextChunk; + /// + /// "Exif" and two zero bytes. Used for the legacy exif parsing. + /// + private static readonly byte[] ExifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + /// /// Initializes a new instance of the class. /// @@ -182,7 +187,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); + this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); @@ -192,7 +197,7 @@ namespace SixLabors.ImageSharp.Formats.Png { var exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); - metadata.ExifProfile = new ExifProfile(exifData); + this.MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); } break; @@ -255,7 +260,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: - this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); + this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); @@ -265,7 +270,7 @@ namespace SixLabors.ImageSharp.Formats.Png { var exifData = new byte[chunk.Length]; chunk.Data.GetSpan().CopyTo(exifData); - metadata.ExifProfile = new ExifProfile(exifData); + this.MergeOrSetExifProfile(metadata, new ExifProfile(exifData), replaceExistingKeys: true); } break; @@ -937,9 +942,10 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Reads the compressed text chunk. Contains a uncompressed keyword and a compressed text string. /// + /// The object. /// The metadata to decode to. /// The containing the data. - private void ReadCompressedTextChunk(PngMetadata metadata, ReadOnlySpan data) + private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) { if (this.ignoreMetadata) { @@ -971,6 +977,94 @@ namespace SixLabors.ImageSharp.Formats.Png { metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); } + + if (name.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)) + { + this.ReadLegacyExifTextChunk(baseMetadata, uncompressed); + } + } + + /// + /// Reads exif data encoded into a text chunk with the name "raw profile type exif". + /// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the + /// 2017 update to png that allowed a true exif chunk. We load + /// + /// The to store the decoded exif tags into. + /// The contents of the "raw profile type exif" text chunk. + private void ReadLegacyExifTextChunk(ImageMetadata metadata, string data) + { + ReadOnlySpan dataSpan = data.AsSpan(); + dataSpan = dataSpan.TrimStart(); + + if (!dataSpan.Slice(0, 4).ToString().Equals("exif", StringComparison.OrdinalIgnoreCase)) + { + // "exif" identifier is missing from the beginning of the text chunk + return; + } + + // Skip to the data length + dataSpan = dataSpan.Slice(4).TrimStart(); + int dataLengthEnd = dataSpan.IndexOf('\n'); + int dataLength = int.Parse(dataSpan.Slice(0, dataSpan.IndexOf('\n')).ToString()); + + // Skip to the hex-encoded data + dataSpan = dataSpan.Slice(dataLengthEnd).Trim(); + string dataSpanString = dataSpan.ToString().Replace("\n", string.Empty); + if (dataSpanString.Length != (dataLength * 2)) + { + // Invalid length + return; + } + + // Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile + byte[] dataBlob = new byte[dataLength - ExifHeader.Length]; + for (int i = 0; i < dataLength; i++) + { + byte parsed = Convert.ToByte(dataSpanString.Substring(i * 2, 2), 16); + if (i < ExifHeader.Length) + { + if (parsed != ExifHeader[i]) + { + // Invalid exif header in the actual data blob + return; + } + } + else + { + dataBlob[i - ExifHeader.Length] = parsed; + } + } + + this.MergeOrSetExifProfile(metadata, new ExifProfile(dataBlob), replaceExistingKeys: false); + } + + /// + /// Sets the in to , + /// or copies exif tags if already contains an . + /// + /// The to store the exif data in. + /// The to copy exif tags from. + /// If already contains an , + /// controls whether existing exif tags in will be overwritten with any conflicting + /// tags from . + private void MergeOrSetExifProfile(ImageMetadata metadata, ExifProfile newProfile, bool replaceExistingKeys) + { + if (metadata.ExifProfile is null) + { + // No exif metadata was loaded yet, so just assign it + metadata.ExifProfile = newProfile; + } + else + { + // Try to merge existing keys with the ones from the new profile + foreach (IExifValue newKey in newProfile.Values) + { + if (replaceExistingKeys || metadata.ExifProfile.GetValueInternal(newKey.Tag) is null) + { + metadata.ExifProfile.SetValueInternal(newKey.Tag, newKey.GetValue()); + } + } + } } /// diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 9fc4d03dd..215cd14a1 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -444,5 +444,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png "Disco") .Dispose(); } + + [Theory] + [WithFile(TestImages.Png.Issue1875, PixelTypes.Rgba32)] + public void PngDecoder_CanDecode_LegacyTextExifChunk(TestImageProvider provider) + { + using Image image = provider.GetImage(PngDecoder); + + Assert.Equal(0, image.Metadata.ExifProfile.InvalidTags.Count); + Assert.Equal(3, image.Metadata.ExifProfile.Values.Count); + + Assert.Equal( + "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", + image.Metadata.ExifProfile.GetValue(ImageSharp.Metadata.Profiles.Exif.ExifTag.ImageDescription).Value); + Assert.Equal( + "Duplicated from basn3p02.png, then image metadata modified with exiv2", + image.Metadata.ExifProfile.GetValue(ImageSharp.Metadata.Profiles.Exif.ExifTag.ImageHistory).Value); + + Assert.Equal(42, (int)image.Metadata.ExifProfile.GetValue(ImageSharp.Metadata.Profiles.Exif.ExifTag.ImageNumber).Value); + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index e00364913..41e5e15d3 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -117,6 +117,9 @@ namespace SixLabors.ImageSharp.Tests // Issue 1765: https://github.com/SixLabors/ImageSharp/issues/1765 public const string Issue1765_Net6DeflateStreamRead = "Png/issues/Issue_1765_Net6DeflateStreamRead.png"; + // Discussion 1875: https://github.com/SixLabors/ImageSharp/discussions/1875 + public const string Issue1875 = "Png/raw-profile-type-exif.png"; + public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; diff --git a/tests/Images/Input/Png/raw-profile-type-exif.png b/tests/Images/Input/Png/raw-profile-type-exif.png new file mode 100644 index 000000000..efd9b35aa --- /dev/null +++ b/tests/Images/Input/Png/raw-profile-type-exif.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2259b08fd0c4681ecd068244df358b486f5eca1fcd18edbc7d9207eeef3ca5ed +size 392 From d5ddc4696e851d93569e7b9c356691b386993ca2 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Sun, 5 Dec 2021 15:57:05 -0800 Subject: [PATCH 351/511] Fixed an incomplete comment --- 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 82fc5e815..042bcb92b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -987,7 +987,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Reads exif data encoded into a text chunk with the name "raw profile type exif". /// This method was used by ImageMagick, exiftool, exiv2, digiKam, etc, before the - /// 2017 update to png that allowed a true exif chunk. We load + /// 2017 update to png that allowed a true exif chunk. /// /// The to store the decoded exif tags into. /// The contents of the "raw profile type exif" text chunk. From c8a191d2968fe2ed61302e35675f449317435150 Mon Sep 17 00:00:00 2001 From: jubilant-enigma <54286000+jubilant-enigma@users.noreply.github.com> Date: Mon, 6 Dec 2021 10:18:14 -0800 Subject: [PATCH 352/511] 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 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 042bcb92b..6db2328f5 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -115,7 +115,8 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// "Exif" and two zero bytes. Used for the legacy exif parsing. /// - private static readonly byte[] ExifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + // This uses C# compiler's optimization to refer to the static data directly, no intermediate array allocations happen. + private static ReadOnlySpan ExifHeader => new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; /// /// Initializes a new instance of the class. From 7f4c9cdd8fd337d13e27c79bb045928ccb2fd70b Mon Sep 17 00:00:00 2001 From: jubilant-enigma <54286000+jubilant-enigma@users.noreply.github.com> Date: Mon, 6 Dec 2021 10:33:08 -0800 Subject: [PATCH 353/511] 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 6db2328f5..a08242e68 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1022,7 +1022,7 @@ namespace SixLabors.ImageSharp.Formats.Png for (int i = 0; i < dataLength; i++) { byte parsed = Convert.ToByte(dataSpanString.Substring(i * 2, 2), 16); - if (i < ExifHeader.Length) + if ((uint)i < (uint)ExifHeader.Length) { if (parsed != ExifHeader[i]) { From 8b9c334bca8f9363893d54235ec5f883c4dc5777 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 6 Dec 2021 12:58:02 -0800 Subject: [PATCH 354/511] Moved the ExifHeader property to after the constructor to satisfy StyleCop. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index a08242e68..5a18826d1 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -112,12 +112,6 @@ namespace SixLabors.ImageSharp.Formats.Png /// private PngChunk? nextChunk; - /// - /// "Exif" and two zero bytes. Used for the legacy exif parsing. - /// - // This uses C# compiler's optimization to refer to the static data directly, no intermediate array allocations happen. - private static ReadOnlySpan ExifHeader => new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; - /// /// Initializes a new instance of the class. /// @@ -130,6 +124,12 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } + /// + /// Gets the sequence of bytes for the exif header ("Exif" ASCII and two zero bytes). Used for the legacy exif parsing. + /// + // This uses C# compiler's optimization to refer to the static data directly, no intermediate array allocations happen. + private static ReadOnlySpan ExifHeader => new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + /// public Configuration Configuration { get; } From c2b906ee12262957c1b91d70d89edd9dd4c4d2a5 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 6 Dec 2021 13:04:56 -0800 Subject: [PATCH 355/511] Removed unnecessary temporary allocations. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 134 +++++++++++++++++-- 1 file changed, 120 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 5a18826d1..236c447ab 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -997,7 +997,7 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan dataSpan = data.AsSpan(); dataSpan = dataSpan.TrimStart(); - if (!dataSpan.Slice(0, 4).ToString().Equals("exif", StringComparison.OrdinalIgnoreCase)) + if (!StringEquals(dataSpan.Slice(0, 4), "exif".AsSpan(), StringComparison.OrdinalIgnoreCase)) { // "exif" identifier is missing from the beginning of the text chunk return; @@ -1006,37 +1006,143 @@ namespace SixLabors.ImageSharp.Formats.Png // Skip to the data length dataSpan = dataSpan.Slice(4).TrimStart(); int dataLengthEnd = dataSpan.IndexOf('\n'); - int dataLength = int.Parse(dataSpan.Slice(0, dataSpan.IndexOf('\n')).ToString()); + int dataLength = ParseInt32(dataSpan.Slice(0, dataSpan.IndexOf('\n'))); // Skip to the hex-encoded data dataSpan = dataSpan.Slice(dataLengthEnd).Trim(); - string dataSpanString = dataSpan.ToString().Replace("\n", string.Empty); - if (dataSpanString.Length != (dataLength * 2)) + + if (dataLength < ExifHeader.Length) { - // Invalid length + // Not enough room for the required exif header, this data couldn't possibly be valid return; } // Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile - byte[] dataBlob = new byte[dataLength - ExifHeader.Length]; - for (int i = 0; i < dataLength; i++) + byte[] exifBlob = new byte[dataLength - ExifHeader.Length]; + + try { - byte parsed = Convert.ToByte(dataSpanString.Substring(i * 2, 2), 16); - if ((uint)i < (uint)ExifHeader.Length) + // Check for the presence of the exif header in the hex-encoded binary data + byte[] tempExifBuf = exifBlob; + if (exifBlob.Length < ExifHeader.Length) + { + // Need to allocate a temporary array, this should be an extremely uncommon (TODO: impossible?) case + tempExifBuf = new byte[ExifHeader.Length]; + } + + HexStringToBytes(dataSpan.Slice(0, ExifHeader.Length * 2), tempExifBuf.AsSpan()); + if (!tempExifBuf.AsSpan().Slice(0, ExifHeader.Length).SequenceEqual(ExifHeader)) { - if (parsed != ExifHeader[i]) + // Exif header in the hex data is not valid + return; + } + + // Skip over the exif header we just tested + dataSpan = dataSpan.Slice(ExifHeader.Length * 2); + dataLength -= ExifHeader.Length; + + // Load the hex-encoded data, one line at a time + for (int i = 0; i < dataLength;) + { + ReadOnlySpan lineSpan = dataSpan; + + int newlineIndex = dataSpan.IndexOf('\n'); + if (newlineIndex != -1) { - // Invalid exif header in the actual data blob - return; + lineSpan = dataSpan.Slice(0, newlineIndex); } + + i += HexStringToBytes(lineSpan, exifBlob.AsSpan().Slice(i)); + + dataSpan = dataSpan.Slice(newlineIndex + 1); + } + } + catch + { + return; + } + + this.MergeOrSetExifProfile(metadata, new ExifProfile(exifBlob), replaceExistingKeys: false); + } + + private static bool StringEquals(ReadOnlySpan span1, ReadOnlySpan span2, StringComparison comparisonType) + { +#pragma warning disable IDE0022 // Use expression body for methods +#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER + return span1.Equals(span2, comparisonType); +#else + return span1.ToString().Equals(span2.ToString(), comparisonType); +#endif +#pragma warning restore IDE0022 // Use expression body for methods + } + + /// + /// int.Parse() a ReadOnlySpan<char>, with a fallback for older frameworks. + /// + /// The to parse. + /// The of the integer to parse. + /// The to use when parsing the integer. + /// The parsed . + private static int ParseInt32( + ReadOnlySpan span, + System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, + IFormatProvider provider = null) + { +#pragma warning disable IDE0022 // Use expression body for methods +#if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER + return int.Parse(span, style, provider); +#else + return int.Parse(span.ToString(), style, provider); +#endif +#pragma warning restore IDE0022 // Use expression body for methods + } + + /// + /// Parses a hexadecimal string into a byte array without allocations. + /// Adapted from https://stackoverflow.com/a/9995303/871842 + /// + /// The hexadecimal string to parse. + /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. + /// The number of bytes written to . + private static int HexStringToBytes(ReadOnlySpan hexString, Span outputBytes) + { + if ((hexString.Length % 2) != 0) + { + throw new ArgumentException("Input string length must be a multiple of 2", nameof(hexString)); + } + + if ((outputBytes.Length * 2) < hexString.Length) + { + throw new ArgumentException("Output span must be at least half the length of the input string"); + } + + static int GetHexVal(char hexChar) + { + if (hexChar >= '0' && hexChar <= '9') + { + return hexChar - '0'; + } + else if (hexChar >= 'A' && hexChar <= 'F') + { + return 10 + (hexChar - 'A'); + } + else if (hexChar >= 'a' && hexChar <= 'f') + { + return 10 + (hexChar - 'a'); } else { - dataBlob[i - ExifHeader.Length] = parsed; + throw new ArgumentException($"Invalid hexadecimal value {hexChar}"); } } - this.MergeOrSetExifProfile(metadata, new ExifProfile(dataBlob), replaceExistingKeys: false); + int inputByteCount = hexString.Length / 2; + for (int i = 0; i < inputByteCount; i++) + { + outputBytes[i] = (byte)((GetHexVal(hexString[i * 2]) << 4) + GetHexVal(hexString[(i * 2) + 1])); + } + + return inputByteCount; } /// From c8e2902be2e1ad4e271e316b36e946eaa2d35e43 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 6 Dec 2021 13:14:35 -0800 Subject: [PATCH 356/511] Moved legacy exif data loading test from PngDecoderTests to PngMetadataTests. --- .../Formats/Png/PngDecoderTests.cs | 19 -------------- .../Formats/Png/PngMetadataTests.cs | 26 +++++++++++++++++++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 215cd14a1..9fc4d03dd 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -444,24 +444,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png "Disco") .Dispose(); } - - [Theory] - [WithFile(TestImages.Png.Issue1875, PixelTypes.Rgba32)] - public void PngDecoder_CanDecode_LegacyTextExifChunk(TestImageProvider provider) - { - using Image image = provider.GetImage(PngDecoder); - - Assert.Equal(0, image.Metadata.ExifProfile.InvalidTags.Count); - Assert.Equal(3, image.Metadata.ExifProfile.Values.Count); - - Assert.Equal( - "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", - image.Metadata.ExifProfile.GetValue(ImageSharp.Metadata.Profiles.Exif.ExifTag.ImageDescription).Value); - Assert.Equal( - "Duplicated from basn3p02.png, then image metadata modified with exiv2", - image.Metadata.ExifProfile.GetValue(ImageSharp.Metadata.Profiles.Exif.ExifTag.ImageHistory).Value); - - Assert.Equal(42, (int)image.Metadata.ExifProfile.GetValue(ImageSharp.Metadata.Profiles.Exif.ExifTag.ImageNumber).Value); - } } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index b4307af5d..d763d2ba0 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -289,5 +289,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png Assert.Contains(meta.TextData, m => m.Keyword is "NoLang" && m.Value is "this text chunk is missing a language tag"); Assert.Contains(meta.TextData, m => m.Keyword is "NoTranslatedKeyword" && m.Value is "dieser chunk hat kein übersetztes Schlüßelwort"); } + + [Theory] + [InlineData(TestImages.Png.Issue1875)] + public void Identify_ReadsLegacyExifData(string imagePath) + { + var testFile = TestFile.Create(imagePath); + using (var stream = new MemoryStream(testFile.Bytes, false)) + { + IImageInfo imageInfo = Image.Identify(stream); + Assert.NotNull(imageInfo); + Assert.NotNull(imageInfo.Metadata.ExifProfile); + ExifProfile exif = imageInfo.Metadata.ExifProfile; + + Assert.Equal(0, exif.InvalidTags.Count); + Assert.Equal(3, exif.Values.Count); + + Assert.Equal( + "A colorful tiling of blue, red, yellow, and green 4x4 pixel blocks.", + exif.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal( + "Duplicated from basn3p02.png, then image metadata modified with exiv2", + exif.GetValue(ExifTag.ImageHistory).Value); + + Assert.Equal(42, (int)exif.GetValue(ExifTag.ImageNumber).Value); + } + } } } From 0c65e13a4ec6becac842f3fda5c4bca92913f97a Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 6 Dec 2021 21:40:26 -0800 Subject: [PATCH 357/511] Don't save the exif text chunk if it is successfully parsed --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 27 +++++++++++-------- .../Formats/Png/PngMetadataTests.cs | 6 ++++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 236c447ab..436e706e8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -976,12 +976,16 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed)) { - metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); - } - - if (name.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)) - { - this.ReadLegacyExifTextChunk(baseMetadata, uncompressed); + if (name.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase) && + this.TryReadLegacyExifTextChunk(baseMetadata, uncompressed)) + { + // Successfully parsed exif data stored as text in this chunk + } + else + { + // Seems to be regular old text data, or we failed to parse it in any special way + metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); + } } } @@ -992,7 +996,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The to store the decoded exif tags into. /// The contents of the "raw profile type exif" text chunk. - private void ReadLegacyExifTextChunk(ImageMetadata metadata, string data) + private bool TryReadLegacyExifTextChunk(ImageMetadata metadata, string data) { ReadOnlySpan dataSpan = data.AsSpan(); dataSpan = dataSpan.TrimStart(); @@ -1000,7 +1004,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (!StringEquals(dataSpan.Slice(0, 4), "exif".AsSpan(), StringComparison.OrdinalIgnoreCase)) { // "exif" identifier is missing from the beginning of the text chunk - return; + return false; } // Skip to the data length @@ -1014,7 +1018,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (dataLength < ExifHeader.Length) { // Not enough room for the required exif header, this data couldn't possibly be valid - return; + return false; } // Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile @@ -1034,7 +1038,7 @@ namespace SixLabors.ImageSharp.Formats.Png if (!tempExifBuf.AsSpan().Slice(0, ExifHeader.Length).SequenceEqual(ExifHeader)) { // Exif header in the hex data is not valid - return; + return false; } // Skip over the exif header we just tested @@ -1059,10 +1063,11 @@ namespace SixLabors.ImageSharp.Formats.Png } catch { - return; + return false; } this.MergeOrSetExifProfile(metadata, new ExifProfile(exifBlob), replaceExistingKeys: false); + return true; } private static bool StringEquals(ReadOnlySpan span1, ReadOnlySpan span2, StringComparison comparisonType) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs index d763d2ba0..8db1d1aaf 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -300,8 +301,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png IImageInfo imageInfo = Image.Identify(stream); Assert.NotNull(imageInfo); Assert.NotNull(imageInfo.Metadata.ExifProfile); - ExifProfile exif = imageInfo.Metadata.ExifProfile; + PngMetadata meta = imageInfo.Metadata.GetFormatMetadata(PngFormat.Instance); + Assert.DoesNotContain(meta.TextData, t => t.Keyword.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase)); + + ExifProfile exif = imageInfo.Metadata.ExifProfile; Assert.Equal(0, exif.InvalidTags.Count); Assert.Equal(3, exif.Values.Count); From bf3035f06b4ad4fd528a6d7826ea8935a4a764ac Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 6 Dec 2021 21:46:59 -0800 Subject: [PATCH 358/511] Don't include unnecessary parameters for helper functions that are only used once. Added a missing comment for StringEqualsInsensitive. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 26 +++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 436e706e8..721a05240 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1001,7 +1001,7 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan dataSpan = data.AsSpan(); dataSpan = dataSpan.TrimStart(); - if (!StringEquals(dataSpan.Slice(0, 4), "exif".AsSpan(), StringComparison.OrdinalIgnoreCase)) + if (!StringEqualsInsensitive(dataSpan.Slice(0, 4), "exif".AsSpan())) { // "exif" identifier is missing from the beginning of the text chunk return false; @@ -1070,13 +1070,20 @@ namespace SixLabors.ImageSharp.Formats.Png return true; } - private static bool StringEquals(ReadOnlySpan span1, ReadOnlySpan span2, StringComparison comparisonType) + /// + /// Compares two ReadOnlySpan<char>s in a case-insensitive method. + /// This is only needed because older frameworks are missing the extension method. + /// + /// The first to compare. + /// The second to compare. + /// True if the spans were identical, false otherwise. + private static bool StringEqualsInsensitive(ReadOnlySpan span1, ReadOnlySpan span2) { #pragma warning disable IDE0022 // Use expression body for methods #if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER - return span1.Equals(span2, comparisonType); + return span1.Equals(span2, StringComparison.OrdinalIgnoreCase); #else - return span1.ToString().Equals(span2.ToString(), comparisonType); + return span1.ToString().Equals(span2.ToString(), StringComparison.OrdinalIgnoreCase); #endif #pragma warning restore IDE0022 // Use expression body for methods } @@ -1085,19 +1092,14 @@ namespace SixLabors.ImageSharp.Formats.Png /// int.Parse() a ReadOnlySpan<char>, with a fallback for older frameworks. /// /// The to parse. - /// The of the integer to parse. - /// The to use when parsing the integer. /// The parsed . - private static int ParseInt32( - ReadOnlySpan span, - System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Integer, - IFormatProvider provider = null) + private static int ParseInt32(ReadOnlySpan span) { #pragma warning disable IDE0022 // Use expression body for methods #if NETSTANDARD2_1 || NETCOREAPP2_1_OR_GREATER - return int.Parse(span, style, provider); + return int.Parse(span); #else - return int.Parse(span.ToString(), style, provider); + return int.Parse(span.ToString()); #endif #pragma warning restore IDE0022 // Use expression body for methods } From c214af56eb40e3740117dfc429f7b600a0559f9c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 7 Dec 2021 19:35:51 +1100 Subject: [PATCH 359/511] 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 360/511] 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 361/511] 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 362/511] 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 363/511] 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 364/511] 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 365/511] 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 366/511] 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 367/511] 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 368/511] 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 369/511] 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 370/511] 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 371/511] 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 372/511] 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 373/511] 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 374/511] 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 375/511] 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 376/511] 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 377/511] 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 378/511] 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 379/511] 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 380/511] 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 381/511] 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 382/511] 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 383/511] 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 384/511] 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 385/511] 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 386/511] 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 387/511] 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 388/511] 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 389/511] 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 390/511] 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 391/511] 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 392/511] 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 393/511] 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 394/511] 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 395/511] 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 396/511] 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 397/511] 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 398/511] 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 399/511] 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 400/511] 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 8ea98385cef8e68317f9249f3e85ee2d6b40f352 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 16 Dec 2021 13:13:06 +0100 Subject: [PATCH 401/511] Verify CRC of IDAT chunk is correct --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index ffaa9d567..f81cbca21 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1206,16 +1206,16 @@ 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 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)) + if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency) { chunk = new PngChunk(length, type); return true; } + long pos = this.currentStream.Position; chunk = new PngChunk( length: length, type: type, @@ -1223,6 +1223,13 @@ namespace SixLabors.ImageSharp.Formats.Png this.ValidateChunk(chunk); + // Restore the stream position for IDAT chunks, because it will be decoded later and + // was only read to verifying the CRC is correct. + if (type == PngChunkType.Data) + { + this.currentStream.Position = pos; + } + return true; } From 3310fa6ffdfd283b834c91cdfa76436bdf07332e Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 16 Dec 2021 13:21:27 +0100 Subject: [PATCH 402/511] Add test for wrong CRC in IDAT chunk --- .../Formats/Png/PngDecoderTests.cs | 33 ++++++++++++++----- tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Png/xcsn0g01.png | 3 ++ 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 tests/Images/Input/Png/xcsn0g01.png diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index d63dad8d4..de1c47d41 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -255,7 +255,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -271,7 +271,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_InvalidBitDepth_ThrowsException(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -287,7 +287,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_InvalidColorType_ThrowsException(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -297,13 +297,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png Assert.Contains("Invalid or unsupported color type", ex.Message); } + [Theory] + [WithFile(TestImages.Png.Bad.WrongCrcDataChunk, PixelTypes.Rgba32)] + public void Decode_InvalidDataChunkCrc_ThrowsException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception( + () => + { + using Image image = provider.GetImage(PngDecoder); + image.DebugSave(provider); + }); + Assert.NotNull(ex); + Assert.Contains("CRC Error. PNG IDAT chunk is corrupt!", ex.Message); + } + // https://github.com/SixLabors/ImageSharp/issues/1014 [Theory] [WithFileCollection(nameof(TestImagesIssue1014), PixelTypes.Rgba32)] public void Issue1014_DataSplitOverMultipleIDatChunks(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -319,7 +334,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Issue1177_CRC_Omitted(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -335,7 +350,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Issue1127(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -351,7 +366,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Issue1047(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -372,7 +387,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Issue1765(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); @@ -388,7 +403,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Issue410_MalformedApplePng(TestImageProvider provider) where TPixel : unmanaged, IPixel { - System.Exception ex = Record.Exception( + Exception ex = Record.Exception( () => { using Image image = provider.GetImage(PngDecoder); diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f43c0c9bd..b87ffc742 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -120,6 +120,7 @@ namespace SixLabors.ImageSharp.Tests public static class Bad { public const string MissingDataChunk = "Png/xdtn0g01.png"; + public const string WrongCrcDataChunk = "Png/xcsn0g01.png"; public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; // Zlib errors. diff --git a/tests/Images/Input/Png/xcsn0g01.png b/tests/Images/Input/Png/xcsn0g01.png new file mode 100644 index 000000000..6908a107c --- /dev/null +++ b/tests/Images/Input/Png/xcsn0g01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71e4b2826f61556eda39f3a93c8769b14d3ac90f135177b9373061199dbef39a +size 164 From f3a5ba0b4d94b2e8422416a733e3d7d272fa0786 Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Thu, 16 Dec 2021 15:45:23 +0100 Subject: [PATCH 403/511] Review suggestion Co-authored-by: Anton Firszov --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index de1c47d41..c29f8c589 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -302,11 +302,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png public void Decode_InvalidDataChunkCrc_ThrowsException(TestImageProvider provider) where TPixel : unmanaged, IPixel { - Exception ex = Record.Exception( + InvalidImageContentException ex = Assert.Throws( () => { using Image image = provider.GetImage(PngDecoder); - image.DebugSave(provider); }); Assert.NotNull(ex); Assert.Contains("CRC Error. PNG IDAT chunk is corrupt!", ex.Message); From 3ee73a89c535e379ec36e6edf032332a7d466bf5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 16 Dec 2021 17:50:55 +0100 Subject: [PATCH 404/511] 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; } } } From e045fbb023b561f9b327e434e7aae319772372b6 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 17 Dec 2021 13:53:46 +0300 Subject: [PATCH 405/511] Sampling factor validation --- .../Jpeg/Components/Decoder/JpegComponent.cs | 11 ------- .../Formats/Jpeg/JpegDecoderCore.cs | 29 +++++++++++++++++-- .../Formats/Jpg/JpegDecoderTests.Images.cs | 6 ++-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs index 4da422e7f..3804e1c6c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs @@ -19,21 +19,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder this.Frame = frame; this.Id = id; - // Validate sampling factors. - if (horizontalFactor == 0 || verticalFactor == 0) - { - JpegThrowHelper.ThrowBadSampling(); - } - this.HorizontalSamplingFactor = horizontalFactor; this.VerticalSamplingFactor = verticalFactor; this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor); - if (quantizationTableIndex > 3) - { - JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex); - } - this.QuantizationTableIndex = quantizationTableIndex; this.Index = index; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4be6731cc..bd221a357 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1022,10 +1022,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int index = 0; for (int i = 0; i < componentCount; i++) { + // 1 byte: component identifier + byte componentId = this.temp[index]; + + // 1 byte: component sampling factors byte hv = this.temp[index + 1]; int h = (hv >> 4) & 15; int v = hv & 15; + // Validate: 1-4 range + if (h is < 1 or > 4) + { + JpegThrowHelper.ThrowInvalidImageContentException($"Bad horizontal sampling factor: {h}"); + } + + // Validate: 1-4 range + if (v is < 1 or > 4) + { + JpegThrowHelper.ThrowInvalidImageContentException($"Bad vertical sampling factor: {v}"); + } + if (maxH < h) { maxH = h; @@ -1036,10 +1052,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg maxV = v; } - var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); + // 1 byte: quantization table destination selector + byte quantTableIndex = this.temp[index + 2]; + + // Validate: 0-3 range + if (quantTableIndex > 3) + { + JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex); + } + + var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i); this.Frame.Components[i] = component; - this.Frame.ComponentIds[i] = component.Id; + this.Frame.ComponentIds[i] = componentId; index += componentBytes; } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index ef817154d..d82359e61 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -40,9 +40,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // LibJpeg can open this despite incorrect colorspace metadata. TestImages.Jpeg.Issues.IncorrectColorspace855, - // LibJpeg can open this despite the invalid subsampling units. - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, - // High depth images TestImages.Jpeg.Baseline.Testorig12bit, }; @@ -90,7 +87,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.Fuzz.AccessViolationException827, TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, - TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B, + TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, }; private static readonly Dictionary CustomToleranceValues = From 1f9ef3e926372325ebc8d342729f03cb782e880a Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 18 Dec 2021 11:13:39 +0300 Subject: [PATCH 406/511] Removed unused methods, added new throw helper method --- .../Formats/Jpeg/JpegDecoderCore.cs | 4 ++-- .../Formats/Jpeg/JpegThrowHelper.cs | 20 +++---------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index bd221a357..4543153de 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1033,13 +1033,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Validate: 1-4 range if (h is < 1 or > 4) { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad horizontal sampling factor: {h}"); + JpegThrowHelper.ThrowBadSampling(h); } // Validate: 1-4 range if (v is < 1 or > 4) { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad vertical sampling factor: {v}"); + JpegThrowHelper.ThrowBadSampling(v); } if (maxH < h) diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index b6c7e7626..0292fbcab 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -22,23 +22,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg [MethodImpl(InliningOptions.ColdPath)] public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage); - /// - /// Cold path optimization for throwing 's. - /// - /// The error message for the exception. - /// The exception that is the cause of the current exception, or a null reference - /// if no inner exception is specified. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException); - - /// - /// Cold path optimization for throwing 's - /// - /// The error message for the exception. - [MethodImpl(InliningOptions.ColdPath)] - public static void ThrowNotImplementedException(string errorMessage) - => throw new NotImplementedException(errorMessage); - [MethodImpl(InliningOptions.ColdPath)] public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); @@ -51,6 +34,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg [MethodImpl(InliningOptions.ColdPath)] public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor."); + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowBadSampling(int factor) => throw new InvalidImageContentException($"Bad sampling factor: {factor}"); + [MethodImpl(InliningOptions.ColdPath)] public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}."); From f3b8e5cd8d1e00492367b40498de1a67f6da2375 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 18 Dec 2021 15:42:13 +0300 Subject: [PATCH 407/511] gfoidl IsOutOfRange --- src/ImageSharp/Common/Helpers/Numerics.cs | 9 +++++++++ .../Formats/Jpeg/JpegDecoderCore.cs | 4 ++-- .../ImageSharp.Tests/Common/NumericsTests.cs | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 513db0c44..8f82476e1 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -963,5 +963,14 @@ namespace SixLabors.ImageSharp public static uint RotateRightSoftwareFallback(uint value, int offset) => (value >> offset) | (value << (32 - offset)); #endif + + /// + /// Tells whether input value is outside of the given range. + /// + /// Value. + /// Mininum value, inclusive. + /// Maximum value, inclusive. + public static bool IsOutOfRange(int value, int min, int max) + => (uint)(value - min) > (uint)(max - min); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4543153de..4b68c39ea 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1031,13 +1031,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int v = hv & 15; // Validate: 1-4 range - if (h is < 1 or > 4) + if (Numerics.IsOutOfRange(h, 1, 4)) { JpegThrowHelper.ThrowBadSampling(h); } // Validate: 1-4 range - if (v is < 1 or > 4) + if (Numerics.IsOutOfRange(v, 1, 4)) { JpegThrowHelper.ThrowBadSampling(v); } diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs index 62819af49..2a43b06a9 100644 --- a/tests/ImageSharp.Tests/Common/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Common/NumericsTests.cs @@ -97,5 +97,25 @@ namespace SixLabors.ImageSharp.Tests.Common Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\n{value} / {divisor} = {expected}"); } } + + private static bool IsOutOfRange_ReferenceImplementation(int value, int min, int max) => value < min || value > max; + + [Theory] + [InlineData(1, 100)] + public void IsOutOfRange(int seed, int count) + { + var rng = new Random(seed); + for (int i = 0; i < count; i++) + { + int value = rng.Next(); + int min = rng.Next(); + int max = rng.Next(min, int.MaxValue); + + bool expected = IsOutOfRange_ReferenceImplementation(value, min, max); + bool actual = Numerics.IsOutOfRange(value, min, max); + + Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); + } + } } } From d2500953f766281f7df571215f3a49923d49225c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Dec 2021 16:56:41 +0300 Subject: [PATCH 408/511] universal format detector --- .../Formats/Tiff/TiffConfigurationModule.cs | 4 +- .../Formats/Tiff/TiffImageFormatDetector.cs | 45 +++++++------------ 2 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs index 656483435..cc97da5bb 100644 --- a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs +++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs @@ -13,9 +13,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder()); configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder()); - configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector(false)); - - configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector(true)); + configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector()); } } } diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs index 5755b306a..51280b4bf 100644 --- a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs @@ -10,16 +10,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// public sealed class TiffImageFormatDetector : IImageFormatDetector { - private readonly bool isBigTiff; - - /// - /// Initializes a new instance of the class. - /// - /// if set to true is BigTiff. - public TiffImageFormatDetector(bool isBigTiff) => this.isBigTiff = isBigTiff; - /// - public int HeaderSize => this.isBigTiff ? 8 : 4; + public int HeaderSize => 8; /// public IImageFormat DetectFormat(ReadOnlySpan header) @@ -39,39 +31,32 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (header[0] == 0x49 && header[1] == 0x49) { // Little-endian - if (this.isBigTiff is false) + if (header[2] == 0x2A && header[3] == 0x00) { - if (header[2] == 0x2A && header[3] == 0x00) - { - return true; - } + // tiff + return true; } - else + else if (header[2] == 0x2B && header[3] == 0x00 + && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) { - if (header[2] == 0x2B && header[3] == 0x00 - && header[4] == 8 && header[5] == 0 && header[6] == 0 && header[7] == 0) - { - return true; - } + // big tiff + return true; } } else if (header[0] == 0x4D && header[1] == 0x4D) { // Big-endian - if (this.isBigTiff is false) + if (header[2] == 0 && header[3] == 0x2A) { - if (header[2] == 0 && header[3] == 0x2A) - { - return true; - } + // tiff + return true; } else + if (header[2] == 0 && header[3] == 0x2B + && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) { - if (header[2] == 0 && header[3] == 0x2B - && header[4] == 0 && header[5] == 8 && header[6] == 0 && header[7] == 0) - { - return true; - } + // big tiff + return true; } } } From 7df7d0dbec9cbfb598d9814a107b865789d1e27c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 18 Dec 2021 17:05:43 +0300 Subject: [PATCH 409/511] add TiffFormatType enum --- .../Tiff/TiffDecoderMetadataCreator.cs | 2 +- src/ImageSharp/Formats/Tiff/TiffFormatType.cs | 21 +++++++++++++++++++ src/ImageSharp/Formats/Tiff/TiffMetadata.cs | 7 ++----- .../Formats/Tiff/BigTiffDecoderTests.cs | 5 +++-- 4 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 src/ImageSharp/Formats/Tiff/TiffFormatType.cs diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index ddaf6fa96..4c4023ace 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); tiffMetadata.ByteOrder = byteOrder; - tiffMetadata.IsBigTiff = isBigTiff; + tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default; return imageMetaData; } diff --git a/src/ImageSharp/Formats/Tiff/TiffFormatType.cs b/src/ImageSharp/Formats/Tiff/TiffFormatType.cs new file mode 100644 index 000000000..320386e4c --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/TiffFormatType.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Formats.Tiff +{ + /// + /// The TIFF format type enum. + /// + public enum TiffFormatType + { + /// + /// The TIFF file format type. + /// + Default, + + /// + /// The BigTIFF format type. + /// + BigTIFF + } +} diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index d537981db..f5124a78a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -27,12 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff public ByteOrder ByteOrder { get; set; } /// - /// Gets or sets a value indicating whether this instance is BigTiff format. + /// Gets or sets the format type. /// - /// - /// true if this instance BigTiff format; otherwise, false. - /// - public bool IsBigTiff { get; set; } + public TiffFormatType FormatType { get; set; } /// public IDeepCloneable DeepClone() => new TiffMetadata(this); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs index 24fce1d0d..7a02c91b8 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffDecoderTests.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; +using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; @@ -80,9 +81,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution); Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits); - ImageSharp.Formats.Tiff.TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata(); + TiffMetadata tiffmeta = info.Metadata.GetTiffMetadata(); Assert.NotNull(tiffmeta); - Assert.True(tiffmeta.IsBigTiff); + Assert.Equal(TiffFormatType.BigTIFF, tiffmeta.FormatType); } } From 7ced49747e84744c93b6dca6138257e0d9395a11 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 18 Dec 2021 23:41:55 +0300 Subject: [PATCH 410/511] Fixed tests --- .../ImageSharp.Tests/Common/NumericsTests.cs | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/ImageSharp.Tests/Common/NumericsTests.cs b/tests/ImageSharp.Tests/Common/NumericsTests.cs index 2a43b06a9..a82997477 100644 --- a/tests/ImageSharp.Tests/Common/NumericsTests.cs +++ b/tests/ImageSharp.Tests/Common/NumericsTests.cs @@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Common this.Output = output; } + public static TheoryData IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue }; + private static int Log2_ReferenceImplementation(uint value) { int n = 0; @@ -101,21 +103,16 @@ namespace SixLabors.ImageSharp.Tests.Common private static bool IsOutOfRange_ReferenceImplementation(int value, int min, int max) => value < min || value > max; [Theory] - [InlineData(1, 100)] - public void IsOutOfRange(int seed, int count) + [MemberData(nameof(IsOutOfRangeTestData))] + public void IsOutOfRange(int value) { - var rng = new Random(seed); - for (int i = 0; i < count; i++) - { - int value = rng.Next(); - int min = rng.Next(); - int max = rng.Next(min, int.MaxValue); + const int min = 7; + const int max = 92; - bool expected = IsOutOfRange_ReferenceImplementation(value, min, max); - bool actual = Numerics.IsOutOfRange(value, min, max); + bool expected = IsOutOfRange_ReferenceImplementation(value, min, max); + bool actual = Numerics.IsOutOfRange(value, min, max); - Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); - } + Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})"); } } } From 8e05f5dba1f9dde0b94f144f30b03a5dc540e531 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 Dec 2021 20:12:01 +0100 Subject: [PATCH 411/511] Add SSE2 version of GetResidualCost --- .../Formats/Webp/Lossy/Vp8Residual.cs | 102 +++++++++++++++--- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs index 4eeeedd37..8da176e77 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.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 { @@ -11,6 +16,16 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy /// internal class Vp8Residual { +#if SUPPORTS_RUNTIME_INTRINSICS + private static readonly Vector128 Cst2 = Vector128.Create((byte)2); + + private static readonly Vector128 Cst67 = Vector128.Create((byte)67); +#endif + + private readonly byte[] scratch = new byte[32]; + + private readonly ushort[] scratchUShort = new ushort[16]; + public int First { get; set; } public int Last { get; set; } @@ -129,27 +144,82 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy return LossyUtils.Vp8BitCost(0, (byte)p0); } - int v; - for (; n < this.Last; ++n) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) { + Span ctxs = this.scratch.AsSpan(0, 16); + Span levels = this.scratch.AsSpan(16, 16); + Span absLevels = this.scratchUShort.AsSpan(); + + // Precompute clamped levels and contexts, packed to 8b. + ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs); + Vector128 c0 = Unsafe.As>(ref outputRef).AsInt16(); + Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)).AsInt16(); + Vector128 d0 = Sse2.Subtract(Vector128.Zero, c0); + Vector128 d1 = Sse2.Subtract(Vector128.Zero, c1); + Vector128 e0 = Sse2.Max(c0, d0); // abs(v), 16b + Vector128 e1 = Sse2.Max(c1, d1); + Vector128 f = Sse2.PackSignedSaturate(e0, e1); + Vector128 g = Sse2.Min(f.AsByte(), Cst2); + Vector128 h = Sse2.Min(f.AsByte(), Cst67); // clampLevel in [0..67] + + ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs); + ref byte levelsRef = ref MemoryMarshal.GetReference(levels); + ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels); + Unsafe.As>(ref ctxsRef) = g; + Unsafe.As>(ref levelsRef) = h; + Unsafe.As>(ref absLevelsRef) = e0.AsUInt16(); + Unsafe.As>(ref Unsafe.Add(ref absLevelsRef, 8)) = e1.AsUInt16(); + + int level; + int flevel; + for (; n < this.Last; ++n) + { + int ctx = ctxs[n]; + level = levels[n]; + flevel = absLevels[n]; + cost += WebpLookupTables.Vp8LevelFixedCosts[flevel] + t.Costs[level]; + t = costs[n + 1].Costs[ctx]; + } + + // Last coefficient is always non-zero. + level = levels[n]; + flevel = absLevels[n]; + cost += WebpLookupTables.Vp8LevelFixedCosts[flevel] + t.Costs[level]; + if (n < 15) + { + int b = WebpConstants.Vp8EncBands[n + 1]; + int ctx = ctxs[n]; + int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; + cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); + } + + return cost; + } +#endif + { + int v; + for (; n < this.Last; ++n) + { + v = Math.Abs(this.Coeffs[n]); + int ctx = v >= 2 ? 2 : v; + cost += LevelCost(t.Costs, v); + t = costs[n + 1].Costs[ctx]; + } + + // Last coefficient is always non-zero v = Math.Abs(this.Coeffs[n]); - int ctx = v >= 2 ? 2 : v; cost += LevelCost(t.Costs, v); - t = costs[n + 1].Costs[ctx]; - } + if (n < 15) + { + int b = WebpConstants.Vp8EncBands[n + 1]; + int ctx = v == 1 ? 1 : 2; + int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; + cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); + } - // Last coefficient is always non-zero - v = Math.Abs(this.Coeffs[n]); - cost += LevelCost(t.Costs, v); - if (n < 15) - { - int b = WebpConstants.Vp8EncBands[n + 1]; - int ctx = v == 1 ? 1 : 2; - int lastP0 = this.Prob[b].Probabilities[ctx].Probabilities[0]; - cost += LossyUtils.Vp8BitCost(0, (byte)lastP0); + return cost; } - - return cost; } [MethodImpl(InliningOptions.ShortMethod)] From 7eb6b238b30bec1781d96a90959c42cb5151f69a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 Dec 2021 20:23:52 +0100 Subject: [PATCH 412/511] Add AVX2 version of GetResidualCost --- .../Formats/Webp/Lossy/Vp8Residual.cs | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs index 8da176e77..0c0f9b02a 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs @@ -17,9 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy internal class Vp8Residual { #if SUPPORTS_RUNTIME_INTRINSICS - private static readonly Vector128 Cst2 = Vector128.Create((byte)2); + private static readonly Vector256 Cst2 = Vector256.Create((byte)2); - private static readonly Vector128 Cst67 = Vector128.Create((byte)67); + private static readonly Vector256 Cst67 = Vector256.Create((byte)67); #endif private readonly byte[] scratch = new byte[32]; @@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy } #if SUPPORTS_RUNTIME_INTRINSICS - if (Sse2.IsSupported) + if (Avx2.IsSupported) { Span ctxs = this.scratch.AsSpan(0, 16); Span levels = this.scratch.AsSpan(16, 16); @@ -153,23 +153,19 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy // Precompute clamped levels and contexts, packed to 8b. ref short outputRef = ref MemoryMarshal.GetReference(this.Coeffs); - Vector128 c0 = Unsafe.As>(ref outputRef).AsInt16(); - Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref outputRef, 8)).AsInt16(); - Vector128 d0 = Sse2.Subtract(Vector128.Zero, c0); - Vector128 d1 = Sse2.Subtract(Vector128.Zero, c1); - Vector128 e0 = Sse2.Max(c0, d0); // abs(v), 16b - Vector128 e1 = Sse2.Max(c1, d1); - Vector128 f = Sse2.PackSignedSaturate(e0, e1); - Vector128 g = Sse2.Min(f.AsByte(), Cst2); - Vector128 h = Sse2.Min(f.AsByte(), Cst67); // clampLevel in [0..67] + Vector256 c0 = Unsafe.As>(ref outputRef).AsInt16(); + Vector256 d0 = Avx2.Subtract(Vector256.Zero, c0); + Vector256 e0 = Avx2.Max(c0, d0); // abs(v), 16b + Vector256 f = Avx2.PackSignedSaturate(e0, e0); + Vector256 g = Avx2.Min(f.AsByte(), Cst2); + Vector256 h = Avx2.Min(f.AsByte(), Cst67); // clampLevel in [0..67] ref byte ctxsRef = ref MemoryMarshal.GetReference(ctxs); ref byte levelsRef = ref MemoryMarshal.GetReference(levels); ref ushort absLevelsRef = ref MemoryMarshal.GetReference(absLevels); - Unsafe.As>(ref ctxsRef) = g; - Unsafe.As>(ref levelsRef) = h; - Unsafe.As>(ref absLevelsRef) = e0.AsUInt16(); - Unsafe.As>(ref Unsafe.Add(ref absLevelsRef, 8)) = e1.AsUInt16(); + Unsafe.As>(ref ctxsRef) = g.GetLower(); + Unsafe.As>(ref levelsRef) = h.GetLower(); + Unsafe.As>(ref absLevelsRef) = e0.AsUInt16(); int level; int flevel; From 0740ee7802251f25f352f9a4c0b3d9fa2ac40348 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 Dec 2021 21:07:22 +0100 Subject: [PATCH 413/511] Add SSE2 version of SetCoeffs --- .../Formats/Webp/Lossy/Vp8Residual.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs index 0c0f9b02a..5e4591894 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Residual.cs @@ -52,14 +52,39 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy public void SetCoeffs(Span coeffs) { - int n; - this.Last = -1; - for (n = 15; n >= 0; --n) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + ref short coeffsRef = ref MemoryMarshal.GetReference(coeffs); + Vector128 c0 = Unsafe.As>(ref coeffsRef); + Vector128 c1 = Unsafe.As>(ref Unsafe.Add(ref coeffsRef, 8)); + + // Use SSE2 to compare 16 values with a single instruction. + Vector128 m0 = Sse2.PackSignedSaturate(c0.AsInt16(), c1.AsInt16()); + Vector128 m1 = Sse2.CompareEqual(m0, Vector128.Zero); + + // Get the comparison results as a bitmask into 16bits. Negate the mask to get + // the position of entries that are not equal to zero. We don't need to mask + // out least significant bits according to res->first, since coeffs[0] is 0 + // if res->first > 0. + uint mask = 0x0000ffffu ^ (uint)Sse2.MoveMask(m1); + + // The position of the most significant non-zero bit indicates the position of + // the last non-zero value. + this.Last = mask != 0 ? Numerics.Log2(mask) : -1; + } + else +#endif { - if (coeffs[n] != 0) + int n; + this.Last = -1; + for (n = 15; n >= 0; --n) { - this.Last = n; - break; + if (coeffs[n] != 0) + { + this.Last = n; + break; + } } } From 8a4b5c6ed5d05a18ca7c10b9f512c29c7b015021 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 19 Dec 2021 21:32:26 +0100 Subject: [PATCH 414/511] moved SwapOrCopyContent responsibility entirely to Buffer2D --- src/ImageSharp/Memory/Buffer2D{T}.cs | 41 +++-- .../MemoryGroup{T}.Owned.cs | 6 + .../DiscontiguousBuffers/MemoryGroup{T}.cs | 27 +-- .../Memory/Buffer2DTests.SwapOrCopyContent.cs | 160 ++++++++++++++++++ .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 53 +----- .../MemoryGroupTests.SwapOrCopyContent.cs | 107 ------------ 6 files changed, 196 insertions(+), 198 deletions(-) create mode 100644 tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs delete mode 100644 tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index ba39a924e..42dce2def 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Memory /// It's public counterpart is , /// which only exposes the view of the MemoryGroup. /// - internal MemoryGroup FastMemoryGroup { get; } + internal MemoryGroup FastMemoryGroup { get; private set; } /// /// Gets a reference to the element at the specified position. @@ -168,25 +168,34 @@ namespace SixLabors.ImageSharp.Memory /// Swaps the contents of 'destination' with 'source' if the buffers are owned (1), /// copies the contents of 'source' to 'destination' otherwise (2). Buffers should be of same size in case 2! /// - internal static void SwapOrCopyContent(Buffer2D destination, Buffer2D source) + internal static bool SwapOrCopyContent(Buffer2D destination, Buffer2D source) { - MemoryGroup.SwapOrCopyContent(destination.FastMemoryGroup, source.FastMemoryGroup); - SwapOwnData(destination, source); + bool swapped = false; + if (MemoryGroup.CanSwapContent(destination.FastMemoryGroup, source.FastMemoryGroup)) + { + (destination.FastMemoryGroup, source.FastMemoryGroup) = + (source.FastMemoryGroup, destination.FastMemoryGroup); + destination.FastMemoryGroup.RecreateViewAfterSwap(); + source.FastMemoryGroup.RecreateViewAfterSwap(); + swapped = true; + } + else + { + if (destination.FastMemoryGroup.TotalLength != source.FastMemoryGroup.TotalLength) + { + throw new InvalidMemoryOperationException( + "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); + } + + source.FastMemoryGroup.CopyTo(destination.MemoryGroup); + } + + (destination.Width, source.Width) = (source.Width, destination.Width); + (destination.Height, source.Height) = (source.Height, destination.Height); + return swapped; } [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) - { - Size aSize = a.Size(); - Size bSize = b.Size(); - - b.Width = aSize.Width; - b.Height = aSize.Height; - - a.Width = bSize.Width; - a.Height = bSize.Height; - } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index 3b9241383..b578b417f 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -122,6 +122,12 @@ namespace SixLabors.ImageSharp.Memory } } + public override void RecreateViewAfterSwap() + { + this.View.Invalidate(); + this.View = new MemoryGroupView(this); + } + /// IEnumerator> IEnumerable>.GetEnumerator() { diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index cdd8e6a75..1f18d9169 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -241,30 +241,11 @@ namespace SixLabors.ImageSharp.Memory return new Owned(source, bufferLength, totalLength, false); } - /// - /// Swaps the contents of 'target' with 'source' if the buffers are allocated (1), - /// copies the contents of 'source' to 'target' otherwise (2). - /// Groups should be of same TotalLength in case 2. - /// - public static bool SwapOrCopyContent(MemoryGroup target, MemoryGroup source) - { - if (source is Owned ownedSrc && ownedSrc.Swappable && - target is Owned ownedTarget && ownedTarget.Swappable) - { - Owned.SwapContents(ownedTarget, ownedSrc); - return true; - } - else - { - if (target.TotalLength != source.TotalLength) - { - throw new InvalidMemoryOperationException( - "Trying to copy/swap incompatible buffers. This is most likely caused by applying an unsupported processor to wrapped-memory images."); - } + public static bool CanSwapContent(MemoryGroup target, MemoryGroup source) => + source is Owned { Swappable: true } && target is Owned { Swappable: true }; - source.CopyTo(target); - return false; - } + public virtual void RecreateViewAfterSwap() + { } public virtual void IncreaseRefCounts() diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs new file mode 100644 index 000000000..f58136f73 --- /dev/null +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs @@ -0,0 +1,160 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Linq; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Memory +{ + public partial class Buffer2DTests + { + public class SwapOrCopyContent + { + private readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); + + [Fact] + public void SwapOrCopyContent_WhenBothAllocated() + { + using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) + using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) + { + a[1, 3] = 666; + b[1, 3] = 444; + + Memory aa = a.FastMemoryGroup.Single(); + Memory bb = b.FastMemoryGroup.Single(); + + Buffer2D.SwapOrCopyContent(a, b); + + Assert.Equal(bb, a.FastMemoryGroup.Single()); + Assert.Equal(aa, b.FastMemoryGroup.Single()); + + Assert.Equal(new Size(3, 7), a.Size()); + Assert.Equal(new Size(10, 5), b.Size()); + + Assert.Equal(666, b[1, 3]); + Assert.Equal(444, a[1, 3]); + } + } + + [Fact] + public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() + { + using var destData = MemoryGroup.Wrap(new int[100]); + using var dest = new Buffer2D(destData, 10, 10); + + using (Buffer2D source = this.MemoryAllocator.Allocate2D(10, 10, AllocationOptions.Clean)) + { + source[0, 0] = 1; + dest[0, 0] = 2; + + Buffer2D.SwapOrCopyContent(dest, source); + } + + int actual1 = dest.DangerousGetRowSpan(0)[0]; + int actual2 = dest.DangerousGetRowSpan(0)[0]; + int actual3 = dest.GetSafeRowMemory(0).Span[0]; + int actual5 = dest[0, 0]; + + Assert.Equal(1, actual1); + Assert.Equal(1, actual2); + Assert.Equal(1, actual3); + Assert.Equal(1, actual5); + } + + [Fact] + public void WhenBothAreMemoryOwners_ShouldSwap() + { + this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; + using Buffer2D a = this.MemoryAllocator.Allocate2D(48, 2); + using Buffer2D b = this.MemoryAllocator.Allocate2D(50, 2); + + Memory a0 = a.FastMemoryGroup[0]; + Memory a1 = a.FastMemoryGroup[1]; + Memory b0 = b.FastMemoryGroup[0]; + Memory b1 = b.FastMemoryGroup[1]; + + bool swap = Buffer2D.SwapOrCopyContent(a, b); + Assert.True(swap); + + Assert.Equal(b0, a.FastMemoryGroup[0]); + Assert.Equal(b1, a.FastMemoryGroup[1]); + Assert.Equal(a0, b.FastMemoryGroup[0]); + Assert.Equal(a1, b.FastMemoryGroup[1]); + Assert.NotEqual(a.FastMemoryGroup[0], b.FastMemoryGroup[0]); + } + + [Fact] + public void WhenBothAreMemoryOwners_ShouldReplaceViews() + { + using Buffer2D a = this.MemoryAllocator.Allocate2D(100, 1); + using Buffer2D b = this.MemoryAllocator.Allocate2D(100, 2); + + a.FastMemoryGroup[0].Span[42] = 1; + b.FastMemoryGroup[0].Span[33] = 2; + MemoryGroupView aView0 = (MemoryGroupView)a.MemoryGroup; + MemoryGroupView bView0 = (MemoryGroupView)b.MemoryGroup; + + Buffer2D.SwapOrCopyContent(a, b); + Assert.False(aView0.IsValid); + Assert.False(bView0.IsValid); + Assert.ThrowsAny(() => _ = aView0[0].Span); + Assert.ThrowsAny(() => _ = bView0[0].Span); + + Assert.True(a.MemoryGroup.IsValid); + Assert.True(b.MemoryGroup.IsValid); + Assert.Equal(2, a.MemoryGroup[0].Span[33]); + Assert.Equal(1, b.MemoryGroup[0].Span[42]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + using var destOwner = new TestMemoryManager(data); + using var dest = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1); + + using Buffer2D source = this.MemoryAllocator.Allocate2D(21, 1); + + source.FastMemoryGroup[0].Span[10] = color; + + // Act: + bool swap = Buffer2D.SwapOrCopyContent(dest, source); + + // Assert: + Assert.False(swap); + Assert.Equal(color, dest.MemoryGroup[0].Span[10]); + Assert.NotEqual(source.FastMemoryGroup[0], dest.FastMemoryGroup[0]); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) + { + var data = new Rgba32[21]; + var color = new Rgba32(1, 2, 3, 4); + + using var destOwner = new TestMemoryManager(data); + using var dest = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1); + + using Buffer2D source = this.MemoryAllocator.Allocate2D(22, 1); + + source.FastMemoryGroup[0].Span[10] = color; + + // Act: + Assert.ThrowsAny(() => Buffer2D.SwapOrCopyContent(dest, source)); + + Assert.Equal(color, source.MemoryGroup[0].Span[10]); + Assert.NotEqual(color, dest.MemoryGroup[0].Span[10]); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 73a0f4d60..72b4ccadf 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -4,7 +4,6 @@ using System; using System.Buffers; using System.Diagnostics; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; @@ -14,7 +13,7 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Memory { - public class Buffer2DTests + public partial class Buffer2DTests { // ReSharper disable once ClassNeverInstantiated.Local private class Assert : Xunit.Assert @@ -229,56 +228,6 @@ namespace SixLabors.ImageSharp.Tests.Memory } } - [Fact] - public void SwapOrCopyContent_WhenBothAllocated() - { - using (Buffer2D a = this.MemoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean)) - using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean)) - { - a[1, 3] = 666; - b[1, 3] = 444; - - Memory aa = a.FastMemoryGroup.Single(); - Memory bb = b.FastMemoryGroup.Single(); - - Buffer2D.SwapOrCopyContent(a, b); - - Assert.Equal(bb, a.FastMemoryGroup.Single()); - Assert.Equal(aa, b.FastMemoryGroup.Single()); - - Assert.Equal(new Size(3, 7), a.Size()); - Assert.Equal(new Size(10, 5), b.Size()); - - Assert.Equal(666, b[1, 3]); - Assert.Equal(444, a[1, 3]); - } - } - - [Fact] - public void SwapOrCopyContent_WhenDestinationIsOwned_ShouldNotSwapInDisposedSourceBuffer() - { - using var destData = MemoryGroup.Wrap(new int[100]); - using var dest = new Buffer2D(destData, 10, 10); - - using (Buffer2D source = this.MemoryAllocator.Allocate2D(10, 10, AllocationOptions.Clean)) - { - source[0, 0] = 1; - dest[0, 0] = 2; - - Buffer2D.SwapOrCopyContent(dest, source); - } - - int actual1 = dest.DangerousGetRowSpan(0)[0]; - int actual2 = dest.DangerousGetRowSpan(0)[0]; - int actual3 = dest.GetSafeRowMemory(0).Span[0]; - int actual5 = dest[0, 0]; - - Assert.Equal(1, actual1); - Assert.Equal(1, actual2); - Assert.Equal(1, actual3); - Assert.Equal(1, actual5); - } - [Theory] [InlineData(100, 20, 0, 90, 10)] [InlineData(100, 3, 0, 50, 50)] diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs deleted file mode 100644 index 61b9f7a89..000000000 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.SwapOrCopyContent.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using Xunit; - -namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers -{ - public partial class MemoryGroupTests - { - public class SwapOrCopyContent : MemoryGroupTestsBase - { - [Fact] - public void WhenBothAreMemoryOwners_ShouldSwap() - { - this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; - using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 50); - using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 50); - - Memory a0 = a[0]; - Memory a1 = a[1]; - Memory b0 = b[0]; - Memory b1 = b[1]; - - bool swap = MemoryGroup.SwapOrCopyContent(a, b); - - Assert.True(swap); - Assert.Equal(b0, a[0]); - Assert.Equal(b1, a[1]); - Assert.Equal(a0, b[0]); - Assert.Equal(a1, b[1]); - Assert.NotEqual(a[0], b[0]); - } - - [Fact] - public void WhenBothAreMemoryOwners_ShouldReplaceViews() - { - using MemoryGroup a = this.MemoryAllocator.AllocateGroup(100, 100); - using MemoryGroup b = this.MemoryAllocator.AllocateGroup(120, 100); - - a[0].Span[42] = 1; - b[0].Span[33] = 2; - MemoryGroupView aView0 = a.View; - MemoryGroupView bView0 = b.View; - - MemoryGroup.SwapOrCopyContent(a, b); - Assert.False(aView0.IsValid); - Assert.False(bView0.IsValid); - Assert.ThrowsAny(() => _ = aView0[0].Span); - Assert.ThrowsAny(() => _ = bView0[0].Span); - - Assert.True(a.View.IsValid); - Assert.True(b.View.IsValid); - Assert.Equal(2, a.View[0].Span[33]); - Assert.Equal(1, b.View[0].Span[42]); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenDestIsNotAllocated_SameSize_ShouldCopy(bool sourceIsAllocated) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - using var destOwner = new TestMemoryManager(data); - using var dest = MemoryGroup.Wrap(destOwner.Memory); - - using MemoryGroup source = this.MemoryAllocator.AllocateGroup(21, 30); - - source[0].Span[10] = color; - - // Act: - bool swap = MemoryGroup.SwapOrCopyContent(dest, source); - - // Assert: - Assert.False(swap); - Assert.Equal(color, dest[0].Span[10]); - Assert.NotEqual(source[0], dest[0]); - } - - [Theory] - [InlineData(false)] - [InlineData(true)] - public void WhenDestIsNotMemoryOwner_DifferentSize_Throws(bool sourceIsOwner) - { - var data = new Rgba32[21]; - var color = new Rgba32(1, 2, 3, 4); - - using var destOwner = new TestMemoryManager(data); - var dest = MemoryGroup.Wrap(destOwner.Memory); - - using MemoryGroup source = this.MemoryAllocator.AllocateGroup(22, 30); - - source[0].Span[10] = color; - - // Act: - Assert.ThrowsAny(() => MemoryGroup.SwapOrCopyContent(dest, source)); - - Assert.Equal(color, source[0].Span[10]); - Assert.NotEqual(color, dest[0].Span[10]); - } - } - } -} From 3f4e05c811fe272439609f765b460d8e2236713e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sun, 19 Dec 2021 21:57:57 +0100 Subject: [PATCH 415/511] Buffer2D_DangerousGetRowSpan benchmark --- .../General/Buffer2D_DangerousGetRowSpan.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs diff --git a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs new file mode 100644 index 000000000..84f035583 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Benchmarks.General +{ + public class Buffer2D_DangerousGetRowSpan + { + [Params(true, false)] + public bool IsDiscontiguousBuffer { get; set; } + + private Buffer2D buffer; + + [GlobalSetup] + public void Setup() + { + MemoryAllocator allocator = Configuration.Default.MemoryAllocator; + this.buffer = this.IsDiscontiguousBuffer + ? allocator.Allocate2D(4000, 1000) + : allocator.Allocate2D(500, 1000); + } + + [GlobalCleanup] + public void Cleanup() => this.buffer.Dispose(); + + [Benchmark] + public int DangerousGetRowSpan() => + this.buffer.DangerousGetRowSpan(1).Length + + this.buffer.DangerousGetRowSpan(999).Length; + + // BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 + // Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores + // + // | Method | IsDiscontiguousBuffer | Mean | Error | StdDev | + // |-------------------- |---------------------- |---------:|---------:|---------:| + // | DangerousGetRowSpan | False | 74.96 ns | 1.505 ns | 1.478 ns | + // | DangerousGetRowSpan | True | 71.49 ns | 1.446 ns | 2.120 ns | + } +} From 0f7f1373fceb388369faa5433e57bbd041c3b06d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 01:19:31 +0100 Subject: [PATCH 416/511] Speed up DangerousGetRowSpan with SpanCache --- .../Internals/SharedArrayPoolBuffer{T}.cs | 17 +++--- .../Internals/UniformUnmanagedMemoryPool.cs | 10 ++-- .../Internals/UnmanagedBuffer{T}.cs | 2 +- src/ImageSharp/Memory/Buffer2D{T}.cs | 18 ++++--- .../MemoryGroupExtensions.cs | 2 +- .../MemoryGroupSpanCache.cs | 50 +++++++++++++++++ .../MemoryGroup{T}.Owned.cs | 27 +--------- .../DiscontiguousBuffers/MemoryGroup{T}.cs | 50 ++++++++++++++++- .../DiscontiguousBuffers/SpanCacheMode.cs | 13 +++++ .../General/Buffer2D_DangerousGetRowSpan.cs | 18 ++++--- .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 53 ++++++++++++++++++- .../DiscontiguousBuffers/MemoryGroupTests.cs | 4 +- 12 files changed, 203 insertions(+), 61 deletions(-) create mode 100644 src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs create mode 100644 src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs diff --git a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs index 21673215a..9302c67e7 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/SharedArrayPoolBuffer{T}.cs @@ -13,34 +13,35 @@ namespace SixLabors.ImageSharp.Memory.Internals 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); + this.Array = ArrayPool.Shared.Rent(this.lengthInBytes); + this.lifetimeGuard = new LifetimeGuard(this.Array); } + public byte[] Array { get; private set; } + protected override void Dispose(bool disposing) { - if (this.array == null) + if (this.Array == null) { return; } this.lifetimeGuard.Dispose(); - this.array = null; + this.Array = null; } public override Span GetSpan() { this.CheckDisposed(); - return MemoryMarshal.Cast(this.array.AsSpan(0, this.lengthInBytes)); + return MemoryMarshal.Cast(this.Array.AsSpan(0, this.lengthInBytes)); } - protected override object GetPinnableObject() => this.array; + protected override object GetPinnableObject() => this.Array; public void AddRef() { @@ -53,7 +54,7 @@ namespace SixLabors.ImageSharp.Memory.Internals [Conditional("DEBUG")] private void CheckDisposed() { - if (this.array == null) + if (this.Array == null) { throw new ObjectDisposedException("SharedArrayPoolBuffer"); } diff --git a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs index 6504787a8..584de4464 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UniformUnmanagedMemoryPool.cs @@ -8,12 +8,10 @@ using System.Threading; namespace SixLabors.ImageSharp.Memory.Internals { - internal partial class UniformUnmanagedMemoryPool -#if !NETSTANDARD1_3 - // 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 + // CriticalFinalizerObject: + // In case UniformUnmanagedMemoryPool is finalized, we prefer to run its finalizer after the guard finalizers, + // but we should not rely on this. + internal partial class UniformUnmanagedMemoryPool : System.Runtime.ConstrainedExecution.CriticalFinalizerObject { private static int minTrimPeriodMilliseconds = int.MaxValue; private static readonly List> AllPools = new(); diff --git a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs index 5d0c6dd61..a948bf848 100644 --- a/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs +++ b/src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs @@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Memory.Internals this.lifetimeGuard = lifetimeGuard; } - private void* Pointer => this.lifetimeGuard.Handle.Pointer; + public void* Pointer => this.lifetimeGuard.Handle.Pointer; public override Span GetSpan() { diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 42dce2def..ea4b8031e 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -97,10 +97,12 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(InliningOptions.ShortMethod)] public Span DangerousGetRowSpan(int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); - DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); + if (y < 0 || y >= this.Height) + { + this.ThrowYOutOfRangeException(y); + } - return this.GetRowMemoryCore(y).Span; + return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); } internal bool TryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) @@ -125,7 +127,7 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(InliningOptions.ShortMethod)] internal ref T GetElementUnsafe(int x, int y) { - Span span = this.GetRowMemoryCore(y).Span; + Span span = this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); return ref span[x]; } @@ -139,7 +141,7 @@ namespace SixLabors.ImageSharp.Memory { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); - return this.FastMemoryGroup.View.GetBoundedSlice(y * (long)this.Width, this.Width); + return this.FastMemoryGroup.View.GetBoundedMemorySlice(y * (long)this.Width, this.Width); } /// @@ -195,7 +197,9 @@ namespace SixLabors.ImageSharp.Memory return swapped; } - [MethodImpl(InliningOptions.ShortMethod)] - private Memory GetRowMemoryCore(int y) => this.FastMemoryGroup.GetBoundedSlice(y * (long)this.Width, this.Width); + [MethodImpl(InliningOptions.ColdPath)] + private void ThrowYOutOfRangeException(int y) => + throw new ArgumentOutOfRangeException( + $"DangerousGetRowSpan({y}). Y was out of range. Height={this.Height}"); } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 8e6f38d14..571ffc98d 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Memory /// Returns a slice that is expected to be within the bounds of a single buffer. /// Otherwise is thrown. /// - internal static Memory GetBoundedSlice(this IMemoryGroup group, long start, int length) + internal static Memory GetBoundedMemorySlice(this IMemoryGroup group, long start, int length) where T : struct { Guard.NotNull(group, nameof(group)); diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs new file mode 100644 index 000000000..fbaf2dcf0 --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs @@ -0,0 +1,50 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Buffers; +using SixLabors.ImageSharp.Memory.Internals; + +namespace SixLabors.ImageSharp.Memory +{ + internal unsafe struct MemoryGroupSpanCache + { + public SpanCacheMode Mode; + public byte[] SingleArray; + public void* SinglePointer; + public void*[] MultiPointer; + + public static MemoryGroupSpanCache Create(IMemoryOwner[] memoryOwners) + where T : struct + { + IMemoryOwner owner0 = memoryOwners[0]; + MemoryGroupSpanCache memoryGroupSpanCache = default; + if (memoryOwners.Length == 1) + { + if (owner0 is SharedArrayPoolBuffer sharedPoolBuffer) + { + memoryGroupSpanCache.Mode = SpanCacheMode.SingleArray; + memoryGroupSpanCache.SingleArray = sharedPoolBuffer.Array; + } + else if (owner0 is UnmanagedBuffer unmanagedBuffer) + { + memoryGroupSpanCache.Mode = SpanCacheMode.SinglePointer; + memoryGroupSpanCache.SinglePointer = unmanagedBuffer.Pointer; + } + } + else + { + if (owner0 is UnmanagedBuffer) + { + memoryGroupSpanCache.Mode = SpanCacheMode.MultiPointer; + memoryGroupSpanCache.MultiPointer = new void*[memoryOwners.Length]; + for (int i = 0; i < memoryOwners.Length; i++) + { + memoryGroupSpanCache.MultiPointer[i] = ((UnmanagedBuffer)memoryOwners[i]).Pointer; + } + } + } + + return memoryGroupSpanCache; + } + } +} diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs index b578b417f..21faf8e56 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.Owned.cs @@ -26,6 +26,7 @@ namespace SixLabors.ImageSharp.Memory this.memoryOwners = memoryOwners; this.Swappable = swappable; this.View = new MemoryGroupView(this); + this.memoryGroupSpanCache = MemoryGroupSpanCache.Create(memoryOwners); } public Owned( @@ -173,32 +174,6 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(MethodImplOptions.NoInlining)] private static void ThrowObjectDisposedException() => throw new ObjectDisposedException(nameof(MemoryGroup)); - internal static void SwapContents(Owned a, Owned b) - { - a.EnsureNotDisposed(); - b.EnsureNotDisposed(); - - IMemoryOwner[] tempOwners = a.memoryOwners; - long tempTotalLength = a.TotalLength; - int tempBufferLength = a.BufferLength; - RefCountedLifetimeGuard tempGroupOwner = a.groupLifetimeGuard; - - a.memoryOwners = b.memoryOwners; - a.TotalLength = b.TotalLength; - a.BufferLength = b.BufferLength; - a.groupLifetimeGuard = b.groupLifetimeGuard; - - b.memoryOwners = tempOwners; - b.TotalLength = tempTotalLength; - b.BufferLength = tempBufferLength; - b.groupLifetimeGuard = tempGroupOwner; - - a.View.Invalidate(); - b.View.Invalidate(); - a.View = new MemoryGroupView(a); - b.View = new MemoryGroupView(b); - } - // 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. diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 1f18d9169..a324f55e4 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -6,6 +6,8 @@ using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory @@ -21,6 +23,8 @@ namespace SixLabors.ImageSharp.Memory { private static readonly int ElementSize = Unsafe.SizeOf(); + private MemoryGroupSpanCache memoryGroupSpanCache; + private MemoryGroup(int bufferLength, long totalLength) { this.BufferLength = bufferLength; @@ -31,10 +35,10 @@ namespace SixLabors.ImageSharp.Memory public abstract int Count { get; } /// - public int BufferLength { get; private set; } + public int BufferLength { get; } /// - public long TotalLength { get; private set; } + public long TotalLength { get; } /// public bool IsValid { get; private set; } = true; @@ -241,6 +245,40 @@ namespace SixLabors.ImageSharp.Memory return new Owned(source, bufferLength, totalLength, false); } + [MethodImpl(InliningOptions.ShortMethod)] + public unsafe Span GetRowSpanCoreUnsafe(int y, int width) + { + switch (this.memoryGroupSpanCache.Mode) + { + case SpanCacheMode.SingleArray: + { + ref byte b0 = ref MemoryMarshal.GetReference(this.memoryGroupSpanCache.SingleArray); + ref T e0 = ref Unsafe.As(ref b0); + e0 = ref Unsafe.Add(ref e0, y * width); + return MemoryMarshal.CreateSpan(ref e0, width); + } + + case SpanCacheMode.SinglePointer: + { + void* start = Unsafe.Add(this.memoryGroupSpanCache.SinglePointer, y * width); + return new Span(start, width); + } + + case SpanCacheMode.MultiPointer: + { + this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); + void* start = Unsafe.Add(this.memoryGroupSpanCache.MultiPointer[bufferIdx], bufferStart); + return new Span(start, width); + } + + default: + { + this.GetMultiBufferPosition(y, width, out int bufferIdx, out int bufferStart); + return this[bufferIdx].Span.Slice(bufferStart, width); + } + } + } + public static bool CanSwapContent(MemoryGroup target, MemoryGroup source) => source is Owned { Swappable: true } && target is Owned { Swappable: true }; @@ -255,5 +293,13 @@ namespace SixLabors.ImageSharp.Memory public virtual void DecreaseRefCounts() { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void GetMultiBufferPosition(int y, int width, out int bufferIdx, out int bufferStart) + { + long start = y * (long)width; + bufferIdx = (int)(start / this.BufferLength); + bufferStart = (int)(start % this.BufferLength); + } } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs new file mode 100644 index 000000000..6ab8db0ca --- /dev/null +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs @@ -0,0 +1,13 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +namespace SixLabors.ImageSharp.Memory +{ + internal enum SpanCacheMode + { + Default = default, + SingleArray, + SinglePointer, + MultiPointer + } +} diff --git a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs index 84f035583..1a130147a 100644 --- a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs +++ b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System.Runtime.CompilerServices; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; @@ -9,18 +10,21 @@ namespace SixLabors.ImageSharp.Benchmarks.General { public class Buffer2D_DangerousGetRowSpan { - [Params(true, false)] - public bool IsDiscontiguousBuffer { get; set; } + private const int Height = 1024; + + [Params(0.5, 2.0, 10.0)] + public double SizeMegaBytes { get; set; } private Buffer2D buffer; [GlobalSetup] - public void Setup() + public unsafe void Setup() { + int totalElements = (int)(1024 * 1024 * this.SizeMegaBytes) / sizeof(Rgba32); + + int width = totalElements / Height; MemoryAllocator allocator = Configuration.Default.MemoryAllocator; - this.buffer = this.IsDiscontiguousBuffer - ? allocator.Allocate2D(4000, 1000) - : allocator.Allocate2D(500, 1000); + this.buffer = allocator.Allocate2D(width, Height); } [GlobalCleanup] @@ -29,7 +33,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General [Benchmark] public int DangerousGetRowSpan() => this.buffer.DangerousGetRowSpan(1).Length + - this.buffer.DangerousGetRowSpan(999).Length; + this.buffer.DangerousGetRowSpan(Height - 1).Length; // BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 // Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 72b4ccadf..0fee2c878 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -120,7 +120,7 @@ namespace SixLabors.ImageSharp.Tests.Memory [InlineData(200, 100, 30, 1, 0)] [InlineData(200, 100, 30, 2, 1)] [InlineData(200, 100, 30, 4, 2)] - public unsafe void GetRowSpanY(int bufferCapacity, int width, int height, int y, int expectedBufferIndex) + public unsafe void DangerousGetRowSpan_TestAllocator(int bufferCapacity, int width, int height, int y, int expectedBufferIndex) { this.MemoryAllocator.BufferCapacityInBytes = sizeof(TestStructs.Foo) * bufferCapacity; @@ -135,6 +135,57 @@ namespace SixLabors.ImageSharp.Tests.Memory } } + [Theory] + [InlineData(100, 5)] // Within shared pool + [InlineData(77, 11)] // Within shared pool + [InlineData(100, 19)] // Single unmanaged pooled buffer + [InlineData(103, 17)] // Single unmanaged pooled buffer + [InlineData(100, 22)] // 2 unmanaged pooled buffers + [InlineData(100, 99)] // 9 unmanaged pooled buffers + [InlineData(100, 120)] // 2 unpooled buffers + public unsafe void DangerousGetRowSpan_UnmanagedAllocator(int width, int height) + { + const int sharedPoolThreshold = 1_000; + const int poolBufferSize = 2_000; + const int maxPoolSize = 10_000; + const int unpooledBufferSize = 8_000; + + int elementSize = sizeof(TestStructs.Foo); + var allocator = new UniformUnmanagedMemoryPoolMemoryAllocator( + sharedPoolThreshold * elementSize, + poolBufferSize * elementSize, + maxPoolSize * elementSize, + unpooledBufferSize * elementSize); + + using Buffer2D buffer = allocator.Allocate2D(width, height); + + var rnd = new Random(42); + + for (int y = 0; y < buffer.Height; y++) + { + Span span = buffer.DangerousGetRowSpan(y); + for (int x = 0; x < span.Length; x++) + { + ref TestStructs.Foo e = ref span[x]; + e.A = rnd.Next(); + e.B = rnd.NextDouble(); + } + } + + // Re-seed + rnd = new Random(42); + for (int y = 0; y < buffer.Height; y++) + { + Span span = buffer.GetSafeRowMemory(y).Span; + for (int x = 0; x < span.Length; x++) + { + ref TestStructs.Foo e = ref span[x]; + Assert.True(rnd.Next() == e.A, $"Mismatch @ y={y} x={x}"); + Assert.True(rnd.NextDouble() == e.B, $"Mismatch @ y={y} x={x}"); + } + } + } + [Theory] [InlineData(10, 0, 0, 0)] [InlineData(10, 0, 2, 0)] diff --git a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs index 13e47bdee..cb6c34b77 100644 --- a/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs +++ b/tests/ImageSharp.Tests/Memory/DiscontiguousBuffers/MemoryGroupTests.cs @@ -146,7 +146,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers { using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); - Memory slice = group.GetBoundedSlice(start, length); + Memory slice = group.GetBoundedMemorySlice(start, length); Assert.Equal(length, slice.Length); @@ -172,7 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Memory.DiscontiguousBuffers public void GetBoundedSlice_WhenOverlapsBuffers_Throws(long totalLength, int bufferLength, long start, int length) { using MemoryGroup group = this.CreateTestGroup(totalLength, bufferLength, true); - Assert.ThrowsAny(() => group.GetBoundedSlice(start, length)); + Assert.ThrowsAny(() => group.GetBoundedMemorySlice(start, length)); } [Fact] From 8d651ebc710b7fede6633cb4ebb240fba4972804 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 01:45:15 +0100 Subject: [PATCH 417/511] simplify TryGetPaddedRowSpan() --- .../Decoder/SpectralConverter{TPixel}.cs | 2 +- src/ImageSharp/Memory/Buffer2D{T}.cs | 8 +++--- .../MemoryGroupExtensions.cs | 25 ------------------- .../MemoryGroupSpanCache.cs | 18 +++++++------ .../DiscontiguousBuffers/MemoryGroup{T}.cs | 11 ++++++++ .../ImageSharp.Tests/Memory/Buffer2DTests.cs | 2 +- 6 files changed, 27 insertions(+), 39 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs index 87f11a085..40411ef28 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs @@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder // PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row. // If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row, // pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row. - if (this.pixelBuffer.TryGetPaddedRowSpan(yy, 3, out Span destRow)) + if (this.pixelBuffer.DangerousTryGetPaddedRowSpan(yy, 3, out Span destRow)) { PixelOperations.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow); } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index ea4b8031e..03d708d75 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -105,22 +105,22 @@ namespace SixLabors.ImageSharp.Memory return this.FastMemoryGroup.GetRowSpanCoreUnsafe(y, this.Width); } - internal bool TryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) + internal bool DangerousTryGetPaddedRowSpan(int y, int padding, out Span paddedSpan) { DebugGuard.MustBeGreaterThanOrEqualTo(y, 0, nameof(y)); DebugGuard.MustBeLessThan(y, this.Height, nameof(y)); int stride = this.Width + padding; - Memory memory = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); + Span slice = this.FastMemoryGroup.GetRemainingSliceOfBuffer(y * (long)this.Width); - if (memory.Length < stride) + if (slice.Length < stride) { paddedSpan = default; return false; } - paddedSpan = memory.Span.Slice(0, stride); + paddedSpan = slice.Slice(0, stride); return true; } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 571ffc98d..0df1080e5 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -57,31 +57,6 @@ namespace SixLabors.ImageSharp.Memory return memory.Slice(bufferStart, length); } - /// - /// Returns the slice of the buffer starting at global index that goes until the end of the buffer. - /// - internal static Memory GetRemainingSliceOfBuffer(this IMemoryGroup group, long start) - where T : struct - { - Guard.NotNull(group, nameof(group)); - Guard.IsTrue(group.IsValid, nameof(group), "Group must be valid!"); - Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); - - int bufferIdx = (int)(start / group.BufferLength); - - // if (bufferIdx < 0 || bufferIdx >= group.Count) - if ((uint)bufferIdx >= group.Count) - { - throw new ArgumentOutOfRangeException(nameof(start)); - } - - int bufferStart = (int)(start % group.BufferLength); - - Memory memory = group[bufferIdx]; - - return memory.Slice(bufferStart); - } - internal static void CopyTo(this IMemoryGroup source, Span target) where T : struct { diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs index fbaf2dcf0..2ec8f97e1 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs @@ -1,11 +1,16 @@ // 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 { + /// + /// Cached pointer or array data enabling fast from + /// known implementations. + /// internal unsafe struct MemoryGroupSpanCache { public SpanCacheMode Mode; @@ -31,16 +36,13 @@ namespace SixLabors.ImageSharp.Memory memoryGroupSpanCache.SinglePointer = unmanagedBuffer.Pointer; } } - else + else if (owner0 is UnmanagedBuffer) { - if (owner0 is UnmanagedBuffer) + memoryGroupSpanCache.Mode = SpanCacheMode.MultiPointer; + memoryGroupSpanCache.MultiPointer = new void*[memoryOwners.Length]; + for (int i = 0; i < memoryOwners.Length; i++) { - memoryGroupSpanCache.Mode = SpanCacheMode.MultiPointer; - memoryGroupSpanCache.MultiPointer = new void*[memoryOwners.Length]; - for (int i = 0; i < memoryOwners.Length; i++) - { - memoryGroupSpanCache.MultiPointer[i] = ((UnmanagedBuffer)memoryOwners[i]).Pointer; - } + memoryGroupSpanCache.MultiPointer[i] = ((UnmanagedBuffer)memoryOwners[i]).Pointer; } } diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index a324f55e4..560f4d4b4 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -279,6 +279,17 @@ namespace SixLabors.ImageSharp.Memory } } + /// + /// Returns the slice of the buffer starting at global index that goes until the end of the buffer. + /// + public Span GetRemainingSliceOfBuffer(long start) + { + int bufferIdx = (int)(start / this.BufferLength); + int bufferStart = (int)(start % this.BufferLength); + Memory memory = this[bufferIdx]; + return memory.Span.Slice(bufferStart); + } + public static bool CanSwapContent(MemoryGroup target, MemoryGroup source) => source is Owned { Swappable: true } && target is Owned { Swappable: true }; diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs index 0fee2c878..486fbe464 100644 --- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs +++ b/tests/ImageSharp.Tests/Memory/Buffer2DTests.cs @@ -203,7 +203,7 @@ namespace SixLabors.ImageSharp.Tests.Memory using Buffer2D buffer = this.MemoryAllocator.Allocate2D(3, 5); bool expectSuccess = expectedBufferIndex >= 0; - bool success = buffer.TryGetPaddedRowSpan(y, padding, out Span paddedSpan); + bool success = buffer.DangerousTryGetPaddedRowSpan(y, padding, out Span paddedSpan); Xunit.Assert.Equal(expectSuccess, success); if (success) { From bce450ce257b8bf00a1c044277c67eee969239a0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 01:53:09 +0100 Subject: [PATCH 418/511] watch SUPPORTS_CREATESPAN --- src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 560f4d4b4..153dd05e5 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -252,10 +252,15 @@ namespace SixLabors.ImageSharp.Memory { case SpanCacheMode.SingleArray: { +#if SUPPORTS_CREATESPAN ref byte b0 = ref MemoryMarshal.GetReference(this.memoryGroupSpanCache.SingleArray); ref T e0 = ref Unsafe.As(ref b0); e0 = ref Unsafe.Add(ref e0, y * width); return MemoryMarshal.CreateSpan(ref e0, width); +#else + return MemoryMarshal.Cast(this.memoryGroupSpanCache.SingleArray).Slice(y * width, width); +#endif + } case SpanCacheMode.SinglePointer: From d9f4dc607b44946a085211ff7eef9f761b38d17f Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 02:01:31 +0100 Subject: [PATCH 419/511] update benchmark results --- .../General/Buffer2D_DangerousGetRowSpan.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs index 1a130147a..87fa85250 100644 --- a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs +++ b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs @@ -38,9 +38,10 @@ namespace SixLabors.ImageSharp.Benchmarks.General // BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19044 // Intel Core i9-10900X CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores // - // | Method | IsDiscontiguousBuffer | Mean | Error | StdDev | - // |-------------------- |---------------------- |---------:|---------:|---------:| - // | DangerousGetRowSpan | False | 74.96 ns | 1.505 ns | 1.478 ns | - // | DangerousGetRowSpan | True | 71.49 ns | 1.446 ns | 2.120 ns | + // | Method | SizeMegaBytes | Mean | Error | StdDev | + // |-------------------- |-------------- |----------:|----------:|----------:| + // | DangerousGetRowSpan | 0.5 | 7.760 ns | 0.0444 ns | 0.0347 ns | + // | DangerousGetRowSpan | 2 | 6.551 ns | 0.0693 ns | 0.0579 ns | + // | DangerousGetRowSpan | 10 | 45.839 ns | 0.9090 ns | 1.0468 ns | } } From ace4fbc229e98f29fb84576b4e8e020f793a8550 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 02:19:13 +0100 Subject: [PATCH 420/511] Numerics.IsOutOfRangeZeroToMax --- src/ImageSharp/Common/Helpers/Numerics.cs | 10 ++++++++++ src/ImageSharp/ImageFrame{TPixel}.cs | 4 ++-- src/ImageSharp/Image{TPixel}.cs | 4 ++-- src/ImageSharp/Memory/Buffer2D{T}.cs | 2 +- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index 8f82476e1..f88c54f0d 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -970,7 +970,17 @@ namespace SixLabors.ImageSharp /// Value. /// Mininum value, inclusive. /// Maximum value, inclusive. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsOutOfRange(int value, int min, int max) => (uint)(value - min) > (uint)(max - min); + + /// + /// Tells whether input value is outside of the 0..max range. + /// + /// Value. + /// Maximum value, inclusive. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsOutOfRangeZeroToMax(int value, int max) + => (uint)value > (uint)max; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index 4753e90e0..fe026b3eb 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -443,12 +443,12 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] private void VerifyCoords(int x, int y) { - if (x < 0 || x >= this.Width) + if (Numerics.IsOutOfRangeZeroToMax(x, this.Width)) { ThrowArgumentOutOfRangeException(nameof(x)); } - if (y < 0 || y >= this.Height) + if (Numerics.IsOutOfRangeZeroToMax(y, this.Height)) { ThrowArgumentOutOfRangeException(nameof(y)); } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index d01706b01..b25fa0d6a 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -452,12 +452,12 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] private void VerifyCoords(int x, int y) { - if (x < 0 || x >= this.Width) + if (Numerics.IsOutOfRangeZeroToMax(x, this.Width)) { ThrowArgumentOutOfRangeException(nameof(x)); } - if (y < 0 || y >= this.Height) + if (Numerics.IsOutOfRangeZeroToMax(y, this.Height)) { ThrowArgumentOutOfRangeException(nameof(y)); } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 03d708d75..bc97add5c 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(InliningOptions.ShortMethod)] public Span DangerousGetRowSpan(int y) { - if (y < 0 || y >= this.Height) + if (Numerics.IsOutOfRangeZeroToMax(y, this.Height)) { this.ThrowYOutOfRangeException(y); } From 4c0dde1b815e2fd86f9d17213a138c69e526346d Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 02:29:23 +0100 Subject: [PATCH 421/511] fix range check --- src/ImageSharp/Common/Helpers/Numerics.cs | 6 +++--- src/ImageSharp/ImageFrame{TPixel}.cs | 4 ++-- src/ImageSharp/Image{TPixel}.cs | 4 ++-- src/ImageSharp/Memory/Buffer2D{T}.cs | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index f88c54f0d..f271f6e5d 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -978,9 +978,9 @@ namespace SixLabors.ImageSharp /// Tells whether input value is outside of the 0..max range. /// /// Value. - /// Maximum value, inclusive. + /// Maximum value, exclusive. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsOutOfRangeZeroToMax(int value, int max) - => (uint)value > (uint)max; + public static bool IsOutOfRangeZeroToExclusiveMax(int value, int max) + => (uint)value >= (uint)max; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index fe026b3eb..b43cf7a82 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -443,12 +443,12 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] private void VerifyCoords(int x, int y) { - if (Numerics.IsOutOfRangeZeroToMax(x, this.Width)) + if (Numerics.IsOutOfRangeZeroToExclusiveMax(x, this.Width)) { ThrowArgumentOutOfRangeException(nameof(x)); } - if (Numerics.IsOutOfRangeZeroToMax(y, this.Height)) + if (Numerics.IsOutOfRangeZeroToExclusiveMax(y, this.Height)) { ThrowArgumentOutOfRangeException(nameof(y)); } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index b25fa0d6a..e1016aa97 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -452,12 +452,12 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] private void VerifyCoords(int x, int y) { - if (Numerics.IsOutOfRangeZeroToMax(x, this.Width)) + if (Numerics.IsOutOfRangeZeroToExclusiveMax(x, this.Width)) { ThrowArgumentOutOfRangeException(nameof(x)); } - if (Numerics.IsOutOfRangeZeroToMax(y, this.Height)) + if (Numerics.IsOutOfRangeZeroToExclusiveMax(y, this.Height)) { ThrowArgumentOutOfRangeException(nameof(y)); } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index bc97add5c..0d9d444e0 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(InliningOptions.ShortMethod)] public Span DangerousGetRowSpan(int y) { - if (Numerics.IsOutOfRangeZeroToMax(y, this.Height)) + if (Numerics.IsOutOfRangeZeroToExclusiveMax(y, this.Height)) { this.ThrowYOutOfRangeException(y); } From b0a45f6c08f830273a65ea5a0609c82dafe5c4c0 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 02:44:03 +0100 Subject: [PATCH 422/511] inline IsOutOfRangeZeroToExclusiveMax --- src/ImageSharp/Common/Helpers/Numerics.cs | 9 --------- src/ImageSharp/ImageFrame{TPixel}.cs | 4 ++-- src/ImageSharp/Image{TPixel}.cs | 4 ++-- src/ImageSharp/Memory/Buffer2D{T}.cs | 2 +- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Common/Helpers/Numerics.cs b/src/ImageSharp/Common/Helpers/Numerics.cs index f271f6e5d..7de838bc9 100644 --- a/src/ImageSharp/Common/Helpers/Numerics.cs +++ b/src/ImageSharp/Common/Helpers/Numerics.cs @@ -973,14 +973,5 @@ namespace SixLabors.ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsOutOfRange(int value, int min, int max) => (uint)(value - min) > (uint)(max - min); - - /// - /// Tells whether input value is outside of the 0..max range. - /// - /// Value. - /// Maximum value, exclusive. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsOutOfRangeZeroToExclusiveMax(int value, int max) - => (uint)value >= (uint)max; } } diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index b43cf7a82..cc1b01ff7 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -443,12 +443,12 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] private void VerifyCoords(int x, int y) { - if (Numerics.IsOutOfRangeZeroToExclusiveMax(x, this.Width)) + if ((uint)x >= (uint)this.Width) { ThrowArgumentOutOfRangeException(nameof(x)); } - if (Numerics.IsOutOfRangeZeroToExclusiveMax(y, this.Height)) + if ((uint)y >= (uint)this.Height) { ThrowArgumentOutOfRangeException(nameof(y)); } diff --git a/src/ImageSharp/Image{TPixel}.cs b/src/ImageSharp/Image{TPixel}.cs index e1016aa97..403a0f7ea 100644 --- a/src/ImageSharp/Image{TPixel}.cs +++ b/src/ImageSharp/Image{TPixel}.cs @@ -452,12 +452,12 @@ namespace SixLabors.ImageSharp [MethodImpl(InliningOptions.ShortMethod)] private void VerifyCoords(int x, int y) { - if (Numerics.IsOutOfRangeZeroToExclusiveMax(x, this.Width)) + if ((uint)x >= (uint)this.Width) { ThrowArgumentOutOfRangeException(nameof(x)); } - if (Numerics.IsOutOfRangeZeroToExclusiveMax(y, this.Height)) + if ((uint)y >= (uint)this.Height) { ThrowArgumentOutOfRangeException(nameof(y)); } diff --git a/src/ImageSharp/Memory/Buffer2D{T}.cs b/src/ImageSharp/Memory/Buffer2D{T}.cs index 0d9d444e0..7ffaae312 100644 --- a/src/ImageSharp/Memory/Buffer2D{T}.cs +++ b/src/ImageSharp/Memory/Buffer2D{T}.cs @@ -97,7 +97,7 @@ namespace SixLabors.ImageSharp.Memory [MethodImpl(InliningOptions.ShortMethod)] public Span DangerousGetRowSpan(int y) { - if (Numerics.IsOutOfRangeZeroToExclusiveMax(y, this.Height)) + if ((uint)y >= (uint)this.Height) { this.ThrowYOutOfRangeException(y); } From 4694be382aff9100b210945c5effdf090743d4d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 20 Dec 2021 14:33:25 +0100 Subject: [PATCH 423/511] Add Vp8Residual tests --- .../Formats/WebP/Vp8ResidualTests.cs | 37 +++++++++++++++++++ .../Formats/WebP/WebpEncoderTests.cs | 20 ++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs diff --git a/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs new file mode 100644 index 000000000..2bca632db --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/WebP/Vp8ResidualTests.cs @@ -0,0 +1,37 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using SixLabors.ImageSharp.Formats.Webp.Lossy; +using SixLabors.ImageSharp.Tests.TestUtilities; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Formats.WebP +{ + [Trait("Format", "Webp")] + public class Vp8ResidualTests + { + private static void RunSetCoeffsTest() + { + // arrange + var residual = new Vp8Residual(); + short[] coeffs = { 110, 0, -2, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0 }; + + // act + residual.SetCoeffs(coeffs); + + // assert + Assert.Equal(9, residual.Last); + } + + [Fact] + public void RunSetCoeffsTest_Works() => RunSetCoeffsTest(); + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void RunSetCoeffsTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.AllowAll); + + [Fact] + public void RunSetCoeffsTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunSetCoeffsTest, HwIntrinsics.DisableHWIntrinsic); +#endif + } +} diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs index 607d4c764..cad39224e 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpEncoderTests.cs @@ -5,6 +5,7 @@ using System.IO; using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Webp; @@ -14,6 +15,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp [Trait("Format", "Webp")] public class WebpEncoderTests { + private static string TestImageLossyFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.NoFilter06); + [Theory] [WithFile(Flag, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] // if its not a webp input image, it should default to lossless. [WithFile(Lossless.NoTransform1, PixelTypes.Rgba32, WebpFileFormatType.Lossless)] @@ -288,6 +291,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); } + public static void RunEncodeLossy_WithPeakImage() + { + var provider = TestImageProvider.File(TestImageLossyFullPath); + using Image image = provider.GetImage(); + + var encoder = new WebpEncoder() { FileFormat = WebpFileFormatType.Lossy }; + image.VerifyEncoder(provider, "webp", string.Empty, encoder, ImageComparer.Tolerant(0.04f)); + } + +#if SUPPORTS_RUNTIME_INTRINSICS + [Fact] + public void RunEncodeLossy_WithPeakImage_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.AllowAll); + + [Fact] + public void RunEncodeLossy_WithPeakImage_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunEncodeLossy_WithPeakImage, HwIntrinsics.DisableHWIntrinsic); +#endif + private static ImageComparer GetComparer(int quality) { float tolerance = 0.01f; // ~1.0% From a1e932feaaa98ed1d5462aa9a1424f9b30179f85 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 14:51:14 +0100 Subject: [PATCH 424/511] docs --- .../Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs | 2 +- src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs index 2ec8f97e1..f2e02bcfe 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupSpanCache.cs @@ -8,7 +8,7 @@ using SixLabors.ImageSharp.Memory.Internals; namespace SixLabors.ImageSharp.Memory { /// - /// Cached pointer or array data enabling fast from + /// Cached pointer or array data enabling fast access from /// known implementations. /// internal unsafe struct MemoryGroupSpanCache diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs index 6ab8db0ca..8bd32efa9 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/SpanCacheMode.cs @@ -3,6 +3,9 @@ namespace SixLabors.ImageSharp.Memory { + /// + /// Selects active values in . + /// internal enum SpanCacheMode { Default = default, From 52f2ce7c17d710a4ef5d45bc3331f8ca3a3a9031 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 17:11:27 +0100 Subject: [PATCH 425/511] use Math.DivRem --- .../DiscontiguousBuffers/MemoryGroupExtensions.cs | 4 ++-- .../Memory/DiscontiguousBuffers/MemoryGroup{T}.cs | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs index 0df1080e5..d200b223a 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroupExtensions.cs @@ -37,7 +37,8 @@ namespace SixLabors.ImageSharp.Memory Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); Guard.MustBeLessThan(start, group.TotalLength, nameof(start)); - int bufferIdx = (int)(start / group.BufferLength); + int bufferIdx = (int)Math.DivRem(start, group.BufferLength, out long bufferStartLong); + int bufferStart = (int)bufferStartLong; // if (bufferIdx < 0 || bufferIdx >= group.Count) if ((uint)bufferIdx >= group.Count) @@ -45,7 +46,6 @@ namespace SixLabors.ImageSharp.Memory throw new ArgumentOutOfRangeException(nameof(start)); } - int bufferStart = (int)(start % group.BufferLength); int bufferEnd = bufferStart + length; Memory memory = group[bufferIdx]; diff --git a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs index 153dd05e5..9844f4a3b 100644 --- a/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs +++ b/src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs @@ -289,10 +289,9 @@ namespace SixLabors.ImageSharp.Memory /// public Span GetRemainingSliceOfBuffer(long start) { - int bufferIdx = (int)(start / this.BufferLength); - int bufferStart = (int)(start % this.BufferLength); - Memory memory = this[bufferIdx]; - return memory.Span.Slice(bufferStart); + long bufferIdx = Math.DivRem(start, this.BufferLength, out long bufferStart); + Memory memory = this[(int)bufferIdx]; + return memory.Span.Slice((int)bufferStart); } public static bool CanSwapContent(MemoryGroup target, MemoryGroup source) => @@ -314,8 +313,9 @@ namespace SixLabors.ImageSharp.Memory private void GetMultiBufferPosition(int y, int width, out int bufferIdx, out int bufferStart) { long start = y * (long)width; - bufferIdx = (int)(start / this.BufferLength); - bufferStart = (int)(start % this.BufferLength); + long bufferIdxLong = Math.DivRem(start, this.BufferLength, out long bufferStartLong); + bufferIdx = (int)bufferIdxLong; + bufferStart = (int)bufferStartLong; } } } From 0d3bd576428e073108b5a19a4bdf81f1b123a741 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Dec 2021 17:14:28 +0100 Subject: [PATCH 426/511] update benchmark results --- .../General/Buffer2D_DangerousGetRowSpan.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs index 87fa85250..01d06bf75 100644 --- a/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs +++ b/tests/ImageSharp.Benchmarks/General/Buffer2D_DangerousGetRowSpan.cs @@ -12,8 +12,7 @@ namespace SixLabors.ImageSharp.Benchmarks.General { private const int Height = 1024; - [Params(0.5, 2.0, 10.0)] - public double SizeMegaBytes { get; set; } + [Params(0.5, 2.0, 10.0)] public double SizeMegaBytes { get; set; } private Buffer2D buffer; @@ -40,8 +39,8 @@ namespace SixLabors.ImageSharp.Benchmarks.General // // | Method | SizeMegaBytes | Mean | Error | StdDev | // |-------------------- |-------------- |----------:|----------:|----------:| - // | DangerousGetRowSpan | 0.5 | 7.760 ns | 0.0444 ns | 0.0347 ns | - // | DangerousGetRowSpan | 2 | 6.551 ns | 0.0693 ns | 0.0579 ns | - // | DangerousGetRowSpan | 10 | 45.839 ns | 0.9090 ns | 1.0468 ns | + // | DangerousGetRowSpan | 0.5 | 7.498 ns | 0.1784 ns | 0.3394 ns | + // | DangerousGetRowSpan | 2 | 6.542 ns | 0.1565 ns | 0.3659 ns | + // | DangerousGetRowSpan | 10 | 38.556 ns | 0.6604 ns | 0.8587 ns | } } From 981ff69831d0f34f61c47456789f0ee3520aa338 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 22 Dec 2021 00:12:45 +1100 Subject: [PATCH 427/511] Update ImageFrame{TPixel}.cs --- src/ImageSharp/ImageFrame{TPixel}.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/ImageSharp/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs index cc1b01ff7..fe037003e 100644 --- a/src/ImageSharp/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/ImageFrame{TPixel}.cs @@ -145,13 +145,8 @@ namespace SixLabors.ImageSharp source.PixelBuffer.FastMemoryGroup.CopyTo(this.PixelBuffer.FastMemoryGroup); } - /// - /// Gets the image pixels. Not private as Buffer2D requires an array in its constructor. - /// - internal Buffer2D PixelBuffer { get; private set; } - /// - Buffer2D IPixelSource.PixelBuffer => this.PixelBuffer; + public Buffer2D PixelBuffer { get; private set; } /// /// Gets or sets the pixel at the specified position. From c57bf82c4446cf99d688f2b23de563379ffccaeb Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 22 Dec 2021 01:24:36 +1100 Subject: [PATCH 428/511] Don't throw when Position is out of range. Fix #1903 --- src/ImageSharp/IO/ChunkedMemoryStream.cs | 1 - tests/Directory.Build.targets | 1 + .../Image/ImageTests.Identify.cs | 53 +++++++++++++++++++ .../ImageSharp.Tests/ImageSharp.Tests.csproj | 1 + 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/IO/ChunkedMemoryStream.cs b/src/ImageSharp/IO/ChunkedMemoryStream.cs index e28baf879..e39930def 100644 --- a/src/ImageSharp/IO/ChunkedMemoryStream.cs +++ b/src/ImageSharp/IO/ChunkedMemoryStream.cs @@ -168,7 +168,6 @@ namespace SixLabors.ImageSharp.IO // Position is out of range this.readChunk = backupReadChunk; this.readOffset = backupReadOffset; - ThrowArgumentOutOfRange(nameof(value)); } } } diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets index ddceaff1f..525860189 100644 --- a/tests/Directory.Build.targets +++ b/tests/Directory.Build.targets @@ -33,6 +33,7 @@ + diff --git a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs index 271aa30cf..93f73c3d3 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.Identify.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System.IO; +using System.IO.Compression; using System.Threading.Tasks; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Tests.TestUtilities; @@ -138,6 +139,32 @@ namespace SixLabors.ImageSharp.Tests Assert.Null(type); } + [Fact] + public void FromStream_ZeroLength_ReturnsNull() + { + // https://github.com/SixLabors/ImageSharp/issues/1903 + using var zipFile = new ZipArchive(new MemoryStream( + new byte[] + { + 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, + 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, + 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, + 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, + 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, + 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, + 0x00, 0x00, 0x00, 0x00 + })); + using Stream stream = zipFile.Entries[0].Open(); + IImageInfo info = Image.Identify(stream); + Assert.Null(info); + } + [Fact] public async Task FromStreamAsync_GlobalConfiguration_NoFormat() { @@ -188,6 +215,32 @@ namespace SixLabors.ImageSharp.Tests Assert.Equal(ExpectedGlobalFormat, res.Format); } + [Fact] + public async Task FromStreamAsync_ZeroLength_ReturnsNull() + { + // https://github.com/SixLabors/ImageSharp/issues/1903 + using var zipFile = new ZipArchive(new MemoryStream( + new byte[] + { + 0x50, 0x4B, 0x03, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xAF, + 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x6D, 0x79, 0x73, 0x74, 0x65, 0x72, + 0x79, 0x50, 0x4B, 0x01, 0x02, 0x3F, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x77, 0xAF, 0x94, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, + 0x79, 0x73, 0x74, 0x65, 0x72, 0x79, 0x0A, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x46, 0x82, 0xFF, 0x91, 0x27, 0xF6, + 0xD7, 0x01, 0x55, 0xA1, 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x55, 0xA1, + 0xF9, 0x91, 0x27, 0xF6, 0xD7, 0x01, 0x50, 0x4B, 0x05, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x59, 0x00, 0x00, 0x00, 0x25, 0x00, + 0x00, 0x00, 0x00, 0x00 + })); + using Stream stream = zipFile.Entries[0].Open(); + IImageInfo info = await Image.IdentifyAsync(stream); + Assert.Null(info); + } + [Fact] public async Task FromPathAsync_CustomConfiguration() { diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 471287006..28c778787 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -47,6 +47,7 @@ + From 2a7ec5dc66de7387c08f93d678d8e4e84b5b7eed Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Tue, 28 Dec 2021 03:29:31 -0800 Subject: [PATCH 429/511] Moved ExifHeader to a local variable since it's only used in one function anyway. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 26 +++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 721a05240..cb1fc3e45 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -124,12 +124,6 @@ namespace SixLabors.ImageSharp.Formats.Png this.ignoreMetadata = options.IgnoreMetadata; } - /// - /// Gets the sequence of bytes for the exif header ("Exif" ASCII and two zero bytes). Used for the legacy exif parsing. - /// - // This uses C# compiler's optimization to refer to the static data directly, no intermediate array allocations happen. - private static ReadOnlySpan ExifHeader => new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; - /// public Configuration Configuration { get; } @@ -1015,35 +1009,39 @@ namespace SixLabors.ImageSharp.Formats.Png // Skip to the hex-encoded data dataSpan = dataSpan.Slice(dataLengthEnd).Trim(); - if (dataLength < ExifHeader.Length) + // Sequence of bytes for the exif header ("Exif" ASCII and two zero bytes). + // This doesn't actually allocate. + ReadOnlySpan exifHeader = new byte[] { 0x45, 0x78, 0x69, 0x66, 0x00, 0x00 }; + + if (dataLength < exifHeader.Length) { // Not enough room for the required exif header, this data couldn't possibly be valid return false; } // Parse the hex-encoded data into the byte array we are going to hand off to ExifProfile - byte[] exifBlob = new byte[dataLength - ExifHeader.Length]; + byte[] exifBlob = new byte[dataLength - exifHeader.Length]; try { // Check for the presence of the exif header in the hex-encoded binary data byte[] tempExifBuf = exifBlob; - if (exifBlob.Length < ExifHeader.Length) + if (exifBlob.Length < exifHeader.Length) { // Need to allocate a temporary array, this should be an extremely uncommon (TODO: impossible?) case - tempExifBuf = new byte[ExifHeader.Length]; + tempExifBuf = new byte[exifHeader.Length]; } - HexStringToBytes(dataSpan.Slice(0, ExifHeader.Length * 2), tempExifBuf.AsSpan()); - if (!tempExifBuf.AsSpan().Slice(0, ExifHeader.Length).SequenceEqual(ExifHeader)) + HexStringToBytes(dataSpan.Slice(0, exifHeader.Length * 2), tempExifBuf.AsSpan()); + if (!tempExifBuf.AsSpan().Slice(0, exifHeader.Length).SequenceEqual(exifHeader)) { // Exif header in the hex data is not valid return false; } // Skip over the exif header we just tested - dataSpan = dataSpan.Slice(ExifHeader.Length * 2); - dataLength -= ExifHeader.Length; + dataSpan = dataSpan.Slice(exifHeader.Length * 2); + dataLength -= exifHeader.Length; // Load the hex-encoded data, one line at a time for (int i = 0; i < dataLength;) From 82e664aad3a21dac75f07a55df6c95eeed3e3b23 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Tue, 28 Dec 2021 04:01:03 -0800 Subject: [PATCH 430/511] New, faster HexStringToBytes implementation based off the reference source implementation of Convert.FromHexString(): https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 95 ++++++++++++++------ 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cb1fc3e45..ef133dece 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1032,7 +1032,7 @@ namespace SixLabors.ImageSharp.Formats.Png tempExifBuf = new byte[exifHeader.Length]; } - HexStringToBytes(dataSpan.Slice(0, exifHeader.Length * 2), tempExifBuf.AsSpan()); + HexStringToBytes(dataSpan.Slice(0, exifHeader.Length * 2), tempExifBuf); if (!tempExifBuf.AsSpan().Slice(0, exifHeader.Length).SequenceEqual(exifHeader)) { // Exif header in the hex data is not valid @@ -1103,51 +1103,90 @@ namespace SixLabors.ImageSharp.Formats.Png } /// - /// Parses a hexadecimal string into a byte array without allocations. - /// Adapted from https://stackoverflow.com/a/9995303/871842 + /// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character. + /// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991. /// - /// The hexadecimal string to parse. - /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. - /// The number of bytes written to . - private static int HexStringToBytes(ReadOnlySpan hexString, Span outputBytes) + /// The hexadecimal string to parse. + /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. + /// The number of bytes written to . + private static int HexStringToBytes(ReadOnlySpan chars, Span bytes) { - if ((hexString.Length % 2) != 0) + if ((chars.Length % 2) != 0) { - throw new ArgumentException("Input string length must be a multiple of 2", nameof(hexString)); + throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars)); } - if ((outputBytes.Length * 2) < hexString.Length) + if ((bytes.Length * 2) < chars.Length) { throw new ArgumentException("Output span must be at least half the length of the input string"); } + else + { + // Slightly better performance in the loop below, allows us to skip a bounds check + // while still supporting output buffers that are larger than necessary + bytes = bytes.Slice(0, chars.Length / 2); + } - static int GetHexVal(char hexChar) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int FromChar(int c) { - if (hexChar >= '0' && hexChar <= '9') - { - return hexChar - '0'; - } - else if (hexChar >= 'A' && hexChar <= 'F') + // Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + // This doesn't actually allocate. + ReadOnlySpan charToHexLookup = new byte[] { - return 10 + (hexChar - 'A'); - } - else if (hexChar >= 'a' && hexChar <= 'f') - { - return 10 + (hexChar - 'a'); - } - else + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 255 + }; + + return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; + } + + // See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361 + int i = 0; + int j = 0; + int byteLo = 0; + int byteHi = 0; + while (j < bytes.Length) + { + byteLo = FromChar(chars[i + 1]); + byteHi = FromChar(chars[i]); + + // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern + // is if either byteHi or byteLo was not a hex character. + if ((byteLo | byteHi) == 0xFF) { - throw new ArgumentException($"Invalid hexadecimal value {hexChar}"); + break; } + + bytes[j++] = (byte)((byteHi << 4) | byteLo); + i += 2; + } + + if (byteLo == 0xFF) + { + i++; } - int inputByteCount = hexString.Length / 2; - for (int i = 0; i < inputByteCount; i++) + if ((byteLo | byteHi) == 0xFF) { - outputBytes[i] = (byte)((GetHexVal(hexString[i * 2]) << 4) + GetHexVal(hexString[(i * 2) + 1])); + throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars)); } - return inputByteCount; + return j; } /// From fe044d131c45d89aa52281e2eb442c82b4424ce1 Mon Sep 17 00:00:00 2001 From: wischi Date: Tue, 28 Dec 2021 19:29:38 +0100 Subject: [PATCH 431/511] Remove return value documentation for void return types. --- src/ImageSharp/Formats/ImageExtensions.Save.cs | 8 -------- src/ImageSharp/Formats/ImageExtensions.Save.tt | 1 - 2 files changed, 9 deletions(-) diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.cs b/src/ImageSharp/Formats/ImageExtensions.Save.cs index a6a65aef6..84f9d69b7 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.cs +++ b/src/ImageSharp/Formats/ImageExtensions.Save.cs @@ -105,7 +105,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder) => source.Save( stream, @@ -208,7 +207,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAsGif(this Image source, Stream stream, GifEncoder encoder) => source.Save( stream, @@ -311,7 +309,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder) => source.Save( stream, @@ -414,7 +411,6 @@ namespace SixLabors.ImageSharp /// 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, @@ -517,7 +513,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAsPng(this Image source, Stream stream, PngEncoder encoder) => source.Save( stream, @@ -620,7 +615,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAsTga(this Image source, Stream stream, TgaEncoder encoder) => source.Save( stream, @@ -723,7 +717,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder) => source.Save( stream, @@ -826,7 +819,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder) => source.Save( stream, diff --git a/src/ImageSharp/Formats/ImageExtensions.Save.tt b/src/ImageSharp/Formats/ImageExtensions.Save.tt index c4a00b37c..ae7648522 100644 --- a/src/ImageSharp/Formats/ImageExtensions.Save.tt +++ b/src/ImageSharp/Formats/ImageExtensions.Save.tt @@ -124,7 +124,6 @@ namespace SixLabors.ImageSharp /// 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 SaveAs<#= fmt #>(this Image source, Stream stream, <#= fmt #>Encoder encoder) => source.Save( stream, From b8bed9f69e4fb3a46831037eb6bf657d4682df8d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 1 Jan 2022 17:12:55 +0100 Subject: [PATCH 432/511] All image formats round trip --- src/ImageSharp/Formats/Gif/GifConstants.cs | 11 + src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 44 ++- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 42 ++- .../Sections/GifGraphicControlExtension.cs | 4 +- .../GifNetscapeLoopingApplicationExtension.cs | 2 + .../Sections/GifXmpApplicationExtension.cs | 96 +++++++ .../Formats/Gif/Sections/IGifExtension.cs | 7 +- .../Components/Decoder/ProfileResolver.cs | 12 + .../Formats/Jpeg/JpegDecoderCore.cs | 41 +++ .../Formats/Jpeg/JpegEncoderCore.cs | 58 +++- src/ImageSharp/Formats/Png/PngConstants.cs | 24 ++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 32 ++- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 38 +++ .../Formats/Tiff/TiffDecoderCore.cs | 9 +- .../Tiff/TiffDecoderMetadataCreator.cs | 7 + .../Tiff/TiffEncoderEntriesCollector.cs | 10 +- .../Formats/Webp/BitWriter/BitWriterBase.cs | 37 ++- .../Formats/Webp/BitWriter/Vp8BitWriter.cs | 28 +- .../Formats/Webp/BitWriter/Vp8LBitWriter.cs | 24 +- .../Formats/Webp/Lossless/Vp8LEncoder.cs | 5 +- .../Formats/Webp/Lossy/Vp8Encoder.cs | 6 +- .../Formats/Webp/WebpDecoderCore.cs | 78 +++-- src/ImageSharp/Metadata/ImageFrameMetadata.cs | 7 +- src/ImageSharp/Metadata/ImageMetadata.cs | 8 +- .../Metadata/Profiles/XMP/XmpProfile.cs | 169 +++++++++++ .../Formats/Tiff/TiffMetadataTests.cs | 13 +- .../Formats/WebP/WebpMetaDataTests.cs | 20 ++ .../Metadata/ImageFrameMetadataTests.cs | 8 +- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 270 ++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 4 + .../Input/Jpg/baseline/Metadata-test-file.jpg | 3 + .../Input/Jpg/baseline/extended-xmp.jpg | 3 + tests/Images/Input/Png/xmp-colorpalette.png | 3 + tests/Images/Input/Webp/xmp_lossy.webp | 3 + 34 files changed, 1028 insertions(+), 98 deletions(-) create mode 100644 src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs create mode 100644 src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs create mode 100644 tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs create mode 100644 tests/Images/Input/Jpg/baseline/Metadata-test-file.jpg create mode 100644 tests/Images/Input/Jpg/baseline/extended-xmp.jpg create mode 100644 tests/Images/Input/Png/xmp-colorpalette.png create mode 100644 tests/Images/Input/Webp/xmp_lossy.webp diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs index 24fd8a936..1179b67b1 100644 --- a/src/ImageSharp/Formats/Gif/GifConstants.cs +++ b/src/ImageSharp/Formats/Gif/GifConstants.cs @@ -121,5 +121,16 @@ namespace SixLabors.ImageSharp.Formats.Gif (byte)'P', (byte)'E', (byte)'2', (byte)'.', (byte)'0' }; + + /// + /// Gets the ASCII encoded application identification bytes. + /// + internal static ReadOnlySpan XmpApplicationIdentificationBytes => new[] + { + (byte)'X', (byte)'M', (byte)'P', + (byte)' ', (byte)'D', (byte)'a', + (byte)'t', (byte)'a', + (byte)'X', (byte)'M', (byte)'P' + }; } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 3e33a6e37..3493e52c7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -11,6 +11,7 @@ using System.Threading; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Gif @@ -250,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.Gif } /// - /// Reads the application extension block parsing any animation information + /// Reads the application extension block parsing any animation or XMP information /// if present. /// private void ReadApplicationExtension() @@ -258,25 +259,42 @@ namespace SixLabors.ImageSharp.Formats.Gif int appLength = this.stream.ReadByte(); // If the length is 11 then it's a valid extension and most likely - // a NETSCAPE or ANIMEXTS extension. We want the loop count from this. + // a NETSCAPE, XMP or ANIMEXTS extension. We want the loop count from this. if (appLength == GifConstants.ApplicationBlockSize) { - this.stream.Skip(appLength); - int subBlockSize = this.stream.ReadByte(); + this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); + bool isXmp = true; + ReadOnlySpan idBytes = GifConstants.XmpApplicationIdentificationBytes; + for (int i = 0; i < idBytes.Length; i++) + { + isXmp &= this.buffer[i] == idBytes[i]; + } - // TODO: There's also a NETSCAPE buffer extension. - // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension - if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) + if (isXmp) { - this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); - this.gifMetadata.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; - this.stream.Skip(1); // Skip the terminator. + var extension = GifXmpApplicationExtension.Read(this.stream); + this.metadata.XmpProfile = new XmpProfile(extension.Data); return; } + else + { + int subBlockSize = this.stream.ReadByte(); + + // TODO: There's also a NETSCAPE buffer extension. + // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension + if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize) + { + this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize); + this.gifMetadata.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount; + this.stream.Skip(1); // Skip the terminator. + return; + } + + // Could be something else not supported yet. + // Skip the subblock and terminator. + this.SkipBlock(subBlockSize); + } - // Could be XMP or something else not supported yet. - // Skip the subblock and terminator. - this.SkipBlock(subBlockSize); return; } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 05ea14e9c..4c2ef4647 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -10,6 +10,7 @@ using System.Threading; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -121,11 +122,8 @@ namespace SixLabors.ImageSharp.Formats.Gif // Write the comments. this.WriteComments(gifMetadata, stream); - // Write application extension to allow additional frames. - if (image.Frames.Count > 1) - { - this.WriteApplicationExtension(stream, gifMetadata.RepeatCount); - } + // Write application extensions. + this.WriteApplicationExtensions(stream, gifMetadata.RepeatCount, metadata.XmpProfile); if (useGlobalTable) { @@ -327,14 +325,23 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The stream to write to. /// The animated image repeat count. - private void WriteApplicationExtension(Stream stream, ushort repeatCount) + /// The XMP metadata profile. Null if profile is not to be written. + private void WriteApplicationExtensions(Stream stream, ushort repeatCount, XmpProfile xmpProfile) { - // Application Extension Header + // Application Extension: Loop repeat count. if (repeatCount != 1) { var loopingExtension = new GifNetscapeLoopingApplicationExtension(repeatCount); this.WriteExtension(loopingExtension, stream); } + + // Application Extension: XMP Profile. + if (xmpProfile != null) + { + xmpProfile.UpdateData(); + var xmpExtension = new GifXmpApplicationExtension(xmpProfile.Data); + this.WriteExtension(xmpExtension, stream); + } } /// @@ -420,14 +427,25 @@ namespace SixLabors.ImageSharp.Formats.Gif private void WriteExtension(TGifExtension extension, Stream stream) where TGifExtension : struct, IGifExtension { - this.buffer[0] = GifConstants.ExtensionIntroducer; - this.buffer[1] = extension.Label; + byte[] buffer; + int extensionSize = extension.ContentLength; + if (extensionSize > this.buffer.Length - 3) + { + buffer = new byte[extensionSize + 3]; + } + else + { + buffer = this.buffer; + } + + buffer[0] = GifConstants.ExtensionIntroducer; + buffer[1] = extension.Label; - int extensionSize = extension.WriteTo(this.buffer.AsSpan(2)); + extension.WriteTo(buffer.AsSpan(2)); - this.buffer[extensionSize + 2] = GifConstants.Terminator; + buffer[extensionSize + 2] = GifConstants.Terminator; - stream.Write(this.buffer, 0, extensionSize + 3); + stream.Write(buffer, 0, extensionSize + 3); } /// diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs index ee5a43d80..801849c9b 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -63,6 +63,8 @@ namespace SixLabors.ImageSharp.Formats.Gif byte IGifExtension.Label => GifConstants.GraphicControlLabel; + int IGifExtension.ContentLength => 5; + public int WriteTo(Span buffer) { ref GifGraphicControlExtension dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer)); diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs index 26faa8925..2c7bed611 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs @@ -12,6 +12,8 @@ namespace SixLabors.ImageSharp.Formats.Gif public byte Label => GifConstants.ApplicationExtensionLabel; + public int ContentLength => 16; + /// /// Gets the repeat count. /// 0 means loop indefinitely. Count is set as play n + 1 times. diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs new file mode 100644 index 000000000..c41ec5894 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs @@ -0,0 +1,96 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Collections.Generic; +using System.IO; + +namespace SixLabors.ImageSharp.Formats.Gif +{ + internal readonly struct GifXmpApplicationExtension : IGifExtension + { + public GifXmpApplicationExtension(byte[] data) => this.Data = data; + + public byte Label => GifConstants.ApplicationExtensionLabel; + + public int ContentLength => this.Data.Length + 269; // 12 + Data Length + 1 + 256 + + /// + /// Gets the raw Data. + /// + public byte[] Data { get; } + + /// + /// Reads the XMP metadata from the specified stream. + /// + /// The stream to read from. + /// The XMP metadata + /// Thrown if the XMP block is not properly terminated. + public static GifXmpApplicationExtension Read(Stream stream) + { + // Read data in blocks, until an \0 character is encountered. + // We overshoot, indicated by the terminatorIndex variable. + const int bufferSize = 256; + var list = new List(); + int terminationIndex = -1; + while (terminationIndex < 0) + { + byte[] temp = new byte[bufferSize]; + int bytesRead = stream.Read(temp); + list.Add(temp); + terminationIndex = Array.IndexOf(temp, (byte)1); + } + + // Pack all the blocks (except magic trailer) into one single array again. + int dataSize = ((list.Count - 1) * bufferSize) + terminationIndex; + byte[] buffer = new byte[dataSize]; + Span bufferSpan = buffer; + int pos = 0; + for (int j = 0; j < list.Count - 1; j++) + { + list[j].CopyTo(bufferSpan.Slice(pos)); + pos += bufferSize; + } + + // Last one only needs the portion until terminationIndex copied over. + Span lastBytes = list[list.Count - 1]; + lastBytes.Slice(0, terminationIndex).CopyTo(bufferSpan.Slice(pos)); + + // Skip the remainder of the magic trailer. + stream.Skip(258 - (bufferSize - terminationIndex)); + return new GifXmpApplicationExtension(buffer); + } + + public int WriteTo(Span buffer) + { + int totalSize = this.ContentLength; + if (buffer.Length < totalSize) + { + throw new InsufficientMemoryException("Unable to write XMP metadata to GIF image"); + } + + int bytesWritten = 0; + buffer[bytesWritten++] = GifConstants.ApplicationBlockSize; + + // Write "XMP DataXMP" + ReadOnlySpan idBytes = GifConstants.XmpApplicationIdentificationBytes; + idBytes.CopyTo(buffer.Slice(bytesWritten)); + bytesWritten += idBytes.Length; + + // XMP Data itself + this.Data.CopyTo(buffer.Slice(bytesWritten)); + bytesWritten += this.Data.Length; + + // Write the Magic Trailer + buffer[bytesWritten++] = 0x01; + for (byte i = 255; i > 0; i--) + { + buffer[bytesWritten++] = i; + } + + buffer[bytesWritten++] = 0x00; + + return totalSize; + } + } +} diff --git a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs index 5a15a6dfa..d2783fc48 100644 --- a/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// byte Label { get; } + /// + /// Gets the length of the contents of this extension. + /// + int ContentLength { get; } + /// /// Writes the extension data to the buffer. /// diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs index e1e0e160c..b41c949b2 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ProfileResolver.cs @@ -60,6 +60,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder (byte)'E', (byte)'x', (byte)'i', (byte)'f', (byte)'\0', (byte)'\0' }; + /// + /// Gets the XMP specific markers. + /// + public static ReadOnlySpan XmpMarker => new[] + { + (byte)'h', (byte)'t', (byte)'t', (byte)'p', (byte)':', (byte)'/', (byte)'/', + (byte)'n', (byte)'s', (byte)'.', (byte)'a', (byte)'d', (byte)'o', (byte)'b', + (byte)'e', (byte)'.', (byte)'c', (byte)'o', (byte)'m', (byte)'/', (byte)'x', + (byte)'a', (byte)'p', (byte)'/', (byte)'1', (byte)'.', (byte)'0', (byte)'/', + (byte)0 + }; + /// /// Gets the Adobe specific markers . /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4be6731cc..eda7a8dbe 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -17,6 +17,7 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -73,6 +74,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private byte[] iptcData; + /// + /// Whether the image has a XMP data. + /// + private bool isXmp; + + /// + /// Contains XMP data. + /// + private byte[] xmpData; + /// /// Contains information about the JFIF marker. /// @@ -183,6 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); + this.InitXmpProfile(); this.InitDerivedMetadataProperties(); return new Image( @@ -198,6 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.InitExifProfile(); this.InitIccProfile(); this.InitIptcProfile(); + this.InitXmpProfile(); this.InitDerivedMetadataProperties(); Size pixelSize = this.Frame.PixelSize; @@ -572,6 +585,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } + /// + /// Initializes the XMP profile. + /// + private void InitXmpProfile() + { + if (this.isXmp) + { + var profile = new XmpProfile(this.xmpData); + this.Metadata.XmpProfile = profile; + } + } + /// /// Assigns derived metadata properties to , eg. horizontal and vertical resolution if it has a JFIF header. /// @@ -657,6 +682,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private void ProcessApp1Marker(BufferedReadStream stream, int remaining) { const int Exif00 = 6; + const int XmpNsLength = 29; if (remaining < Exif00 || this.IgnoreMetadata) { // Skip the application header length @@ -686,6 +712,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.ExtendProfile(ref this.exifData, profile.AsSpan(Exif00).ToArray()); } } + + if (ProfileResolver.IsProfile(profile, ProfileResolver.XmpMarker)) + { + this.isXmp = true; + if (this.xmpData is null) + { + // The first 29 bytes will be skipped, because this is Jpeg specific + this.xmpData = profile.AsSpan(XmpNsLength).ToArray(); + } + else + { + // If the XMP information exceeds 64K, it will be split over multiple APP1 markers + this.ExtendProfile(ref this.xmpData, profile.AsSpan(XmpNsLength).ToArray()); + } + } } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index abe59516f..e90c6473e 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -14,6 +14,7 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Jpeg @@ -109,7 +110,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.WriteJfifApplicationHeader(metadata); } - // Write Exif, ICC and IPTC profiles + // Write Exif, XMP, ICC and IPTC profiles this.WriteProfiles(metadata); if (this.colorType == JpegColorType.Rgb) @@ -466,6 +467,55 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.outputStream.Write(data, 0, data.Length); } + /// + /// Writes the XMP metadata. + /// + /// The XMP metadata to write. + /// + /// Thrown if the XMP profile size exceeds the limit of 65533 bytes. + /// + private void WriteXmpProfile(XmpProfile xmpProfile) + { + if (xmpProfile is null) + { + return; + } + + const int XmpOverheadLength = 29; + const int Max = 65533; + const int MaxData = Max - XmpOverheadLength; + + xmpProfile.UpdateData(); + byte[] data = xmpProfile.Data; + + if (data is null || data.Length == 0) + { + return; + } + + int dataLength = data.Length; + int offset = 0; + + while (dataLength > 0) + { + int length = dataLength; // Number of bytes to write. + + if (length > MaxData) + { + length = MaxData; + } + + dataLength -= length; + + int app1Length = 2 + ProfileResolver.XmpMarker.Length + length; + this.WriteApp1Header(app1Length); + this.outputStream.Write(ProfileResolver.XmpMarker); + this.outputStream.Write(data, offset, length); + + offset += length; + } + } + /// /// Writes the App1 header. /// @@ -579,8 +629,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg return; } + // For compatibility, place the profiles in the following order: + // - APP1 EXIF + // - APP1 XMP + // - APP2 ICC + // - APP13 IPTC metadata.SyncProfiles(); this.WriteExifProfile(metadata.ExifProfile); + this.WriteXmpProfile(metadata.XmpProfile); this.WriteIccProfile(metadata.IccProfile); this.WriteIptcProfile(metadata.IptcProfile); } diff --git a/src/ImageSharp/Formats/Png/PngConstants.cs b/src/ImageSharp/Formats/Png/PngConstants.cs index b4ef28083..fcc8fd992 100644 --- a/src/ImageSharp/Formats/Png/PngConstants.cs +++ b/src/ImageSharp/Formats/Png/PngConstants.cs @@ -78,5 +78,29 @@ namespace SixLabors.ImageSharp.Formats.Png 0x1A, // EOF 0x0A // LF }; + + /// + /// Gets the keyword of the XMP metadata, encoded in an iTXT chunk. + /// + public static ReadOnlySpan XmpKeyword => new byte[] + { + (byte)'X', + (byte)'M', + (byte)'L', + (byte)':', + (byte)'c', + (byte)'o', + (byte)'m', + (byte)'.', + (byte)'a', + (byte)'d', + (byte)'o', + (byte)'b', + (byte)'e', + (byte)'.', + (byte)'x', + (byte)'m', + (byte)'p' + }; } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index ffaa9d567..891a16fc7 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -18,6 +18,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Png @@ -193,7 +194,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.InternationalText: - this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); + this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: if (!this.ignoreMetadata) @@ -315,7 +316,7 @@ namespace SixLabors.ImageSharp.Formats.Png break; } - this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan()); + this.ReadInternationalTextChunk(metadata, chunk.Data.GetSpan()); break; case PngChunkType.Exif: if (this.colorMetadataOnly) @@ -1045,13 +1046,14 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The metadata to decode to. /// The containing the data. - private void ReadInternationalTextChunk(PngMetadata metadata, ReadOnlySpan data) + private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan data) { if (this.ignoreMetadata) { return; } + PngMetadata pngMetadata = metadata.GetPngMetadata(); int zeroIndexKeyword = data.IndexOf((byte)0); if (zeroIndexKeyword < PngConstants.MinTextKeywordLength || zeroIndexKeyword > PngConstants.MaxTextKeywordLength) { @@ -1097,13 +1099,18 @@ namespace SixLabors.ImageSharp.Formats.Png if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed)) { - metadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); + pngMetadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); } } + else if (this.IsXmpTextData(keywordBytes)) + { + XmpProfile xmpProfile = new XmpProfile(data.Slice(dataStartIdx).ToArray()); + metadata.XmpProfile = xmpProfile; + } else { string value = PngConstants.TranslatedEncoding.GetString(data.Slice(dataStartIdx)); - metadata.TextData.Add(new PngTextData(keyword, value, language, translatedKeyword)); + pngMetadata.TextData.Add(new PngTextData(keyword, value, language, translatedKeyword)); } } @@ -1364,6 +1371,21 @@ namespace SixLabors.ImageSharp.Formats.Png return true; } + private bool IsXmpTextData(ReadOnlySpan keywordBytes) + { + ReadOnlySpan expected = PngConstants.XmpKeyword; + bool result = keywordBytes.Length == expected.Length; + if (result) + { + for (int i = 0; i < keywordBytes.Length; i++) + { + result |= keywordBytes[i] == expected[i]; + } + } + + return result; + } + private void SwapScanlineBuffers() { IMemoryOwner temp = this.previousScanline; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 5e067aba5..d9b4375c6 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -138,6 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.WriteTransparencyChunk(stream, pngMetadata); this.WritePhysicalChunk(stream, metadata); this.WriteExifChunk(stream, metadata); + this.WriteXmpChunk(stream, metadata); this.WriteTextChunks(stream, pngMetadata); this.WriteDataChunks(clearTransparency ? clonedImage : image, quantized, stream); this.WriteEndChunk(stream); @@ -654,6 +655,43 @@ namespace SixLabors.ImageSharp.Formats.Png this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray()); } + /// + /// Writes an iTXT chunk, containing the XMP metdata to the stream, if such profile is present in the metadata. + /// + /// The containing image data. + /// The image metadata. + private void WriteXmpChunk(Stream stream, ImageMetadata meta) + { + if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) + { + return; + } + + if (meta.XmpProfile is null) + { + return; + } + + meta.XmpProfile.UpdateData(); + var xmpData = meta.XmpProfile.Data; + + if (xmpData.Length == 0) + { + return; + } + + byte[] payload = new byte[xmpData.Length + PngConstants.XmpKeyword.Length + 5]; + PngConstants.XmpKeyword.CopyTo(payload); + int bytesWritten = PngConstants.XmpKeyword.Length; + payload[bytesWritten++] = 0; // Keyword string terminator + payload[bytesWritten++] = 0; // Compression flag + payload[bytesWritten++] = 0; // Compression method + payload[bytesWritten++] = 0; // Language tag + payload[bytesWritten++] = 0; // Translated keyword + xmpData.CopyTo(payload.AsSpan(bytesWritten)); + this.WriteChunk(stream, PngChunkType.InternationalText, payload); + } + /// /// Writes a text chunk to the stream. Can be either a tTXt, iTXt or zTXt chunk, /// depending whether the text contains any latin characters or should be compressed. diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 55af87005..20661b982 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -12,6 +12,7 @@ using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -197,9 +198,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff private ImageFrame DecodeFrame(ExifProfile tags, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - ImageFrameMetadata imageFrameMetaData = this.ignoreMetadata ? - new ImageFrameMetadata() : - new ImageFrameMetadata { ExifProfile = tags, XmpProfile = tags.GetValue(ExifTag.XMP)?.Value }; + var imageFrameMetaData = new ImageFrameMetadata(); + if (!this.ignoreMetadata) + { + imageFrameMetaData.ExifProfile = tags; + } TiffFrameMetadata tiffFrameMetaData = imageFrameMetaData.GetTiffMetadata(); TiffFrameMetadata.Parse(tiffFrameMetaData, tags); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index 6f8a81a82..ebfe146ca 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff @@ -45,6 +46,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff frameMetaData.IptcProfile = new IptcProfile(iptcBytes); } + IExifValue xmpProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.XMP); + if (xmpProfileBytes != null) + { + frameMetaData.XmpProfile = new XmpProfile(xmpProfileBytes.Value); + } + IExifValue iccProfileBytes = frameMetaData.ExifProfile.GetValue(ExifTag.IccProfile); if (iccProfileBytes != null) { diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 55dd7d397..c5fe395a0 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; namespace SixLabors.ImageSharp.Formats.Tiff { @@ -57,9 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff { ImageFrame rootFrame = image.Frames.RootFrame; ExifProfile rootFrameExifProfile = rootFrame.Metadata.ExifProfile ?? new ExifProfile(); - byte[] foorFrameXmpBytes = rootFrame.Metadata.XmpProfile; + XmpProfile rootFrameXmpProfile = rootFrame.Metadata.XmpProfile; - this.ProcessProfiles(image.Metadata, rootFrameExifProfile, foorFrameXmpBytes); + this.ProcessProfiles(image.Metadata, rootFrameExifProfile, rootFrameXmpProfile); this.ProcessMetadata(rootFrameExifProfile); if (!this.Collector.Entries.Exists(t => t.Tag == ExifTag.Software)) @@ -149,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff } } - private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfile, byte[] xmpProfile) + private void ProcessProfiles(ImageMetadata imageMetadata, ExifProfile exifProfile, XmpProfile xmpProfile) { if (exifProfile != null && exifProfile.Parts != ExifParts.None) { @@ -201,9 +202,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (xmpProfile != null) { + xmpProfile.UpdateData(); var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) { - Value = xmpProfile + Value = xmpProfile.Data }; this.Collector.Add(xmp); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs index 920888136..ac039be79 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/BitWriterBase.cs @@ -5,6 +5,7 @@ using System; using System.Buffers.Binary; using System.IO; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { @@ -90,34 +91,35 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter } /// - /// Calculates the exif chunk size. + /// Calculates the chunk size of EXIF or XMP metadata. /// - /// The exif profile bytes. + /// The metadata profile bytes. /// The exif chunk size in bytes. - protected uint ExifChunkSize(byte[] exifBytes) + protected uint MetadataChunkSize(byte[] metadataBytes) { - uint exifSize = (uint)exifBytes.Length; - uint exifChunkSize = WebpConstants.ChunkHeaderSize + exifSize + (exifSize & 1); + uint metaSize = (uint)metadataBytes.Length; + uint metaChunkSize = WebpConstants.ChunkHeaderSize + metaSize + (metaSize & 1); - return exifChunkSize; + return metaChunkSize; } /// - /// Writes the Exif profile to the stream. + /// Writes a metadata profile (EXIF or XMP) to the stream. /// /// The stream to write to. - /// The exif profile bytes. - protected void WriteExifProfile(Stream stream, byte[] exifBytes) + /// The metadata profile's bytes. + /// The chuck type to write. + protected void WriteMetadataProfile(Stream stream, byte[] metadataBytes, WebpChunkType chunkType) { - DebugGuard.NotNull(exifBytes, nameof(exifBytes)); + DebugGuard.NotNull(metadataBytes, nameof(metadataBytes)); - uint size = (uint)exifBytes.Length; + uint size = (uint)metadataBytes.Length; Span buf = this.scratchBuffer.AsSpan(0, 4); - BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)WebpChunkType.Exif); + BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)chunkType); stream.Write(buf); BinaryPrimitives.WriteUInt32LittleEndian(buf, size); stream.Write(buf); - stream.Write(exifBytes); + stream.Write(metadataBytes); // Add padding byte if needed. if ((size & 1) == 1) @@ -131,10 +133,11 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// /// The stream to write to. /// A exif profile or null, if it does not exist. + /// A XMP profile or null, if it does not exist. /// The width of the image. /// The height of the image. /// Flag indicating, if a alpha channel is present. - protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, uint width, uint height, bool hasAlpha) + protected void WriteVp8XHeader(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha) { if (width > MaxDimension || height > MaxDimension) { @@ -154,6 +157,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter flags |= 8; } + if (xmpProfile != null) + { + // Set xmp bit. + flags |= 4; + } + if (hasAlpha) { // Set alpha bit. diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 3b2f943db..4e91bedb0 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.IO; using SixLabors.ImageSharp.Formats.Webp.Lossy; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { @@ -404,20 +405,30 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// /// The stream to write to. /// The exif profile. + /// The XMP profile. /// The width of the image. /// The height of the image. /// Flag indicating, if a alpha channel is present. - public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height, bool hasAlpha) + public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha) { bool isVp8X = false; byte[] exifBytes = null; + byte[] xmpBytes = null; uint riffSize = 0; if (exifProfile != null) { isVp8X = true; riffSize += ExtendedFileChunkSize; exifBytes = exifProfile.ToByteArray(); - riffSize += this.ExifChunkSize(exifBytes); + riffSize += this.MetadataChunkSize(exifBytes); + } + + if (xmpProfile != null) + { + isVp8X = true; + riffSize += ExtendedFileChunkSize; + xmpBytes = xmpProfile.Data; + riffSize += this.MetadataChunkSize(xmpBytes); } this.Finish(); @@ -440,7 +451,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter riffSize += WebpConstants.TagSize + WebpConstants.ChunkHeaderSize + vp8Size; // Emit headers and partition #0 - this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, hasAlpha); + this.WriteWebpHeaders(stream, size0, vp8Size, riffSize, isVp8X, width, height, exifProfile, xmpProfile, hasAlpha); bitWriterPartZero.WriteToStream(stream); // Write the encoded image to the stream. @@ -452,7 +463,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter if (exifProfile != null) { - this.WriteExifProfile(stream, exifBytes); + this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif); + } + + if (xmpProfile != null) + { + this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp); } } @@ -623,14 +639,14 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter while (it.Next()); } - private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize, bool isVp8X, uint width, uint height, ExifProfile exifProfile, bool hasAlpha) + private void WriteWebpHeaders(Stream stream, uint size0, uint vp8Size, uint riffSize, bool isVp8X, uint width, uint height, ExifProfile exifProfile, XmpProfile xmpProfile, bool hasAlpha) { this.WriteRiffHeader(stream, riffSize); // Write VP8X, header if necessary. if (isVp8X) { - this.WriteVp8XHeader(stream, exifProfile, width, height, hasAlpha); + this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha); } this.WriteVp8Header(stream, vp8Size); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index b83865aa3..d41224f90 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs @@ -6,6 +6,7 @@ using System.Buffers.Binary; using System.IO; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Metadata.Profiles.Exif; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { @@ -132,20 +133,30 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter /// /// The stream to write to. /// The exif profile. + /// The XMP profile. /// The width of the image. /// The height of the image. /// Flag indicating, if a alpha channel is present. - public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, uint width, uint height, bool hasAlpha) + public void WriteEncodedImageToStream(Stream stream, ExifProfile exifProfile, XmpProfile xmpProfile, uint width, uint height, bool hasAlpha) { bool isVp8X = false; byte[] exifBytes = null; + byte[] xmpBytes = null; uint riffSize = 0; if (exifProfile != null) { isVp8X = true; riffSize += ExtendedFileChunkSize; exifBytes = exifProfile.ToByteArray(); - riffSize += this.ExifChunkSize(exifBytes); + riffSize += this.MetadataChunkSize(exifBytes); + } + + if (xmpProfile != null) + { + isVp8X = true; + riffSize += ExtendedFileChunkSize; + xmpBytes = xmpProfile.Data; + riffSize += this.MetadataChunkSize(xmpBytes); } this.Finish(); @@ -160,7 +171,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter // Write VP8X, header if necessary. if (isVp8X) { - this.WriteVp8XHeader(stream, exifProfile, width, height, hasAlpha); + this.WriteVp8XHeader(stream, exifProfile, xmpProfile, width, height, hasAlpha); } // Write magic bytes indicating its a lossless webp. @@ -180,7 +191,12 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter if (exifProfile != null) { - this.WriteExifProfile(stream, exifBytes); + this.WriteMetadataProfile(stream, exifBytes, WebpChunkType.Exif); + } + + if (xmpProfile != null) + { + this.WriteMetadataProfile(stream, xmpBytes, WebpChunkType.Xmp); } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 8566566f6..e9dce913a 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp.Lossless @@ -252,7 +253,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossless this.EncodeStream(image); // Write bytes from the bitwriter buffer to the stream. - this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height, hasAlpha); + ImageMetadata metadata = image.Metadata; + metadata.SyncProfiles(); + this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha); } /// diff --git a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs index 37e09d080..022232050 100644 --- a/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossy/Vp8Encoder.cs @@ -7,6 +7,7 @@ using System.IO; using System.Runtime.CompilerServices; using SixLabors.ImageSharp.Formats.Webp.BitWriter; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp.Lossy @@ -355,8 +356,9 @@ namespace SixLabors.ImageSharp.Formats.Webp.Lossy this.AdjustFilterStrength(); // Write bytes from the bitwriter buffer to the stream. - image.Metadata.SyncProfiles(); - this.bitWriter.WriteEncodedImageToStream(stream, image.Metadata.ExifProfile, (uint)width, (uint)height, hasAlpha); + ImageMetadata metadata = image.Metadata; + metadata.SyncProfiles(); + this.bitWriter.WriteEncodedImageToStream(stream, metadata.ExifProfile, metadata.XmpProfile, (uint)width, (uint)height, hasAlpha); } /// diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 09071406c..9d18e5d82 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -13,6 +13,7 @@ using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Webp @@ -177,6 +178,7 @@ namespace SixLabors.ImageSharp.Formats.Webp /// Reads an the extended webp file header. An extended file header consists of: /// - A 'VP8X' chunk with information about features used in the file. /// - An optional 'ICCP' chunk with color profile. + /// - An optional 'XMP' chunk with metadata. /// - An optional 'ANIM' chunk with animation control data. /// - An optional 'ALPH' chunk with alpha channel data. /// After the image header, image data will follow. After that optional image metadata chunks (EXIF and XMP) can follow. @@ -228,12 +230,27 @@ namespace SixLabors.ImageSharp.Formats.Webp this.buffer[3] = 0; uint height = (uint)BinaryPrimitives.ReadInt32LittleEndian(this.buffer) + 1; - // Optional chunks ICCP, ALPH and ANIM can follow here. - WebpChunkType chunkType = this.ReadChunkType(); - while (IsOptionalVp8XChunk(chunkType)) + // Read all the chunks in the order they occur. + var info = new WebpImageInfo(); + while (this.currentStream.Position < this.currentStream.Length) { - this.ParseOptionalExtendedChunks(chunkType, features); - chunkType = this.ReadChunkType(); + WebpChunkType chunkType = this.ReadChunkType(); + if (chunkType == WebpChunkType.Vp8) + { + info = this.ReadVp8Header(features); + } + else if (chunkType == WebpChunkType.Vp8L) + { + info = this.ReadVp8LHeader(features); + } + else if (IsOptionalVp8XChunk(chunkType)) + { + this.ParseOptionalExtendedChunks(chunkType, features); + } + else + { + WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); + } } if (features.Animation) @@ -242,17 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Webp return new WebpImageInfo() { Width = width, Height = height, Features = features }; } - switch (chunkType) - { - case WebpChunkType.Vp8: - return this.ReadVp8Header(features); - case WebpChunkType.Vp8L: - return this.ReadVp8LHeader(features); - } - - WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); - - return new WebpImageInfo(); + return info; } /// @@ -413,7 +420,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } /// - /// Parses optional VP8X chunks, which can be ICCP, ANIM or ALPH chunks. + /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks. /// /// The chunk type. /// The webp image features. @@ -440,6 +447,38 @@ namespace SixLabors.ImageSharp.Formats.Webp break; + case WebpChunkType.Exif: + uint exifChunkSize = this.ReadChunkSize(); + if (this.IgnoreMetadata) + { + this.currentStream.Skip((int)exifChunkSize); + } + else + { + byte[] exifData = new byte[exifChunkSize]; + this.currentStream.Read(exifData, 0, (int)exifChunkSize); + var profile = new ExifProfile(exifData); + this.Metadata.ExifProfile = profile; + } + + break; + + case WebpChunkType.Xmp: + uint xmpChunkSize = this.ReadChunkSize(); + if (this.IgnoreMetadata) + { + this.currentStream.Skip((int)xmpChunkSize); + } + else + { + byte[] xmpData = new byte[xmpChunkSize]; + this.currentStream.Read(xmpData, 0, (int)xmpChunkSize); + var profile = new XmpProfile(xmpData); + this.Metadata.XmpProfile = profile; + } + + break; + case WebpChunkType.Animation: // TODO: Decoding animation is not implemented yet. break; @@ -451,6 +490,9 @@ namespace SixLabors.ImageSharp.Formats.Webp features.AlphaData = this.memoryAllocator.Allocate(alphaDataSize); this.currentStream.Read(features.AlphaData.Memory.Span, 0, alphaDataSize); break; + default: + WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); + break; } } @@ -530,7 +572,9 @@ namespace SixLabors.ImageSharp.Formats.Webp { WebpChunkType.Alpha => true, WebpChunkType.Animation => true, + WebpChunkType.Exif => true, WebpChunkType.Iccp => true, + WebpChunkType.Xmp => true, _ => false }; } diff --git a/src/ImageSharp/Metadata/ImageFrameMetadata.cs b/src/ImageSharp/Metadata/ImageFrameMetadata.cs index 1819fd2bc..1cad4ebe8 100644 --- a/src/ImageSharp/Metadata/ImageFrameMetadata.cs +++ b/src/ImageSharp/Metadata/ImageFrameMetadata.cs @@ -1,12 +1,12 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System; using System.Collections.Generic; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; namespace SixLabors.ImageSharp.Metadata { @@ -43,8 +43,7 @@ namespace SixLabors.ImageSharp.Metadata this.ExifProfile = other.ExifProfile?.DeepClone(); this.IccProfile = other.IccProfile?.DeepClone(); this.IptcProfile = other.IptcProfile?.DeepClone(); - this.XmpProfile = other.XmpProfile != null ? new byte[other.XmpProfile.Length] : null; - other.XmpProfile?.AsSpan().CopyTo(this.XmpProfile.AsSpan()); + this.XmpProfile = other.XmpProfile?.DeepClone(); } /// @@ -55,7 +54,7 @@ namespace SixLabors.ImageSharp.Metadata /// /// Gets or sets the XMP profile. /// - internal byte[] XmpProfile { get; set; } + public XmpProfile XmpProfile { get; set; } /// /// Gets or sets the list of ICC profiles. diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 425fd9b47..b7ab23c2b 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -6,6 +6,7 @@ using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; namespace SixLabors.ImageSharp.Metadata { @@ -119,13 +120,18 @@ namespace SixLabors.ImageSharp.Metadata /// public ExifProfile ExifProfile { get; set; } + /// + /// Gets or sets the XMP profile. + /// + public XmpProfile XmpProfile { get; set; } + /// /// Gets or sets the list of ICC profiles. /// public IccProfile IccProfile { get; set; } /// - /// Gets or sets the iptc profile. + /// Gets or sets the IPTC profile. /// public IptcProfile IptcProfile { get; set; } diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs new file mode 100644 index 000000000..7c9cfc55f --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs @@ -0,0 +1,169 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.IO; +using System.Text; +using System.Xml.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp +{ + /// + /// Represents an XMP profile, providing access to the raw XML. + /// + public sealed class XmpProfile : IDeepCloneable + { + private XDocument document; + + /// + /// Initializes a new instance of the class. + /// + public XmpProfile() + : this((byte[])null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The UTF8 encoded byte array to read the XMP profile from. + public XmpProfile(byte[] data) => this.Data = data; + + /// + /// Initializes a new instance of the class + /// by making a copy from another XMP profile. + /// + /// The other XMP profile, from which the clone should be made from. + private XmpProfile(XmpProfile other) + { + Guard.NotNull(other, nameof(other)); + + if (other.Data != null) + { + this.Data = new byte[other.Data.Length]; + other.Data.AsSpan().CopyTo(this.Data); + } + } + + /// + /// Gets the rax XML document containing the XMP profile. + /// + public XDocument Document + { + get + { + this.InitializeDocument(); + return this.document; + } + } + + /// + /// Gets the byte data of the XMP profile. + /// + public byte[] Data { get; private set; } + + /// + /// Checks whether two structures are equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is equal to the parameter; + /// otherwise, false. + /// + public static bool operator ==(XmpProfile left, XmpProfile right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (ReferenceEquals(left, null)) + { + return false; + } + + return left.Equals(right); + } + + /// + /// Checks whether two structures are not equal. + /// + /// The left hand operand. + /// The right hand operand. + /// + /// True if the parameter is not equal to the parameter; + /// otherwise, false. + /// + public static bool operator !=(XmpProfile left, XmpProfile right) + { + return !(left == right); + } + + /// + public XmpProfile DeepClone() => new(this); + + /// + /// Updates the data of the profile. + /// + public void UpdateData() + { + if (this.document == null) + { + return; + } + + using var stream = new MemoryStream(this.Data.Length); + using var writer = new StreamWriter(stream, Encoding.UTF8); + this.document.Save(writer); + this.Data = stream.ToArray(); + } + + /// + public override int GetHashCode() => base.GetHashCode(); + + /// + public override bool Equals(object obj) + { + XmpProfile other = obj as XmpProfile; + if (ReferenceEquals(other, null)) + { + return false; + } + + if (ReferenceEquals(this.Data, null)) + { + return false; + } + + return this.Data.Equals(other.Data); + } + + private void InitializeDocument() + { + if (this.document != null) + { + return; + } + + if (this.Data == null) + { + return; + } + + // Strip leading whitespace, as the XmlReader doesn't like them. + int count = this.Data.Length; + for (int i = count - 1; i > 0; i--) + { + if (this.Data[i] is 0 or 0x0f) + { + count--; + } + } + + using var stream = new MemoryStream(this.Data, 0, count); + using var reader = new StreamReader(stream, Encoding.UTF8); + this.document = XDocument.Load(reader); + } + } +} diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 7715ac3a3..6a47a9577 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -132,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.Length); + Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); } } @@ -163,7 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(32, rootFrame.Width); Assert.Equal(32, rootFrame.Height); Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Length); + Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); @@ -251,7 +252,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff ImageMetadata inputMetaData = image.Metadata; ImageFrame rootFrameInput = image.Frames.RootFrame; TiffFrameMetadata frameMetaInput = rootFrameInput.Metadata.GetTiffMetadata(); - byte[] xmpProfileInput = rootFrameInput.Metadata.XmpProfile; + XmpProfile xmpProfileInput = rootFrameInput.Metadata.XmpProfile; ExifProfile exifProfileInput = rootFrameInput.Metadata.ExifProfile; Assert.Equal(TiffCompression.Lzw, frameMetaInput.Compression); @@ -270,7 +271,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff ImageFrame rootFrameEncodedImage = encodedImage.Frames.RootFrame; TiffFrameMetadata tiffMetaDataEncodedRootFrame = rootFrameEncodedImage.Metadata.GetTiffMetadata(); ExifProfile encodedImageExifProfile = rootFrameEncodedImage.Metadata.ExifProfile; - byte[] encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; + XmpProfile encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaDataEncodedRootFrame.BitsPerPixel); Assert.Equal(TiffCompression.Lzw, tiffMetaDataEncodedRootFrame.Compression); @@ -288,7 +289,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(exifProfileInput.GetValue(ExifTag.XResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.XResolution).Value.ToDouble()); Assert.Equal(exifProfileInput.GetValue(ExifTag.YResolution).Value.ToDouble(), encodedImageExifProfile.GetValue(ExifTag.YResolution).Value.ToDouble()); - Assert.Equal(xmpProfileInput, encodedImageXmpProfile); + Assert.NotNull(xmpProfileInput); + Assert.NotNull(encodedImageXmpProfile); + Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index a051de1c0..7fba86b4f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -63,6 +63,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + [Theory] + [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, false)] + [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32, true)] + public async void IgnoreMetadata_ControlsWhetherXmpIsParsed(TestImageProvider provider, bool ignoreMetadata) + where TPixel : unmanaged, IPixel + { + var decoder = new WebpDecoder { IgnoreMetadata = ignoreMetadata }; + + using Image image = await provider.GetImageAsync(decoder); + if (ignoreMetadata) + { + Assert.Null(image.Metadata.XmpProfile); + } + else + { + Assert.NotNull(image.Metadata.XmpProfile); + Assert.NotEmpty(image.Metadata.XmpProfile.Data); + } + } + [Theory] [InlineData(WebpFileFormatType.Lossy)] [InlineData(WebpFileFormatType.Lossless)] diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index f1a90d43e..17be08f13 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -1,10 +1,10 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. -using System.Linq; using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Icc; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using Xunit; using ExifProfile = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifProfile; using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag; @@ -41,10 +41,10 @@ namespace SixLabors.ImageSharp.Tests.Metadata public void CloneIsDeep() { // arrange - byte[] xmpProfile = { 1, 2, 3 }; var exifProfile = new ExifProfile(); exifProfile.SetValue(ExifTag.Software, "UnitTest"); exifProfile.SetValue(ExifTag.Artist, "UnitTest"); + var xmpProfile = new XmpProfile(new byte[0]); var iccProfile = new IccProfile() { Header = new IccProfileHeader() @@ -72,8 +72,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata Assert.NotNull(clone.IptcProfile); Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); - Assert.False(metaData.XmpProfile.Equals(clone.XmpProfile)); - Assert.True(metaData.XmpProfile.SequenceEqual(clone.XmpProfile)); + Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); + Assert.True(metaData.XmpProfile.Data.Length.Equals(clone.XmpProfile.Data.Length)); Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs new file mode 100644 index 000000000..371c110b7 --- /dev/null +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -0,0 +1,270 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.IO; +using System.Text; +using System.Xml.Linq; +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Gif; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Formats.Png; +using SixLabors.ImageSharp.Formats.Tiff; +using SixLabors.ImageSharp.Formats.Webp; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; +using SixLabors.ImageSharp.PixelFormats; +using Xunit; + +namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp +{ + public class XmpProfileTests + { + private static GifDecoder GifDecoder => new GifDecoder() { IgnoreMetadata = false }; + + private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + + private static PngDecoder PngDecoder => new PngDecoder() { IgnoreMetadata = false }; + + private static TiffDecoder TiffDecoder => new TiffDecoder() { IgnoreMetadata = false }; + + private static WebpDecoder WebpDecoder => new WebpDecoder() { IgnoreMetadata = false }; + + [Theory] + [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)] + public async void ReadXmpMetadata_FromGif_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(GifDecoder)) + { + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + } + + [Theory] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.Metadata, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Baseline.ExtendedXmp, PixelTypes.Rgba32)] + public async void ReadXmpMetadata_FromJpg_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(JpegDecoder)) + { + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + } + + [Theory] + [WithFile(TestImages.Png.XmpColorPalette, PixelTypes.Rgba32)] + public async void ReadXmpMetadata_FromPng_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(PngDecoder)) + { + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + } + + [Theory] + [WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)] + public async void ReadXmpMetadata_FromTiff_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(TiffDecoder)) + { + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + } + + [Theory] + [WithFile(TestImages.Webp.Lossy.WithXmp, PixelTypes.Rgba32)] + public async void ReadXmpMetadata_FromWebp_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = await provider.GetImageAsync(WebpDecoder)) + { + XmpProfile actual = image.Metadata.XmpProfile ?? image.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + } + + [Fact] + public void XmpProfile_ToAndFromByteArray_Works() + { + // arrange + XmpProfile profile = CreateMinimalXmlProfile(); + profile.Document.Root.AddFirst(new XElement(XName.Get("written"))); + + // act + profile.UpdateData(); + byte[] profileBytes = profile.Data; + var profileFromBytes = new XmpProfile(profileBytes); + + // assert + XmpProfileContainsExpectedValues(profileFromBytes); + Assert.Equal("written", ((XElement)profileFromBytes.Document.Root.FirstNode).Name); + } + + [Fact] + public void XmpProfile_EqualalityIsByValue() + { + // arrange + byte[] content = new byte[0]; + XmpProfile original = new XmpProfile(content); + XmpProfile other = new XmpProfile(content); + + // act + var equals = original.Equals(other); + var equality = original == other; + var inequality = original != other; + + // assert + Assert.True(equals); + Assert.True(equality); + Assert.False(inequality); + } + + [Fact] + public void XmpProfile_CloneIsDeep() + { + // arrange + XmpProfile profile = CreateMinimalXmlProfile(); + profile.Document.Root.AddFirst(new XElement(XName.Get("written"))); + + // act + XmpProfile clone = profile.DeepClone(); + clone.Document.Root.AddFirst(new XElement(XName.Get("onlyonclone"))); + + // assert + XmpProfileContainsExpectedValues(clone); + Assert.Equal("onlyonclone", ((XElement)clone.Document.Root.FirstNode).Name); + } + + [Fact] + public void WritingGif_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + var encoder = new GifEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + + [Fact] + public void WritingJpeg_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + var encoder = new JpegEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + + [Fact] + public async void WritingJpeg_PreservesExtendedXmpProfile() + { + // arrange + var provider = TestImageProvider.File(TestImages.Jpeg.Baseline.ExtendedXmp); + using Image image = await provider.GetImageAsync(JpegDecoder); + var encoder = new JpegEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + + [Fact] + public void WritingPng_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + var encoder = new PngEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + + [Fact] + public void WritingTiff_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + image.Frames.RootFrame.Metadata.XmpProfile = CreateMinimalXmlProfile(); + var encoder = new TiffEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + + [Fact] + public void WritingWebp_PreservesXmpProfile() + { + // arrange + var image = new Image(1, 1); + image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + var encoder = new WebpEncoder(); + + // act + using Image reloadedImage = WriteAndRead(image, encoder); + + // assert + XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; + XmpProfileContainsExpectedValues(actual); + } + + private static void XmpProfileContainsExpectedValues(XmpProfile xmp) + { + Assert.NotNull(xmp); + XDocument document = xmp.Document; + Assert.NotNull(document); + Assert.Equal("xmpmeta", document.Root.Name.LocalName); + Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); + } + + private static XmpProfile CreateMinimalXmlProfile() + { + string content = ""; + byte[] data = Encoding.UTF8.GetBytes(content); + var profile = new XmpProfile(data); + return profile; + } + + private static Image WriteAndRead(Image image, IImageEncoder encoder) + { + using (var memStream = new MemoryStream()) + { + image.Save(memStream, encoder); + image.Dispose(); + + memStream.Position = 0; + return Image.Load(memStream); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f43c0c9bd..25e8de2aa 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -61,6 +61,7 @@ namespace SixLabors.ImageSharp.Tests public const string David = "Png/david.png"; public const string TestPattern31x31 = "Png/testpattern31x31.png"; public const string TestPattern31x31HalfTransparent = "Png/testpattern31x31-halftransparent.png"; + public const string XmpColorPalette = "Png/xmp-colorpalette.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; @@ -214,6 +215,8 @@ namespace SixLabors.ImageSharp.Tests public const string ArithmeticCodingProgressive = "Jpg/progressive/arithmetic_progressive.jpg"; public const string Lossless = "Jpg/baseline/lossless.jpg"; public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; + public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; + public const string ExtendedXmp = "Jpg/baseline/Extended-XMP.jpg"; public static readonly string[] All = { @@ -620,6 +623,7 @@ namespace SixLabors.ImageSharp.Tests public const string Earth = "Webp/earth_lossy.webp"; public const string WithExif = "Webp/exif_lossy.webp"; public const string WithIccp = "Webp/lossy_with_iccp.webp"; + public const string WithXmp = "webp/xmp_lossy.webp"; public const string BikeSmall = "Webp/bike_lossless_small.webp"; // Lossy images without macroblock filtering. diff --git a/tests/Images/Input/Jpg/baseline/Metadata-test-file.jpg b/tests/Images/Input/Jpg/baseline/Metadata-test-file.jpg new file mode 100644 index 000000000..160d7ebf8 --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/Metadata-test-file.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:800911efb6f0c796d61b5ea14fc67fe891aaae3c04a49cfd5b86e68958598436 +size 138810 diff --git a/tests/Images/Input/Jpg/baseline/extended-xmp.jpg b/tests/Images/Input/Jpg/baseline/extended-xmp.jpg new file mode 100644 index 000000000..6fc84b95e --- /dev/null +++ b/tests/Images/Input/Jpg/baseline/extended-xmp.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:000c67f210059b101570949e889846bb11d6bdc801bd641d7d26424ad9cd027f +size 623986 diff --git a/tests/Images/Input/Png/xmp-colorpalette.png b/tests/Images/Input/Png/xmp-colorpalette.png new file mode 100644 index 000000000..375879413 --- /dev/null +++ b/tests/Images/Input/Png/xmp-colorpalette.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb55607fd7de6a47d8dd242c1a7be9627c564821554db896ed46603d15963c06 +size 1025 diff --git a/tests/Images/Input/Webp/xmp_lossy.webp b/tests/Images/Input/Webp/xmp_lossy.webp new file mode 100644 index 000000000..4e92f280c --- /dev/null +++ b/tests/Images/Input/Webp/xmp_lossy.webp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:755a63652695d7e190f375c9c0697cd37c9b601cd54405c704ec8efc200e67fc +size 474772 From fa3e6ff1808fe1b75cbd4b87c0e6525901894d08 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 2 Jan 2022 14:18:36 +0100 Subject: [PATCH 433/511] More robust equals implementation --- .../Profiles/XMP/XElementEqualityComparer.cs | 21 +++++++ .../Metadata/Profiles/XMP/XmpProfile.cs | 56 ++++++++++++++----- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 53 +++++++++++++----- 3 files changed, 104 insertions(+), 26 deletions(-) create mode 100644 src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs b/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs new file mode 100644 index 000000000..156872792 --- /dev/null +++ b/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs @@ -0,0 +1,21 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Xml.Linq; + +namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp +{ + /// + /// Compare objects for Name and Value equality. + /// + public class XElementEqualityComparer : IEqualityComparer + { + /// + public bool Equals([AllowNull] XElement x, [AllowNull] XElement y) => x.Name == y.Name && x.Value == y.Value; + + /// + public int GetHashCode([DisallowNull] XElement obj) => obj.GetHashCode(); + } +} diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs index 7c9cfc55f..7261b4521 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Linq; using System.Text; using System.Xml.Linq; @@ -10,6 +11,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp { /// /// Represents an XMP profile, providing access to the raw XML. + /// See for the full specification. /// public sealed class XmpProfile : IDeepCloneable { @@ -29,6 +31,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// The UTF8 encoded byte array to read the XMP profile from. public XmpProfile(byte[] data) => this.Data = data; + /// + /// Initializes a new instance of the class, based on the speicief . + /// + /// The document to base this instance on. + public XmpProfile(XDocument doc) + { + this.document = doc; + this.UpdateData(); + } + /// /// Initializes a new instance of the class /// by making a copy from another XMP profile. @@ -78,7 +90,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp return true; } - if (ReferenceEquals(left, null)) + if (left is null) { return false; } @@ -95,10 +107,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// True if the parameter is not equal to the parameter; /// otherwise, false. /// - public static bool operator !=(XmpProfile left, XmpProfile right) - { - return !(left == right); - } + public static bool operator !=(XmpProfile left, XmpProfile right) => !(left == right); /// public XmpProfile DeepClone() => new(this); @@ -113,7 +122,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp return; } - using var stream = new MemoryStream(this.Data.Length); + int initialLength = 256; + if (this.Data != null) + { + initialLength = this.Data.Length; + } + + using var stream = new MemoryStream(initialLength); using var writer = new StreamWriter(stream, Encoding.UTF8); this.document.Save(writer); this.Data = stream.ToArray(); @@ -125,28 +140,37 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// public override bool Equals(object obj) { - XmpProfile other = obj as XmpProfile; - if (ReferenceEquals(other, null)) + if (obj is not XmpProfile other) { return false; } - if (ReferenceEquals(this.Data, null)) + XElement thisRoot = this.Document.Root; + XElement otherRoot = other.Document.Root; + + return this.CompareElements(thisRoot, otherRoot); + } + + private bool CompareElements(XElement left, XElement right) + { + var comparer = new XElementEqualityComparer(); + bool result = comparer.Equals(left, right); + if (result) { - return false; + result |= !left.Elements().Except(right.Elements(), comparer).Any(); } - return this.Data.Equals(other.Data); + return result; } private void InitializeDocument() { - if (this.document != null) + if (!(this.document is null)) { return; } - if (this.Data == null) + if (this.Data is null) { return; } @@ -164,6 +188,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp using var stream = new MemoryStream(this.Data, 0, count); using var reader = new StreamReader(stream, Encoding.UTF8); this.document = XDocument.Load(reader); + + // In case we removed any trailing bytes, update the Data property accordingly. + if (count != this.Data.Length) + { + this.UpdateData(); + } } } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index 371c110b7..5aa68f59d 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Text; using System.Xml.Linq; @@ -108,22 +109,36 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp } [Fact] - public void XmpProfile_EqualalityIsByValue() + public void XmpProfile_EqualityIsByValue() { // arrange - byte[] content = new byte[0]; - XmpProfile original = new XmpProfile(content); - XmpProfile other = new XmpProfile(content); + XmpProfile original = CreateMinimalXmlProfile(); + var other = new XmpProfile(original.Data); // act - var equals = original.Equals(other); - var equality = original == other; - var inequality = original != other; + bool equals = original.Equals(other); + bool equality = original == other; + bool inequality = original != other; // assert Assert.True(equals); Assert.True(equality); Assert.False(inequality); + Assert.Equal(original, other); + } + + [Fact] + public void XmpProfile_DocumentConstructor() + { + // arrange + XmpProfile original = CreateMinimalXmlProfile(); + + // act + var actual = new XmpProfile(original.Document); + + // assert + XmpProfileContainsExpectedValues(actual); + Assert.Equal(original, actual); } [Fact] @@ -147,7 +162,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { // arrange var image = new Image(1, 1); - image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; var encoder = new GifEncoder(); // act @@ -156,6 +172,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); + Assert.Equal(original, actual); } [Fact] @@ -163,7 +180,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { // arrange var image = new Image(1, 1); - image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; var encoder = new JpegEncoder(); // act @@ -172,6 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); + Assert.Equal(original, actual); } [Fact] @@ -180,6 +199,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // arrange var provider = TestImageProvider.File(TestImages.Jpeg.Baseline.ExtendedXmp); using Image image = await provider.GetImageAsync(JpegDecoder); + XmpProfile original = image.Metadata.XmpProfile; var encoder = new JpegEncoder(); // act @@ -188,6 +208,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); + Assert.Equal(original, actual); } [Fact] @@ -195,7 +216,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { // arrange var image = new Image(1, 1); - image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; var encoder = new PngEncoder(); // act @@ -204,6 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); + Assert.Equal(original, actual); } [Fact] @@ -211,7 +234,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { // arrange var image = new Image(1, 1); - image.Frames.RootFrame.Metadata.XmpProfile = CreateMinimalXmlProfile(); + XmpProfile original = CreateMinimalXmlProfile(); + image.Frames.RootFrame.Metadata.XmpProfile = original; var encoder = new TiffEncoder(); // act @@ -220,6 +244,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); + Assert.Equal(original, actual); } [Fact] @@ -227,7 +252,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { // arrange var image = new Image(1, 1); - image.Metadata.XmpProfile = CreateMinimalXmlProfile(); + XmpProfile original = CreateMinimalXmlProfile(); + image.Metadata.XmpProfile = original; var encoder = new WebpEncoder(); // act @@ -236,6 +262,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); + Assert.Equal(original, actual); } private static void XmpProfileContainsExpectedValues(XmpProfile xmp) @@ -249,7 +276,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp private static XmpProfile CreateMinimalXmlProfile() { - string content = ""; + string content = $""; byte[] data = Encoding.UTF8.GetBytes(content); var profile = new XmpProfile(data); return profile; From 32aa75b3457545ab6649d26fb2094bc2794a803f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 2 Jan 2022 15:06:41 +0100 Subject: [PATCH 434/511] Fix build on .NET core 2.1 --- .../Metadata/Profiles/XMP/XElementEqualityComparer.cs | 5 ++--- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 10 +++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs b/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs index 156872792..231b3f5dd 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs +++ b/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Xml.Linq; namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp @@ -13,9 +12,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp public class XElementEqualityComparer : IEqualityComparer { /// - public bool Equals([AllowNull] XElement x, [AllowNull] XElement y) => x.Name == y.Name && x.Value == y.Value; + public bool Equals(XElement x, XElement y) => x.Name == y.Name && x.Value == y.Value; /// - public int GetHashCode([DisallowNull] XElement obj) => obj.GetHashCode(); + public int GetHashCode(XElement obj) => obj.GetHashCode(); } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index 5aa68f59d..4ef5fd774 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -19,15 +19,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { public class XmpProfileTests { - private static GifDecoder GifDecoder => new GifDecoder() { IgnoreMetadata = false }; + private static GifDecoder GifDecoder => new() { IgnoreMetadata = false }; - private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + private static JpegDecoder JpegDecoder => new() { IgnoreMetadata = false }; - private static PngDecoder PngDecoder => new PngDecoder() { IgnoreMetadata = false }; + private static PngDecoder PngDecoder => new() { IgnoreMetadata = false }; - private static TiffDecoder TiffDecoder => new TiffDecoder() { IgnoreMetadata = false }; + private static TiffDecoder TiffDecoder => new() { IgnoreMetadata = false }; - private static WebpDecoder WebpDecoder => new WebpDecoder() { IgnoreMetadata = false }; + private static WebpDecoder WebpDecoder => new() { IgnoreMetadata = false }; [Theory] [WithFile(TestImages.Gif.Receipt, PixelTypes.Rgba32)] From 06beffb2db9b7a7667cc3a09e98512d96dd811eb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 2 Jan 2022 21:34:58 +0100 Subject: [PATCH 435/511] Performance improvements --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 7 +------ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 15 +-------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 3493e52c7..b6348803a 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -263,12 +263,7 @@ namespace SixLabors.ImageSharp.Formats.Gif if (appLength == GifConstants.ApplicationBlockSize) { this.stream.Read(this.buffer, 0, GifConstants.ApplicationBlockSize); - bool isXmp = true; - ReadOnlySpan idBytes = GifConstants.XmpApplicationIdentificationBytes; - for (int i = 0; i < idBytes.Length; i++) - { - isXmp &= this.buffer[i] == idBytes[i]; - } + bool isXmp = this.buffer.AsSpan().StartsWith(GifConstants.XmpApplicationIdentificationBytes); if (isXmp) { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 891a16fc7..b32e79299 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1371,20 +1371,7 @@ namespace SixLabors.ImageSharp.Formats.Png return true; } - private bool IsXmpTextData(ReadOnlySpan keywordBytes) - { - ReadOnlySpan expected = PngConstants.XmpKeyword; - bool result = keywordBytes.Length == expected.Length; - if (result) - { - for (int i = 0; i < keywordBytes.Length; i++) - { - result |= keywordBytes[i] == expected[i]; - } - } - - return result; - } + private bool IsXmpTextData(ReadOnlySpan keywordBytes) => keywordBytes.SequenceEqual(PngConstants.XmpKeyword); private void SwapScanlineBuffers() { From 76261ff808d4b78cee3c392c0d0418c510986909 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 3 Jan 2022 22:23:52 +1100 Subject: [PATCH 436/511] 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 f4510132fceaa8266d0769d3567160f600577f1f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 3 Jan 2022 20:34:17 +0100 Subject: [PATCH 437/511] Use memory allocator in Gif encoder --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 4c2ef4647..fd1556eb3 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -427,11 +427,13 @@ namespace SixLabors.ImageSharp.Formats.Gif private void WriteExtension(TGifExtension extension, Stream stream) where TGifExtension : struct, IGifExtension { - byte[] buffer; + IMemoryOwner owner = null; + Span buffer; int extensionSize = extension.ContentLength; if (extensionSize > this.buffer.Length - 3) { - buffer = new byte[extensionSize + 3]; + owner = this.memoryAllocator.Allocate(extensionSize + 3); + buffer = owner.GetSpan(); } else { @@ -441,11 +443,12 @@ namespace SixLabors.ImageSharp.Formats.Gif buffer[0] = GifConstants.ExtensionIntroducer; buffer[1] = extension.Label; - extension.WriteTo(buffer.AsSpan(2)); + extension.WriteTo(buffer.Slice(2)); buffer[extensionSize + 2] = GifConstants.Terminator; stream.Write(buffer, 0, extensionSize + 3); + owner?.Dispose(); } /// From f64f2cc7191110936da53a0c64b30d82bd21ed25 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 3 Jan 2022 21:12:53 +0100 Subject: [PATCH 438/511] Skip over iTXT header --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index d9b4375c6..80c81fda2 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -662,6 +662,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// The image metadata. private void WriteXmpChunk(Stream stream, ImageMetadata meta) { + const int iTxtHeaderSize = 5; if (((this.options.ChunkFilter ?? PngChunkFilter.None) & PngChunkFilter.ExcludeTextChunks) == PngChunkFilter.ExcludeTextChunks) { return; @@ -682,12 +683,7 @@ namespace SixLabors.ImageSharp.Formats.Png byte[] payload = new byte[xmpData.Length + PngConstants.XmpKeyword.Length + 5]; PngConstants.XmpKeyword.CopyTo(payload); - int bytesWritten = PngConstants.XmpKeyword.Length; - payload[bytesWritten++] = 0; // Keyword string terminator - payload[bytesWritten++] = 0; // Compression flag - payload[bytesWritten++] = 0; // Compression method - payload[bytesWritten++] = 0; // Language tag - payload[bytesWritten++] = 0; // Translated keyword + int bytesWritten = PngConstants.XmpKeyword.Length + iTxtHeaderSize; xmpData.CopyTo(payload.AsSpan(bytesWritten)); this.WriteChunk(stream, PngChunkType.InternationalText, payload); } From 6dba6cf1f8fb07847d0e5036557679a202a4d9c6 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 3 Jan 2022 14:45:35 -0800 Subject: [PATCH 439/511] Moved HexStringToBytes into a SixLabors.ImageSharp.Common.Helpers.HexConverter. --- src/ImageSharp/Common/Helpers/HexConverter.cs | 98 +++++++++++++++++++ src/ImageSharp/Formats/Png/PngDecoderCore.cs | 92 +---------------- 2 files changed, 101 insertions(+), 89 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/HexConverter.cs diff --git a/src/ImageSharp/Common/Helpers/HexConverter.cs b/src/ImageSharp/Common/Helpers/HexConverter.cs new file mode 100644 index 000000000..c55e9bbd9 --- /dev/null +++ b/src/ImageSharp/Common/Helpers/HexConverter.cs @@ -0,0 +1,98 @@ +// Copyright (c) Six Labors. +// Licensed under the Apache License, Version 2.0. + +using System; +using System.Runtime.CompilerServices; + +namespace SixLabors.ImageSharp.Common.Helpers +{ + internal static class HexConverter + { + /// + /// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character. + /// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991. + /// + /// The hexadecimal string to parse. + /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. + /// The number of bytes written to . + public static int HexStringToBytes(ReadOnlySpan chars, Span bytes) + { + if ((chars.Length % 2) != 0) + { + throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars)); + } + + if ((bytes.Length * 2) < chars.Length) + { + throw new ArgumentException("Output span must be at least half the length of the input string"); + } + else + { + // Slightly better performance in the loop below, allows us to skip a bounds check + // while still supporting output buffers that are larger than necessary + bytes = bytes.Slice(0, chars.Length / 2); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static int FromChar(int c) + { + // Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. + // This doesn't actually allocate. + ReadOnlySpan charToHexLookup = new byte[] + { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 + 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 + 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 255 + }; + + return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; + } + + // See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361 + int i = 0; + int j = 0; + int byteLo = 0; + int byteHi = 0; + while (j < bytes.Length) + { + byteLo = FromChar(chars[i + 1]); + byteHi = FromChar(chars[i]); + + // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern + // is if either byteHi or byteLo was not a hex character. + if ((byteLo | byteHi) == 0xFF) + { + break; + } + + bytes[j++] = (byte)((byteHi << 4) | byteLo); + i += 2; + } + + if (byteLo == 0xFF) + { + i++; + } + + if ((byteLo | byteHi) == 0xFF) + { + throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars)); + } + + return j; + } + } +} diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 182d6e7be..708cd7f5f 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Threading; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Formats.Png.Chunks; using SixLabors.ImageSharp.Formats.Png.Filters; @@ -1093,7 +1094,7 @@ namespace SixLabors.ImageSharp.Formats.Png tempExifBuf = new byte[exifHeader.Length]; } - HexStringToBytes(dataSpan.Slice(0, exifHeader.Length * 2), tempExifBuf); + HexConverter.HexStringToBytes(dataSpan.Slice(0, exifHeader.Length * 2), tempExifBuf); if (!tempExifBuf.AsSpan().Slice(0, exifHeader.Length).SequenceEqual(exifHeader)) { // Exif header in the hex data is not valid @@ -1115,7 +1116,7 @@ namespace SixLabors.ImageSharp.Formats.Png lineSpan = dataSpan.Slice(0, newlineIndex); } - i += HexStringToBytes(lineSpan, exifBlob.AsSpan().Slice(i)); + i += HexConverter.HexStringToBytes(lineSpan, exifBlob.AsSpan().Slice(i)); dataSpan = dataSpan.Slice(newlineIndex + 1); } @@ -1163,93 +1164,6 @@ namespace SixLabors.ImageSharp.Formats.Png #pragma warning restore IDE0022 // Use expression body for methods } - /// - /// Parses a hexadecimal string into a byte array without allocations. Throws on non-hexadecimal character. - /// Adapted from https://source.dot.net/#System.Private.CoreLib/Convert.cs,c9e4fbeaca708991. - /// - /// The hexadecimal string to parse. - /// The destination for the parsed bytes. Must be at least .Length / 2 bytes long. - /// The number of bytes written to . - private static int HexStringToBytes(ReadOnlySpan chars, Span bytes) - { - if ((chars.Length % 2) != 0) - { - throw new ArgumentException("Input string length must be a multiple of 2", nameof(chars)); - } - - if ((bytes.Length * 2) < chars.Length) - { - throw new ArgumentException("Output span must be at least half the length of the input string"); - } - else - { - // Slightly better performance in the loop below, allows us to skip a bounds check - // while still supporting output buffers that are larger than necessary - bytes = bytes.Slice(0, chars.Length / 2); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static int FromChar(int c) - { - // Map from an ASCII char to its hex value, e.g. arr['b'] == 11. 0xFF means it's not a hex digit. - // This doesn't actually allocate. - ReadOnlySpan charToHexLookup = new byte[] - { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 15 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 31 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 47 - 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 63 - 0xFF, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 79 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 95 - 0xFF, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 111 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 127 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 143 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 159 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 175 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 191 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 207 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 223 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 239 - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 255 - }; - - return c >= charToHexLookup.Length ? 0xFF : charToHexLookup[c]; - } - - // See https://source.dot.net/#System.Private.CoreLib/HexConverter.cs,4681d45a0aa0b361 - int i = 0; - int j = 0; - int byteLo = 0; - int byteHi = 0; - while (j < bytes.Length) - { - byteLo = FromChar(chars[i + 1]); - byteHi = FromChar(chars[i]); - - // byteHi hasn't been shifted to the high half yet, so the only way the bitwise or produces this pattern - // is if either byteHi or byteLo was not a hex character. - if ((byteLo | byteHi) == 0xFF) - { - break; - } - - bytes[j++] = (byte)((byteHi << 4) | byteLo); - i += 2; - } - - if (byteLo == 0xFF) - { - i++; - } - - if ((byteLo | byteHi) == 0xFF) - { - throw new ArgumentException("Input string contained non-hexadecimal characters", nameof(chars)); - } - - return j; - } - /// /// Sets the in to , /// or copies exif tags if already contains an . From bdb69d1027c13d8b3ded75bb83703c1d6fb3d3e6 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 3 Jan 2022 15:03:18 -0800 Subject: [PATCH 440/511] Allow reading legacy exif data from uncompressed text chunks as well. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 49 ++++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 708cd7f5f..2a8f9614b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -188,7 +188,7 @@ namespace SixLabors.ImageSharp.Formats.Png this.AssignTransparentMarkers(alpha, pngMetadata); break; case PngChunkType.Text: - this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); + this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: this.ReadCompressedTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); @@ -298,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Png break; } - this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan()); + this.ReadTextChunk(metadata, pngMetadata, chunk.Data.GetSpan()); break; case PngChunkType.CompressedText: if (this.colorMetadataOnly) @@ -970,7 +970,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// The metadata to decode to. /// The containing the data. - private void ReadTextChunk(PngMetadata metadata, ReadOnlySpan data) + private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) { if (this.ignoreMetadata) { @@ -993,7 +993,10 @@ namespace SixLabors.ImageSharp.Formats.Png string value = PngConstants.Encoding.GetString(data.Slice(zeroIndex + 1)); - metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty)); + if (!this.TryReadSpecialTextData(baseMetadata, name, value)) + { + metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty)); + } } /// @@ -1030,19 +1033,35 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan compressedData = data.Slice(zeroIndex + 2); - if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed)) + if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed) && + !this.TryReadSpecialTextData(baseMetadata, name, uncompressed)) { - if (name.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase) && - this.TryReadLegacyExifTextChunk(baseMetadata, uncompressed)) - { - // Successfully parsed exif data stored as text in this chunk - } - else - { - // Seems to be regular old text data, or we failed to parse it in any special way - metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); - } + metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); + } + } + + /// + /// Checks if the given text chunk is actually storing parsable metadata. + /// + /// + /// + /// + /// True if metadata was successfully parsed from the text chunk. False if the + /// text chunk was not identified as metadata, and should be stored in the metadata + /// object unmodified. + private bool TryReadSpecialTextData(ImageMetadata baseMetadata, string chunkName, string chunkText) + { + if (chunkName.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase) && + this.TryReadLegacyExifTextChunk(baseMetadata, chunkText)) + { + // Successfully parsed legacy exif data from text + return true; } + + // TODO: "Raw profile type iptc", potentially others? + + // No special chunk data identified + return false; } /// From 6cdc595583fa6798c2fec04de8d016a4b13b4047 Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 3 Jan 2022 15:04:00 -0800 Subject: [PATCH 441/511] Merge with remote --- shared-infrastructure | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 7e7ea93882d7721dfcc7a83844e26a343bbd3a3d Mon Sep 17 00:00:00 2001 From: "WINDEV2110EVAL\\User" Date: Mon, 3 Jan 2022 15:12:34 -0800 Subject: [PATCH 442/511] Fixed comments. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 2a8f9614b..c9f0ce375 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -968,6 +968,7 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Reads a text chunk containing image properties from the data. /// + /// The object. /// The metadata to decode to. /// The containing the data. private void ReadTextChunk(ImageMetadata baseMetadata, PngMetadata metadata, ReadOnlySpan data) @@ -993,7 +994,7 @@ namespace SixLabors.ImageSharp.Formats.Png string value = PngConstants.Encoding.GetString(data.Slice(zeroIndex + 1)); - if (!this.TryReadSpecialTextData(baseMetadata, name, value)) + if (!this.TryReadTextChunkMetadata(baseMetadata, name, value)) { metadata.TextData.Add(new PngTextData(name, value, string.Empty, string.Empty)); } @@ -1034,7 +1035,7 @@ namespace SixLabors.ImageSharp.Formats.Png ReadOnlySpan compressedData = data.Slice(zeroIndex + 2); if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed) && - !this.TryReadSpecialTextData(baseMetadata, name, uncompressed)) + !this.TryReadTextChunkMetadata(baseMetadata, name, uncompressed)) { metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); } @@ -1043,13 +1044,13 @@ namespace SixLabors.ImageSharp.Formats.Png /// /// Checks if the given text chunk is actually storing parsable metadata. /// - /// - /// - /// + /// The object to store the parsed metadata in. + /// The name of the text chunk. + /// The contents of the text chunk. /// True if metadata was successfully parsed from the text chunk. False if the /// text chunk was not identified as metadata, and should be stored in the metadata /// object unmodified. - private bool TryReadSpecialTextData(ImageMetadata baseMetadata, string chunkName, string chunkText) + private bool TryReadTextChunkMetadata(ImageMetadata baseMetadata, string chunkName, string chunkText) { if (chunkName.Equals("Raw profile type exif", StringComparison.OrdinalIgnoreCase) && this.TryReadLegacyExifTextChunk(baseMetadata, chunkText)) From c50935f5ca30652437a776642bea2330658f0213 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 4 Jan 2022 06:21:26 +0300 Subject: [PATCH 443/511] Fixed runtime error --- .../Formats/Jpeg/Components/FastFloatingPointDCT.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs index 81bfe2135..c27ad5b82 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs @@ -146,11 +146,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// /// Apply floating point IDCT inplace using API. /// + /// + /// This method can be used even if there's no SIMD intrinsics available + /// as can be compiled to scalar instructions. + /// /// Input block. private static void IDCT_Vector4(ref Block8x8F transposedBlock) { - DebugGuard.IsTrue(Vector.IsHardwareAccelerated, "Scalar implementation should be called for non-accelerated hardware."); - // First pass - process columns IDCT8x4_Vector4(ref transposedBlock.V0L); IDCT8x4_Vector4(ref transposedBlock.V0R); From c719cf2d718e9e58997e947bcaba8e5a2009fda8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 4 Jan 2022 08:21:53 +0300 Subject: [PATCH 444/511] Fixed intrinsic flag in DCT tests --- tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 85f30d28d..75572b943 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -149,12 +149,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // 4 paths: // 1. AllowAll - call avx/fma implementation // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call sse Vector4 implementation - // 4. DisableHWIntrinsic - call scalar fallback implementation + // 3. DisableAvx - call sse implementation + // 4. DisableSIMD - call Vector4 fallback implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSIMD); } // Forward transform @@ -197,12 +197,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // 4 paths: // 1. AllowAll - call avx/fma implementation // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call sse Vector4 implementation - // 4. DisableHWIntrinsic - call scalar fallback implementation + // 3. DisableAvx - call sse implementation + // 4. DisableSIMD - call Vector4 fallback implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, - HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic); + HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSIMD); } } } From d61b845fd93fff6875638966bcc1b97445ccc3fb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Tue, 4 Jan 2022 08:22:44 +0300 Subject: [PATCH 445/511] Fixed FDCT test comment --- tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs index 75572b943..36570ce55 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs @@ -197,8 +197,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // 4 paths: // 1. AllowAll - call avx/fma implementation // 2. DisableFMA - call avx without fma implementation - // 3. DisableAvx - call sse implementation - // 4. DisableSIMD - call Vector4 fallback implementation + // 3. DisableAvx - call Vector4 implementation + // 4. DisableSIMD - call scalar fallback implementation FeatureTestRunner.RunWithHwIntrinsicsFeature( RunTest, seed, From 95318b1d9d93d84dc295e0f648d8e2bd6f0d7b4d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 4 Jan 2022 22:55:56 +1100 Subject: [PATCH 446/511] 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 7d3921aa74ce04ca4092d4fb1db0925377900cd0 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 4 Jan 2022 15:08:49 +0300 Subject: [PATCH 447/511] cleanup --- .../Metadata/Profiles/Exif/ExifReader.cs | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs index 4fcac5299..2fcd1cc07 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs @@ -224,17 +224,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } - ////protected void ReadSubIfd64(List values) - ////{ - //// if (this.subIfds is not null) - //// { - //// foreach (ulong subIfdOffset in this.subIfds) - //// { - //// this.ReadValues64(values, subIfdOffset); - //// } - //// } - ////} - private static TDataType[] ToArray(ExifDataType dataType, ReadOnlySpan data, ConverterMethod converter) { int dataTypeSize = (int)ExifDataTypes.GetSize(dataType); @@ -479,15 +468,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif case ExifTagValue.TileByteCounts: exifValue = new ExifLong8Array(ExifTagValue.TileByteCounts); break; - ////case ExifTagValue.SubIFDOffset: - //// exifValue = new ExifLong8(ExifTagValue.SubIFDOffset); - //// break; - ////case ExifTagValue.GPSIFDOffset: - //// exifValue = new ExifLong8(ExifTagValue.GPSIFDOffset); - //// break; - ////case ExifTagValue.SubIFDs: - //// exifValue = new ExifLong8Array(ExifTagValue.SubIFDs); - //// break; default: exifValue = ExifValues.Create(tag) ?? ExifValues.Create(tag, dataType, numberOfComponents); break; @@ -543,14 +523,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { this.AddSubIfd(value); } - else if (exif.Tag == ExifTag.SubIFDs) - { - //// didn't find any useful data in SubIFDs - ////foreach (object val in (Array)value) - ////{ - //// this.AddSubIfd(val); - ////} - } else { values.Add(exif); From 46f976a9b6c84b468fe41bc43a42a816dc99bd35 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 4 Jan 2022 15:09:11 +0300 Subject: [PATCH 448/511] comment --- tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs index 1493060d5..9f5b78cc3 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/BigTiffMetadataTests.cs @@ -166,7 +166,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { { new ExifTag((ExifTagValue)0xdd11), (ExifDataType.Long8, ulong.MaxValue) }, { new ExifTag((ExifTagValue)0xdd12), (ExifDataType.SignedLong8, long.MaxValue) }, - //// WriteIfdTags64Bit: arrays aren't support + //// WriteIfdTags64Bit: arrays aren't support (by our code) ////{ new ExifTag((ExifTagValue)0xdd13), (ExifDataType.Long8, new ulong[] { 0, 1234, 56789UL, ulong.MaxValue }) }, ////{ new ExifTag((ExifTagValue)0xdd14), (ExifDataType.SignedLong8, new long[] { -1234, 56789L, long.MaxValue }) }, }; From 62fc16224e72f558243e6207b22381afe9262ad2 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 4 Jan 2022 15:10:03 +0300 Subject: [PATCH 449/511] HTML -> MD --- .../Input/Tiff/BigTiff/BigTIFFSamples.html | 239 ------------------ .../Input/Tiff/BigTiff/BigTIFFSamples.md | 220 ++++++++++++++++ tests/Images/Input/Tiff/BigTiff/readme.md | 6 +- 3 files changed, 224 insertions(+), 241 deletions(-) delete mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html create mode 100644 tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html deleted file mode 100644 index c674e57b5..000000000 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - -

- These images were created by AWare Systems. -

-

Index

-

- Classic.tif
- BigTIFF.tif
- BigTIFFMotorola.tif
- BigTIFFLong.tif
- BigTIFFLong8.tif
- BigTIFFMotorolaLongStrips.tif
- BigTIFFLong8Tiles.tif
- BigTIFFSubIFD4.tif
- BigTIFFSubIFD8.tif
-

-

Classic.tif

-

- Classic.tif is a basic Classic TIFF file. All files in this package have the same actual image content, - so this TIFF file serves as a reference. -

-

- Format: Classic TIFF
- Byte Order: Intel
- Ifd Offset: 12302
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 8
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Short): 12288
-

-

BigTIFF.tif

-

- BigTIFF.tif ressembles Classic.tif as close as possible. Except that it's a BigTIFF, that is... -

-

- Format: BigTIFF
- Byte Order: Intel
- Ifd Offset: 12304
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 16
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Short): 12288
-

-

BigTIFFMotorola.tif

-

- BigTIFFMotorola.tif reverses the byte order. -

-

- Format: BigTIFF
- Byte Order: Motorola
- Ifd Offset: 12304
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 16
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Short): 12288
-

-

BigTIFFLong.tif

-

- All previous TIFFs specify DataType Short for StripOffsets and StripByteCounts tags. This BigTIFF instead specifies DataType Long, for these tags. -

-

- Format: BigTIFF
- Byte Order: Intel
- Ifd Offset: 12304
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Long): 16
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Long): 12288
-

-

BigTIFFLong8.tif

-

- This next one specifies DataType Long8, for StripOffsets and StripByteCounts tags. -

-

- Format: BigTIFF
- Byte Order: Intel
- Ifd Offset: 12304
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Long8): 16
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Long8): 12288
-

-

BigTIFFMotorolaLongStrips.tif

-

- This BigTIFF has Motorola byte order, plus, it's divided over two strips. StripOffsets and StripByteCounts tags have DataType Long, so their actual value fits inside the IFD. -

-

- Format: BigTIFF
- Byte Order: Motorola
- Ifd Offset: 12304
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (2 Long): 16, 6160
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 32
-     StripByteCounts (2 Long): 6144, 6144
-

-

BigTIFFLong8Tiles.tif

-

- BigTIFFLong8Tiles.tif is a tiled BigTIFF. TileOffsets and TileByteCounts tags specify DataType Long8. -

-

- Format: BigTIFF
- Byte Order: Intel
- Ifd Offset: 12368
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     SamplesPerPixel (1 Short): 3
-     TileWidth (1 Short): 32
-     TileLength (1 Short): 32
-     TileOffsets (4 Long8): 16, 3088, 6160, 9232
-     TileByteCounts (4 Long8): 3072, 3072, 3072, 3072
-

-

BigTIFFSubIFD4.tif

-

- This BigTIFF contains two pages, the second page showing almost the same image content as the first, except that the black square is white, and text color is black. - Both pages point to a downsample SubIFD, using SubIFDs DataType TIFF_IFD. -

-

- Format: BigTIFF
- Byte Order: Intel
- Ifd Offset: 15572
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 3284
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Short): 12288
-     SubIFDs (1 IFD): 3088
- SubIfd Offset: 3088
-     NewSubFileType (1 Long): 1
-     ImageWidth (1 Short): 32
-     ImageLength (1 Short): 32
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 16
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 32
-     StripByteCounts (1 Short): 3072
- Ifd Offset: 31324
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 19036
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Short): 12288
-     SubIFDs (1 IFD): 18840
- SubIfd Offset: 18840
-     NewSubFileType (1 Long): 1
-     ImageWidth (1 Short): 32
-     ImageLength (1 Short): 32
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 15768
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 32
-     StripByteCounts (1 Short): 3072
-

-

BigTIFFSubIFD8.tif

-

- BigTIFFSubIFD4.tif is very much the same as BigTIFFSubIFD4.tif, except that the new DataType TIFF_IFD8 is used for the SubIFDs tag. -

-

- Format: BigTIFF
- Byte Order: Intel
- Ifd Offset: 15572
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 3284
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Short): 12288
-     SubIFDs (1 IFD8): 3088
- SubIfd Offset: 3088
-     NewSubFileType (1 Long): 1
-     ImageWidth (1 Short): 32
-     ImageLength (1 Short): 32
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 16
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 32
-     StripByteCounts (1 Short): 3072
- Ifd Offset: 31324
-     ImageWidth (1 Short): 64
-     ImageLength (1 Short): 64
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 19036
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 64
-     StripByteCounts (1 Short): 12288
-     SubIFDs (1 IFD8): 18840
- SubIfd Offset: 18840
-     NewSubFileType (1 Long): 1
-     ImageWidth (1 Short): 32
-     ImageLength (1 Short): 32
-     BitsPerSample (3 Short): 8, 8, 8
-     PhotometricInterpretation (1 Short): RGB
-     StripOffsets (1 Short): 15768
-     SamplesPerPixel (1 Short): 3
-     RowsPerStrip (1 Short): 32
-     StripByteCounts (1 Short): 3072
-

- - diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md new file mode 100644 index 000000000..c3047009e --- /dev/null +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md @@ -0,0 +1,220 @@ +These images were created by [AWare Systems](http://www.awaresystems.be/). + +# Index + +[Classic.tif](#classic) +[BigTIFF.tif](#bigtiff) +[BigTIFFMotorola.tif](#bigtiffmotorola) +[BigTIFFLong.tif](#bigtifflong) +[BigTIFFLong8.tif](#bigtifflong8) +[BigTIFFMotorolaLongStrips.tif](#bigtiffmotorolalongstrips) +[BigTIFFLong8Tiles.tif](#bigtifflong8tiles) +[BigTIFFSubIFD4.tif](#bigtiffsubifd4) +[BigTIFFSubIFD8.tif](#bigtiffsubifd8) + +# Classic.tif + +Classic.tif is a basic Classic TIFF file. All files in this package have the same actual image content, so this TIFF file serves as a reference. + +Format: Classic TIFF +Byte Order: Intel +Ifd Offset: 12302 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 8 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Short): 12288 + +# BigTIFF.tif + +BigTIFF.tif ressembles Classic.tif as close as possible. Except that it's a BigTIFF, that is... + +Format: BigTIFF +Byte Order: Intel +Ifd Offset: 12304 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 16 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Short): 12288 + +# BigTIFFMotorola.tif + +BigTIFFMotorola.tif reverses the byte order. + +Format: BigTIFF +Byte Order: Motorola +Ifd Offset: 12304 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 16 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Short): 12288 + +# BigTIFFLong.tif + +All previous TIFFs specify DataType Short for StripOffsets and StripByteCounts tags. This BigTIFF instead specifies DataType Long, for these tags. + +Format: BigTIFF +Byte Order: Intel +Ifd Offset: 12304 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Long): 16 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Long): 12288 + +# BigTIFFLong8.tif + +This next one specifies DataType Long8, for StripOffsets and StripByteCounts tags. + +Format: BigTIFF +Byte Order: Intel +Ifd Offset: 12304 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Long8): 16 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Long8): 12288 + +# BigTIFFMotorolaLongStrips.tif + +This BigTIFF has Motorola byte order, plus, it's divided over two strips. StripOffsets and StripByteCounts tags have DataType Long, so their actual value fits inside the IFD. + +Format: BigTIFF +Byte Order: Motorola +Ifd Offset: 12304 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (2 Long): 16, 6160 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 32 +    StripByteCounts (2 Long): 6144, 6144 + +# BigTIFFLong8Tiles.tif + +BigTIFFLong8Tiles.tif is a tiled BigTIFF. TileOffsets and TileByteCounts tags specify DataType Long8. + +Format: BigTIFF +Byte Order: Intel +Ifd Offset: 12368 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    SamplesPerPixel (1 Short): 3 +    TileWidth (1 Short): 32 +    TileLength (1 Short): 32 +    TileOffsets (4 Long8): 16, 3088, 6160, 9232 +    TileByteCounts (4 Long8): 3072, 3072, 3072, 3072 + +# BigTIFFSubIFD4.tif + +This BigTIFF contains two pages, the second page showing almost the same image content as the first, except that the black square is white, and text color is black. Both pages point to a downsample SubIFD, using SubIFDs DataType TIFF_IFD. + +Format: BigTIFF +Byte Order: Intel +Ifd Offset: 15572 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 3284 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Short): 12288 +    SubIFDs (1 IFD): 3088 +SubIfd Offset: 3088 +    NewSubFileType (1 Long): 1 +    ImageWidth (1 Short): 32 +    ImageLength (1 Short): 32 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 16 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 32 +    StripByteCounts (1 Short): 3072 +Ifd Offset: 31324 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 19036 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Short): 12288 +    SubIFDs (1 IFD): 18840 +SubIfd Offset: 18840 +    NewSubFileType (1 Long): 1 +    ImageWidth (1 Short): 32 +    ImageLength (1 Short): 32 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 15768 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 32 +    StripByteCounts (1 Short): 3072 + +# BigTIFFSubIFD8.tif + +BigTIFFSubIFD4.tif is very much the same as BigTIFFSubIFD4.tif, except that the new DataType TIFF_IFD8 is used for the SubIFDs tag. + +Format: BigTIFF +Byte Order: Intel +Ifd Offset: 15572 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 3284 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Short): 12288 +    SubIFDs (1 IFD8): 3088 +SubIfd Offset: 3088 +    NewSubFileType (1 Long): 1 +    ImageWidth (1 Short): 32 +    ImageLength (1 Short): 32 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 16 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 32 +    StripByteCounts (1 Short): 3072 +Ifd Offset: 31324 +    ImageWidth (1 Short): 64 +    ImageLength (1 Short): 64 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 19036 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 64 +    StripByteCounts (1 Short): 12288 +    SubIFDs (1 IFD8): 18840 +SubIfd Offset: 18840 +    NewSubFileType (1 Long): 1 +    ImageWidth (1 Short): 32 +    ImageLength (1 Short): 32 +    BitsPerSample (3 Short): 8, 8, 8 +    PhotometricInterpretation (1 Short): RGB +    StripOffsets (1 Short): 15768 +    SamplesPerPixel (1 Short): 3 +    RowsPerStrip (1 Short): 32 +    StripByteCounts (1 Short): 3072 \ No newline at end of file diff --git a/tests/Images/Input/Tiff/BigTiff/readme.md b/tests/Images/Input/Tiff/BigTiff/readme.md index be5d28906..80f6b595b 100644 --- a/tests/Images/Input/Tiff/BigTiff/readme.md +++ b/tests/Images/Input/Tiff/BigTiff/readme.md @@ -1,3 +1,5 @@ -BigTIFF samples. +#### BigTIFF samples. -Downloaded from AWARE SYSTEMS: https://www.awaresystems.be/imaging/tiff/bigtiff.html \ No newline at end of file +For details: BigTIFFSamples.md + +Downloaded from https://www.awaresystems.be/imaging/tiff/bigtiff.html \ No newline at end of file From feb49798bb316635c87a116da2974faf9e98cc14 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 4 Jan 2022 15:35:06 +0300 Subject: [PATCH 450/511] format --- .../Input/Tiff/BigTiff/BigTIFFSamples.md | 18 +++++++++--------- tests/Images/Input/Tiff/BigTiff/readme.md | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md index c3047009e..0401109fd 100644 --- a/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md +++ b/tests/Images/Input/Tiff/BigTiff/BigTIFFSamples.md @@ -2,15 +2,15 @@ These images were created by [AWare Systems](http://www.awaresystems.be/). # Index -[Classic.tif](#classic) -[BigTIFF.tif](#bigtiff) -[BigTIFFMotorola.tif](#bigtiffmotorola) -[BigTIFFLong.tif](#bigtifflong) -[BigTIFFLong8.tif](#bigtifflong8) -[BigTIFFMotorolaLongStrips.tif](#bigtiffmotorolalongstrips) -[BigTIFFLong8Tiles.tif](#bigtifflong8tiles) -[BigTIFFSubIFD4.tif](#bigtiffsubifd4) -[BigTIFFSubIFD8.tif](#bigtiffsubifd8) +[Classic.tif](#classictif) +[BigTIFF.tif](#bigtifftif) +[BigTIFFMotorola.tif](#bigtiffmotorolatif) +[BigTIFFLong.tif](#bigtifflongtif) +[BigTIFFLong8.tif](#bigtifflong8tif) +[BigTIFFMotorolaLongStrips.tif](#bigtiffmotorolalongstripstif) +[BigTIFFLong8Tiles.tif](#bigtifflong8tilestif) +[BigTIFFSubIFD4.tif](#bigtiffsubifd4tif) +[BigTIFFSubIFD8.tif](#bigtiffsubifd8tif) # Classic.tif diff --git a/tests/Images/Input/Tiff/BigTiff/readme.md b/tests/Images/Input/Tiff/BigTiff/readme.md index 80f6b595b..29f9c8ea9 100644 --- a/tests/Images/Input/Tiff/BigTiff/readme.md +++ b/tests/Images/Input/Tiff/BigTiff/readme.md @@ -1,5 +1,5 @@ #### BigTIFF samples. -For details: BigTIFFSamples.md +For details: [BigTIFFSamples.md](BigTIFFSamples.md) Downloaded from https://www.awaresystems.be/imaging/tiff/bigtiff.html \ No newline at end of file From 4f199ff73a53088f5e3bb6e1574084d9373e0e1f Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 4 Jan 2022 16:20:42 +0300 Subject: [PATCH 451/511] 4 byte IFD pointer --- src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index e7a01b070..00ed4adb3 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif // two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; - length += 4 + 2; + length += 4 + 4; var result = new byte[length]; @@ -103,7 +103,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif i = this.WriteData(startIndex, this.gpsValues, result, i); } - WriteUInt16(0, result, i); + WriteUInt32(0, result, i); return result; } From 4491343ee8d9426d2c91ed6dcf9c6351bb87f760 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 4 Jan 2022 16:47:08 +0300 Subject: [PATCH 452/511] test fix --- .../ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index ebc096852..18d53e9e4 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -420,7 +420,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime)); byte[] bytes = profile.ToByteArray(); - Assert.Equal(525, bytes.Length); + Assert.Equal(527, bytes.Length); var profile2 = new ExifProfile(bytes); Assert.Equal(25, profile2.Values.Count); From ded8b20b5c1b5ead318fd47817fad75f15c5b58b Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 4 Jan 2022 21:27:56 +0100 Subject: [PATCH 453/511] Read only API for XmpProfile --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 3 +- .../Sections/GifXmpApplicationExtension.cs | 1 + .../Formats/Jpeg/JpegEncoderCore.cs | 3 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 3 +- .../Tiff/TiffEncoderEntriesCollector.cs | 3 +- .../Formats/Webp/BitWriter/Vp8BitWriter.cs | 2 +- .../Formats/Webp/BitWriter/Vp8LBitWriter.cs | 2 +- .../Metadata/Profiles/XMP/XmpProfile.cs | 165 +++--------------- .../Formats/Tiff/TiffMetadataTests.cs | 6 +- .../Formats/WebP/WebpMetaDataTests.cs | 2 +- .../Metadata/ImageFrameMetadataTests.cs | 2 +- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 67 ++----- 12 files changed, 53 insertions(+), 206 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index fd1556eb3..c359653cb 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -338,8 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // Application Extension: XMP Profile. if (xmpProfile != null) { - xmpProfile.UpdateData(); - var xmpExtension = new GifXmpApplicationExtension(xmpProfile.Data); + var xmpExtension = new GifXmpApplicationExtension(xmpProfile.ToByteArray()); this.WriteExtension(xmpExtension, stream); } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs index c41ec5894..236508fe9 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifXmpApplicationExtension.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using SixLabors.ImageSharp.Metadata.Profiles.Xmp; namespace SixLabors.ImageSharp.Formats.Gif { diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index e90c6473e..b1125c8e4 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -485,8 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg const int Max = 65533; const int MaxData = Max - XmpOverheadLength; - xmpProfile.UpdateData(); - byte[] data = xmpProfile.Data; + byte[] data = xmpProfile.ToByteArray(); if (data is null || data.Length == 0) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 80c81fda2..3bb63a24b 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -673,8 +673,7 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - meta.XmpProfile.UpdateData(); - var xmpData = meta.XmpProfile.Data; + var xmpData = meta.XmpProfile.ToByteArray(); if (xmpData.Length == 0) { diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index c5fe395a0..8be029788 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -202,10 +202,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff if (xmpProfile != null) { - xmpProfile.UpdateData(); var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) { - Value = xmpProfile.Data + Value = xmpProfile.ToByteArray() }; this.Collector.Add(xmp); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 4e91bedb0..26942472f 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs @@ -427,7 +427,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { isVp8X = true; riffSize += ExtendedFileChunkSize; - xmpBytes = xmpProfile.Data; + xmpBytes = xmpProfile.ToByteArray(); riffSize += this.MetadataChunkSize(xmpBytes); } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index d41224f90..d4d4f196c 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { isVp8X = true; riffSize += ExtendedFileChunkSize; - xmpBytes = xmpProfile.Data; + xmpBytes = xmpProfile.ToByteArray(); riffSize += this.MetadataChunkSize(xmpBytes); } diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs index 7261b4521..144d93774 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Linq; using System.Text; using System.Xml.Linq; @@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp ///
public sealed class XmpProfile : IDeepCloneable { - private XDocument document; + private byte[] data; /// /// Initializes a new instance of the class. @@ -29,17 +28,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// Initializes a new instance of the class. /// /// The UTF8 encoded byte array to read the XMP profile from. - public XmpProfile(byte[] data) => this.Data = data; - - /// - /// Initializes a new instance of the class, based on the speicief . - /// - /// The document to base this instance on. - public XmpProfile(XDocument doc) - { - this.document = doc; - this.UpdateData(); - } + public XmpProfile(byte[] data) => this.data = data; /// /// Initializes a new instance of the class @@ -50,150 +39,48 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp { Guard.NotNull(other, nameof(other)); - if (other.Data != null) - { - this.Data = new byte[other.Data.Length]; - other.Data.AsSpan().CopyTo(this.Data); - } - } - - /// - /// Gets the rax XML document containing the XMP profile. - /// - public XDocument Document - { - get - { - this.InitializeDocument(); - return this.document; - } - } - - /// - /// Gets the byte data of the XMP profile. - /// - public byte[] Data { get; private set; } - - /// - /// Checks whether two structures are equal. - /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is equal to the parameter; - /// otherwise, false. - /// - public static bool operator ==(XmpProfile left, XmpProfile right) - { - if (ReferenceEquals(left, right)) - { - return true; - } - - if (left is null) - { - return false; - } - - return left.Equals(right); + this.data = other.ToByteArray(); } /// - /// Checks whether two structures are not equal. + /// Gets the raw XML document containing the XMP profile. /// - /// The left hand operand. - /// The right hand operand. - /// - /// True if the parameter is not equal to the parameter; - /// otherwise, false. - /// - public static bool operator !=(XmpProfile left, XmpProfile right) => !(left == right); - - /// - public XmpProfile DeepClone() => new(this); - - /// - /// Updates the data of the profile. - /// - public void UpdateData() + /// The + public XDocument GetDocument() { - if (this.document == null) + byte[] byteArray = this.ToByteArray(); + if (byteArray is null) { - return; - } - - int initialLength = 256; - if (this.Data != null) - { - initialLength = this.Data.Length; - } - - using var stream = new MemoryStream(initialLength); - using var writer = new StreamWriter(stream, Encoding.UTF8); - this.document.Save(writer); - this.Data = stream.ToArray(); - } - - /// - public override int GetHashCode() => base.GetHashCode(); - - /// - public override bool Equals(object obj) - { - if (obj is not XmpProfile other) - { - return false; - } - - XElement thisRoot = this.Document.Root; - XElement otherRoot = other.Document.Root; - - return this.CompareElements(thisRoot, otherRoot); - } - - private bool CompareElements(XElement left, XElement right) - { - var comparer = new XElementEqualityComparer(); - bool result = comparer.Equals(left, right); - if (result) - { - result |= !left.Elements().Except(right.Elements(), comparer).Any(); - } - - return result; - } - - private void InitializeDocument() - { - if (!(this.document is null)) - { - return; - } - - if (this.Data is null) - { - return; + return null; } // Strip leading whitespace, as the XmlReader doesn't like them. - int count = this.Data.Length; + int count = byteArray.Length; for (int i = count - 1; i > 0; i--) { - if (this.Data[i] is 0 or 0x0f) + if (byteArray[i] is 0 or 0x0f) { count--; } } - using var stream = new MemoryStream(this.Data, 0, count); + using var stream = new MemoryStream(byteArray, 0, count); using var reader = new StreamReader(stream, Encoding.UTF8); - this.document = XDocument.Load(reader); + return XDocument.Load(reader); + } - // In case we removed any trailing bytes, update the Data property accordingly. - if (count != this.Data.Length) - { - this.UpdateData(); - } + /// + /// Convert the content of this into a byte array. + /// + /// The + public byte[] ToByteArray() + { + byte[] result = new byte[this.data.Length]; + this.data.AsSpan().CopyTo(result); + return result; } + + /// + public XmpProfile DeepClone() => new(this); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 6a47a9577..8f5a5f76f 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); + Assert.Equal(2599, rootFrameMetaData.XmpProfile.ToByteArray().Length); Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); } } @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(32, rootFrame.Width); Assert.Equal(32, rootFrame.Height); Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); + Assert.Equal(2599, rootFrame.Metadata.XmpProfile.ToByteArray().Length); ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.NotNull(xmpProfileInput); Assert.NotNull(encodedImageXmpProfile); - Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); + Assert.Equal(xmpProfileInput.ToByteArray(), encodedImageXmpProfile.ToByteArray()); Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 7fba86b4f..79c4ee57a 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp else { Assert.NotNull(image.Metadata.XmpProfile); - Assert.NotEmpty(image.Metadata.XmpProfile.Data); + Assert.NotEmpty(image.Metadata.XmpProfile.ToByteArray()); } } diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index 17be08f13..e5918a9ea 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); - Assert.True(metaData.XmpProfile.Data.Length.Equals(clone.XmpProfile.Data.Length)); + Assert.True(metaData.XmpProfile.ToByteArray().Length.Equals(clone.XmpProfile.ToByteArray().Length)); Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index 4ef5fd774..f1c5edc54 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -92,53 +92,17 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp } [Fact] - public void XmpProfile_ToAndFromByteArray_Works() + public void XmpProfile_ToFromByteArray_ReturnsClone() { // arrange XmpProfile profile = CreateMinimalXmlProfile(); - profile.Document.Root.AddFirst(new XElement(XName.Get("written"))); + byte[] original = profile.ToByteArray(); // act - profile.UpdateData(); - byte[] profileBytes = profile.Data; - var profileFromBytes = new XmpProfile(profileBytes); + byte[] actual = profile.ToByteArray(); // assert - XmpProfileContainsExpectedValues(profileFromBytes); - Assert.Equal("written", ((XElement)profileFromBytes.Document.Root.FirstNode).Name); - } - - [Fact] - public void XmpProfile_EqualityIsByValue() - { - // arrange - XmpProfile original = CreateMinimalXmlProfile(); - var other = new XmpProfile(original.Data); - - // act - bool equals = original.Equals(other); - bool equality = original == other; - bool inequality = original != other; - - // assert - Assert.True(equals); - Assert.True(equality); - Assert.False(inequality); - Assert.Equal(original, other); - } - - [Fact] - public void XmpProfile_DocumentConstructor() - { - // arrange - XmpProfile original = CreateMinimalXmlProfile(); - - // act - var actual = new XmpProfile(original.Document); - - // assert - XmpProfileContainsExpectedValues(actual); - Assert.Equal(original, actual); + Assert.False(ReferenceEquals(original, actual)); } [Fact] @@ -146,15 +110,14 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp { // arrange XmpProfile profile = CreateMinimalXmlProfile(); - profile.Document.Root.AddFirst(new XElement(XName.Get("written"))); + byte[] original = profile.ToByteArray(); // act XmpProfile clone = profile.DeepClone(); - clone.Document.Root.AddFirst(new XElement(XName.Get("onlyonclone"))); + byte[] actual = clone.ToByteArray(); // assert - XmpProfileContainsExpectedValues(clone); - Assert.Equal("onlyonclone", ((XElement)clone.Document.Root.FirstNode).Name); + Assert.False(ReferenceEquals(original, actual)); } [Fact] @@ -172,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original, actual); + Assert.Equal(original.ToByteArray(), actual.ToByteArray()); } [Fact] @@ -190,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original, actual); + Assert.Equal(original.ToByteArray(), actual.ToByteArray()); } [Fact] @@ -208,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original, actual); + Assert.Equal(original.ToByteArray(), actual.ToByteArray()); } [Fact] @@ -226,7 +189,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original, actual); + Assert.Equal(original.ToByteArray(), actual.ToByteArray()); } [Fact] @@ -244,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original, actual); + Assert.Equal(original.ToByteArray(), actual.ToByteArray()); } [Fact] @@ -262,13 +225,13 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original, actual); + Assert.Equal(original.ToByteArray(), actual.ToByteArray()); } private static void XmpProfileContainsExpectedValues(XmpProfile xmp) { Assert.NotNull(xmp); - XDocument document = xmp.Document; + XDocument document = xmp.GetDocument(); Assert.NotNull(document); Assert.Equal("xmpmeta", document.Root.Name.LocalName); Assert.Equal("adobe:ns:meta/", document.Root.Name.NamespaceName); @@ -276,7 +239,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp private static XmpProfile CreateMinimalXmlProfile() { - string content = $""; + string content = $" "; byte[] data = Encoding.UTF8.GetBytes(content); var profile = new XmpProfile(data); return profile; From 12263f9e538f318d4920719f130810d60b9897c1 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 4 Jan 2022 21:33:53 +0100 Subject: [PATCH 454/511] Delete XElementEqualityComparer.cs --- .../Profiles/XMP/XElementEqualityComparer.cs | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs b/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs deleted file mode 100644 index 231b3f5dd..000000000 --- a/src/ImageSharp/Metadata/Profiles/XMP/XElementEqualityComparer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Apache License, Version 2.0. - -using System.Collections.Generic; -using System.Xml.Linq; - -namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp -{ - /// - /// Compare objects for Name and Value equality. - /// - public class XElementEqualityComparer : IEqualityComparer - { - /// - public bool Equals(XElement x, XElement y) => x.Name == y.Name && x.Value == y.Value; - - /// - public int GetHashCode(XElement obj) => obj.GetHashCode(); - } -} From b443e824a2598ed98ea6be8fc7155d180313c307 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Tue, 4 Jan 2022 21:51:25 +0100 Subject: [PATCH 455/511] Gif: Check on frame count before writing repeat count --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index c359653cb..3f51fa639 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -123,7 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Gif this.WriteComments(gifMetadata, stream); // Write application extensions. - this.WriteApplicationExtensions(stream, gifMetadata.RepeatCount, metadata.XmpProfile); + this.WriteApplicationExtensions(stream, image.Frames.Count, gifMetadata.RepeatCount, metadata.XmpProfile); if (useGlobalTable) { @@ -324,9 +324,10 @@ namespace SixLabors.ImageSharp.Formats.Gif /// Writes the application extension to the stream. /// /// The stream to write to. + /// The frame count fo this image. /// The animated image repeat count. /// The XMP metadata profile. Null if profile is not to be written. - private void WriteApplicationExtensions(Stream stream, ushort repeatCount, XmpProfile xmpProfile) + private void WriteApplicationExtensions(Stream stream, int frameCount, ushort repeatCount, XmpProfile xmpProfile) { // Application Extension: Loop repeat count. if (repeatCount != 1) From c682504b38915949ce8699a1bd7dbbf630370fe0 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 5 Jan 2022 19:55:42 +0300 Subject: [PATCH 456/511] Fix --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 10 +++++++++- src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4b68c39ea..73af42afd 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -472,7 +472,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg : JpegColorSpace.Cmyk; } - JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {componentCount}"); + JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); return default; } @@ -998,6 +998,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // 1 byte: Number of components byte componentCount = this.temp[5]; + // Validate: componentCount more than 4 can lead to a buffer overflow during stream + // reading so we must limit it to 4 + // We do not support jpeg images with more than 4 components anyway + if (componentCount > 4) + { + JpegThrowHelper.ThrowNotSupportedComponentCount(componentCount); + } + this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount); remaining -= length; diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs index 0292fbcab..b238e45ef 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs @@ -45,5 +45,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg [MethodImpl(InliningOptions.ColdPath)] public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for JPEG format."); + + [MethodImpl(InliningOptions.ColdPath)] + public static void ThrowNotSupportedComponentCount(int componentCount) => throw new NotSupportedException($"Images with {componentCount} components are not supported."); } } From 6b36d552c17f1d942de945da25dc078414c72bd4 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 5 Jan 2022 20:02:06 +0300 Subject: [PATCH 457/511] Added example image to the test suite --- tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 1 + .../issues/issue-1900-malformed-unsupported-255-components.jpg | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 tests/Images/Input/Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 08d8d9038..695a10ec3 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -193,6 +193,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Baseline.Lossless, PixelTypes.Rgba32)] + [WithFile(TestImages.Jpeg.Issues.MalformedUnsupportedComponentCount, PixelTypes.Rgba32)] public void ThrowsNotSupported_WithUnsupportedJpegs(TestImageProvider provider) where TPixel : unmanaged, IPixel { diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 3f28a6116..deed0b240 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -256,6 +256,7 @@ namespace SixLabors.ImageSharp.Tests public const string BadSubSampling1076 = "Jpg/issues/issue-1076-invalid-subsampling.jpg"; public const string IdentifyMultiFrame1211 = "Jpg/issues/issue-1221-identify-multi-frame.jpg"; public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; + public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg b/tests/Images/Input/Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg new file mode 100644 index 000000000..7b6d7c572 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3553f76b72b98986df13a7508cd073a30b7c846dc441d1d68a518bf7b93bc66 +size 3951 From 22ed6da3697852e381bde4915c9a3c9f7d0039ed Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Wed, 5 Jan 2022 20:13:33 +0300 Subject: [PATCH 458/511] Moved 'unrecoverable image' to 'unsupported' test case --- .../Formats/Jpg/JpegDecoderTests.Images.cs | 150 ++++++++++-------- .../Formats/Jpg/JpegDecoderTests.cs | 5 +- 2 files changed, 82 insertions(+), 73 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs index d82359e61..db5169a04 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs @@ -10,58 +10,72 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public partial class JpegDecoderTests { public static string[] BaselineTestJpegs = - { - TestImages.Jpeg.Baseline.Calliphora, - TestImages.Jpeg.Baseline.Cmyk, - TestImages.Jpeg.Baseline.Ycck, - TestImages.Jpeg.Baseline.Jpeg400, - TestImages.Jpeg.Baseline.Turtle420, - TestImages.Jpeg.Baseline.Testorig420, - TestImages.Jpeg.Baseline.Jpeg420Small, - TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, - TestImages.Jpeg.Baseline.Jpeg444, - TestImages.Jpeg.Baseline.Jpeg422, - TestImages.Jpeg.Baseline.Bad.BadEOF, - TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, - TestImages.Jpeg.Baseline.YcckSubsample1222, - TestImages.Jpeg.Baseline.Bad.BadRST, - TestImages.Jpeg.Issues.MultiHuffmanBaseline394, - TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, - TestImages.Jpeg.Issues.InvalidEOI695, - TestImages.Jpeg.Issues.ExifResizeOutOfRange696, - TestImages.Jpeg.Issues.InvalidAPP0721, - TestImages.Jpeg.Issues.ExifGetString750Load, - TestImages.Jpeg.Issues.ExifGetString750Transform, - TestImages.Jpeg.Issues.BadSubSampling1076, + { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Jpeg.Baseline.Cmyk, + TestImages.Jpeg.Baseline.Ycck, + TestImages.Jpeg.Baseline.Jpeg400, + TestImages.Jpeg.Baseline.Turtle420, + TestImages.Jpeg.Baseline.Testorig420, + TestImages.Jpeg.Baseline.Jpeg420Small, + TestImages.Jpeg.Issues.Fuzz.AccessViolationException922, + TestImages.Jpeg.Baseline.Jpeg444, + TestImages.Jpeg.Baseline.Jpeg422, + TestImages.Jpeg.Baseline.Bad.BadEOF, + TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, + TestImages.Jpeg.Baseline.YcckSubsample1222, + TestImages.Jpeg.Baseline.Bad.BadRST, + TestImages.Jpeg.Issues.MultiHuffmanBaseline394, + TestImages.Jpeg.Issues.ExifDecodeOutOfRange694, + TestImages.Jpeg.Issues.InvalidEOI695, + TestImages.Jpeg.Issues.ExifResizeOutOfRange696, + TestImages.Jpeg.Issues.InvalidAPP0721, + TestImages.Jpeg.Issues.ExifGetString750Load, + TestImages.Jpeg.Issues.ExifGetString750Transform, + TestImages.Jpeg.Issues.BadSubSampling1076, - // LibJpeg can open this despite the invalid density units. - TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825B, + // LibJpeg can open this despite the invalid density units. + TestImages.Jpeg.Issues.Fuzz.ArgumentOutOfRangeException825B, - // LibJpeg can open this despite incorrect colorspace metadata. - TestImages.Jpeg.Issues.IncorrectColorspace855, + // LibJpeg can open this despite incorrect colorspace metadata. + TestImages.Jpeg.Issues.IncorrectColorspace855, - // High depth images - TestImages.Jpeg.Baseline.Testorig12bit, - }; + // High depth images + TestImages.Jpeg.Baseline.Testorig12bit, + }; public static string[] ProgressiveTestJpegs = - { - TestImages.Jpeg.Progressive.Fb, - TestImages.Jpeg.Progressive.Progress, - TestImages.Jpeg.Progressive.Festzug, - TestImages.Jpeg.Progressive.Bad.BadEOF, - TestImages.Jpeg.Issues.BadCoeffsProgressive178, - TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, - TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, - TestImages.Jpeg.Issues.BadZigZagProgressive385, - TestImages.Jpeg.Progressive.Bad.ExifUndefType, - TestImages.Jpeg.Issues.NoEoiProgressive517, - TestImages.Jpeg.Issues.BadRstProgressive518, - TestImages.Jpeg.Issues.DhtHasWrongLength624, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B, - TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C - }; + { + TestImages.Jpeg.Progressive.Fb, + TestImages.Jpeg.Progressive.Progress, + TestImages.Jpeg.Progressive.Festzug, + TestImages.Jpeg.Progressive.Bad.BadEOF, + TestImages.Jpeg.Issues.BadCoeffsProgressive178, + TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159, + TestImages.Jpeg.Issues.MissingFF00ProgressiveBedroom159, + TestImages.Jpeg.Issues.BadZigZagProgressive385, + TestImages.Jpeg.Progressive.Bad.ExifUndefType, + TestImages.Jpeg.Issues.NoEoiProgressive517, + TestImages.Jpeg.Issues.BadRstProgressive518, + TestImages.Jpeg.Issues.DhtHasWrongLength624, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723A, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723B, + TestImages.Jpeg.Issues.OrderedInterleavedProgressive723C + }; + + public static string[] UnsupportedTestJpegs = + { + // Invalid componentCount value (2 or > 4) + TestImages.Jpeg.Issues.Fuzz.NullReferenceException823, + TestImages.Jpeg.Issues.MalformedUnsupportedComponentCount, + + // Arithmetic coding + TestImages.Jpeg.Baseline.ArithmeticCoding, + TestImages.Jpeg.Baseline.ArithmeticCodingProgressive, + + // Lossless jpeg + TestImages.Jpeg.Baseline.Lossless + }; public static string[] UnrecoverableTestJpegs = { @@ -70,7 +84,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.Fuzz.AccessViolationException798, TestImages.Jpeg.Issues.Fuzz.DivideByZeroException821, TestImages.Jpeg.Issues.Fuzz.DivideByZeroException822, - TestImages.Jpeg.Issues.Fuzz.NullReferenceException823, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824A, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824B, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824D, @@ -91,28 +104,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, }; - private static readonly Dictionary CustomToleranceValues = - new Dictionary - { - // Baseline: - [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, - [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, + private static readonly Dictionary CustomToleranceValues = new() + { + // Baseline: + [TestImages.Jpeg.Baseline.Calliphora] = 0.00002f / 100, + [TestImages.Jpeg.Baseline.Bad.BadEOF] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Bad.BadRST] = 0.0589f / 100, - [TestImages.Jpeg.Baseline.Jpeg422] = 0.0013f / 100, - [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, - [TestImages.Jpeg.Baseline.Jpeg420Small] = 0.287f / 100, - [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, + [TestImages.Jpeg.Baseline.Jpeg422] = 0.0013f / 100, + [TestImages.Jpeg.Baseline.Testorig420] = 0.38f / 100, + [TestImages.Jpeg.Baseline.Jpeg420Small] = 0.287f / 100, + [TestImages.Jpeg.Baseline.Turtle420] = 1.0f / 100, - // Progressive: - [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, - [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, - [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, - [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, - [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, - [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, - [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, - [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, - }; + // Progressive: + [TestImages.Jpeg.Issues.MissingFF00ProgressiveGirl159] = 0.34f / 100, + [TestImages.Jpeg.Issues.BadCoeffsProgressive178] = 0.38f / 100, + [TestImages.Jpeg.Progressive.Bad.BadEOF] = 0.3f / 100, + [TestImages.Jpeg.Progressive.Festzug] = 0.02f / 100, + [TestImages.Jpeg.Progressive.Fb] = 0.16f / 100, + [TestImages.Jpeg.Progressive.Progress] = 0.31f / 100, + [TestImages.Jpeg.Issues.BadZigZagProgressive385] = 0.23f / 100, + [TestImages.Jpeg.Progressive.Bad.ExifUndefType] = 0.011f / 100, + }; } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 695a10ec3..7a2446959 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -190,10 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } [Theory] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Baseline.Lossless, PixelTypes.Rgba32)] - [WithFile(TestImages.Jpeg.Issues.MalformedUnsupportedComponentCount, PixelTypes.Rgba32)] + [WithFileCollection(nameof(UnsupportedTestJpegs), PixelTypes.Rgba32)] public void ThrowsNotSupported_WithUnsupportedJpegs(TestImageProvider provider) where TPixel : unmanaged, IPixel { From 6cf7ab9b5babbf30c9dc85d6e2988211f78c2970 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 6 Jan 2022 15:19:27 +0300 Subject: [PATCH 459/511] Removed Configure() method --- .../Components/Decoder/HuffmanScanDecoder.cs | 25 ---------------- .../Jpeg/Components/Decoder/HuffmanTable.cs | 30 +++---------------- 2 files changed, 4 insertions(+), 51 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index ce5e5110b..20c5d4a9b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -167,18 +167,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int mcusPerLine = this.frame.McusPerLine; ref HuffmanScanBuffer buffer = ref this.scanBuffer; - // Pre-derive the huffman table to avoid in-loop checks. - for (int i = 0; i < this.componentsCount; i++) - { - int order = this.frame.ComponentOrder[i]; - JpegComponent component = this.components[order]; - - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - dcHuffmanTable.Configure(); - acHuffmanTable.Configure(); - } - for (int j = 0; j < mcusPerColumn; j++) { this.cancellationToken.ThrowIfCancellationRequested(); @@ -248,8 +236,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - dcHuffmanTable.Configure(); - acHuffmanTable.Configure(); for (int j = 0; j < h; j++) { @@ -347,15 +333,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int mcusPerLine = this.frame.McusPerLine; ref HuffmanScanBuffer buffer = ref this.scanBuffer; - // Pre-derive the huffman table to avoid in-loop checks. - for (int k = 0; k < this.componentsCount; k++) - { - int order = this.frame.ComponentOrder[k]; - JpegComponent component = this.components[order]; - ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - dcHuffmanTable.Configure(); - } - for (int j = 0; j < mcusPerColumn; j++) { for (int i = 0; i < mcusPerLine; i++) @@ -416,7 +393,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder if (this.SpectralStart == 0) { ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; - dcHuffmanTable.Configure(); for (int j = 0; j < h; j++) { @@ -444,7 +420,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder else { ref HuffmanTable acHuffmanTable = ref this.acHuffmanTables[component.ACHuffmanTableId]; - acHuffmanTable.Configure(); for (int j = 0; j < h; j++) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index f18c63627..2b2983461 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -13,13 +13,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder [StructLayout(LayoutKind.Sequential)] internal unsafe struct HuffmanTable { - private bool isConfigured; - - /// - /// Derived from the DHT marker. Sizes[k] = # of symbols with codes of length k bits; Sizes[0] is unused. - /// - public fixed byte Sizes[17]; - /// /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. /// @@ -62,20 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// The huffman values public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) { - this.isConfigured = false; - Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length); Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - } - - /// - /// Expands the HuffmanTable into its readable form. - /// - public void Configure() - { - if (this.isConfigured) - { - return; - } Span huffSize = stackalloc char[257]; Span huffCode = stackalloc uint[257]; @@ -84,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int p = 0; for (int j = 1; j <= 16; j++) { - int i = this.Sizes[j]; + int i = codeLengths[j]; while (i-- != 0) { huffSize[p++] = (char)j; @@ -113,10 +93,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder p = 0; for (int j = 1; j <= 16; j++) { - if (this.Sizes[j] != 0) + if (codeLengths[j] != 0) { this.ValOffset[j] = p - (int)huffCode[p]; - p += this.Sizes[j]; + p += codeLengths[j]; this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; @@ -142,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) { int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= this.Sizes[length]; i++, p++) + for (int i = 1; i <= codeLengths[length]; i++, p++) { // length = current code's length, p = its index in huffCode[] & Values[]. // Generate left-justified code followed by all possible bit sequences @@ -155,8 +135,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder } } } - - this.isConfigured = true; } } } From 2334bcf5eb3c28b992678dbe7de111825cbcf1c7 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Thu, 6 Jan 2022 17:38:38 +0300 Subject: [PATCH 460/511] Testing commit, lot of out of bounds guards --- .../Jpeg/Components/Decoder/HuffmanTable.cs | 128 ++++++++++-------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 2b2983461..7c6b2700d 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -57,81 +57,91 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffSize = stackalloc char[257]; - Span huffCode = stackalloc uint[257]; + // TODO: testing code + Span huffSize = new char[257]; + Span huffCode = new uint[257]; - // Figure C.1: make table of Huffman code length for each symbol - int p = 0; - for (int j = 1; j <= 16; j++) + fixed (void* maxCodePtr = this.MaxCode, valOffsetPtr = this.ValOffset, lookaheadSizePtr = this.LookaheadSize, lookaheadValuePtr = this.LookaheadValue) { - int i = codeLengths[j]; - while (i-- != 0) + // TODO: testing spans + var maxCodeSpan = new Span(maxCodePtr, 18); + var valOffsetSpan = new Span(valOffsetPtr, 19); + var lookaheadSizeSpan = new Span(lookaheadSizePtr, JpegConstants.Huffman.LookupSize); + var lookaheadValueSpan = new Span(lookaheadValuePtr, JpegConstants.Huffman.LookupSize); + + // Figure C.1: make table of Huffman code length for each symbol + int p = 0; + for (int j = 1; j <= 16; j++) { - huffSize[p++] = (char)j; + int i = codeLengths[j]; + while (i-- != 0) + { + huffSize[p++] = (char)j; + } } - } - huffSize[p] = (char)0; + huffSize[p] = (char)0; - // Figure C.2: generate the codes themselves - uint code = 0; - int si = huffSize[0]; - p = 0; - while (huffSize[p] != 0) - { - while (huffSize[p] == si) + // Figure C.2: generate the codes themselves + uint code = 0; + int si = huffSize[0]; + p = 0; + while (huffSize[p] != 0) { - huffCode[p++] = code; - code++; - } - - code <<= 1; - si++; - } + while (huffSize[p] == si) + { + huffCode[p++] = code; + code++; + } - // Figure F.15: generate decoding tables for bit-sequential decoding - p = 0; - for (int j = 1; j <= 16; j++) - { - if (codeLengths[j] != 0) - { - this.ValOffset[j] = p - (int)huffCode[p]; - p += codeLengths[j]; - this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l - this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify - this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; + code <<= 1; + si++; } - else + + // Figure F.15: generate decoding tables for bit-sequential decoding + p = 0; + for (int j = 1; j <= 16; j++) { - this.MaxCode[j] = 0; + if (codeLengths[j] != 0) + { + valOffsetSpan[j] = p - (int)huffCode[p]; + p += codeLengths[j]; + maxCodeSpan[j] = huffCode[p - 1]; // Maximum code of length l + maxCodeSpan[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify + maxCodeSpan[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; + } + else + { + maxCodeSpan[j] = 0; + } } - } - this.ValOffset[18] = 0; - this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates + valOffsetSpan[18] = 0; + maxCodeSpan[17] = ulong.MaxValue; // Ensures huff decode terminates - // Compute lookahead tables to speed up decoding. - // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; - // then we iterate through the Huffman codes that are short enough and - // fill in all the entries that correspond to bit sequences starting - // with that code. - ref byte lookupSizeRef = ref this.LookaheadSize[0]; - Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); + // Compute lookahead tables to speed up decoding. + // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; + // then we iterate through the Huffman codes that are short enough and + // fill in all the entries that correspond to bit sequences starting + // with that code. + ref byte lookupSizeRef = ref lookaheadSizeSpan[0]; + Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); - p = 0; - for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) - { - int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= codeLengths[length]; i++, p++) + p = 0; + for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) { - // length = current code's length, p = its index in huffCode[] & Values[]. - // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(huffCode[p] << jShift); - for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) + int jShift = JpegConstants.Huffman.LookupBits - length; + for (int i = 1; i <= codeLengths[length]; i++, p++) { - this.LookaheadSize[lookBits] = (byte)length; - this.LookaheadValue[lookBits] = this.Values[p]; - lookBits++; + // length = current code's length, p = its index in huffCode[] & Values[]. + // Generate left-justified code followed by all possible bit sequences + int lookBits = (int)(huffCode[p] << jShift); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) + { + lookaheadSizeSpan[lookBits] = (byte)length; + lookaheadValueSpan[lookBits] = this.Values[p]; + lookBits++; + } } } } From 2d9ac58b29c40dd52eceb20f88e19a0da44c9abb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 6 Jan 2022 17:24:16 +0100 Subject: [PATCH 461/511] AVX2 version of VerticalUnfilter --- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 31 +++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index e63cd27b5..ed129dc26 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -9,6 +9,10 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Webp.BitReader; using SixLabors.ImageSharp.Formats.Webp.Lossless; using SixLabors.ImageSharp.Memory; +#if SUPPORTS_RUNTIME_INTRINSICS +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; +#endif namespace SixLabors.ImageSharp.Formats.Webp { @@ -325,9 +329,32 @@ namespace SixLabors.ImageSharp.Formats.Webp } else { - for (int i = 0; i < width; i++) +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) { - dst[i] = (byte)(prev[i] + input[i]); + int i; + int maxPos = width & ~31; + for (i = 0; i < maxPos; i += 32) + { + Vector256 a0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(input), i)); + Vector256 b0 = Unsafe.As>(ref Unsafe.Add(ref MemoryMarshal.GetReference(prev), i)); + Vector256 c0 = Avx2.Add(a0.AsByte(), b0.AsByte()); + ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i); + Unsafe.As>(ref outputRef) = c0; + } + + for (; i < width; i++) + { + dst[i] = (byte)(prev[i] + input[i]); + } + } + else +#endif + { + for (int i = 0; i < width; i++) + { + dst[i] = (byte)(prev[i] + input[i]); + } } } } From 4289b8c3984f2d4a1926c6eb26f4707e6c3e8d62 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 6 Jan 2022 17:24:46 +0100 Subject: [PATCH 462/511] Fix indenting --- tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 5a8425c1f..89e65b211 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -85,11 +85,11 @@ namespace SixLabors.ImageSharp.Tests.Formats public static readonly TheoryData QuantizerNames = new() { - nameof(KnownQuantizers.Octree), - nameof(KnownQuantizers.WebSafe), - nameof(KnownQuantizers.Werner), - nameof(KnownQuantizers.Wu) - }; + nameof(KnownQuantizers.Octree), + nameof(KnownQuantizers.WebSafe), + nameof(KnownQuantizers.Werner), + nameof(KnownQuantizers.Wu) + }; [Theory] [WithFile(TestImages.Png.CalliphoraPartial, nameof(QuantizerNames), PixelTypes.Rgba32)] From d7ecfb58660585fc1bee2511a2169cbf31d23355 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 6 Jan 2022 19:36:44 +0100 Subject: [PATCH 463/511] SSE2 version of HorizontalUnfilter --- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 47 +++++++++++++++++-- .../Formats/Webp/Lossless/LosslessUtils.cs | 1 - 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index ed129dc26..39ff0dbbb 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -311,13 +311,50 @@ namespace SixLabors.ImageSharp.Formats.Webp private static void HorizontalUnfilter(Span prev, Span input, Span dst, int width) { - byte pred = (byte)(prev == null ? 0 : prev[0]); +#if SUPPORTS_RUNTIME_INTRINSICS + if (Sse2.IsSupported) + { + dst[0] = (byte)(input[0] + (prev == null ? 0 : prev[0])); + if (width <= 1) + { + return; + } - for (int i = 0; i < width; i++) + int i; + var last = Vector128.Create(dst[0], 0, 0, 0); + ref byte srcRef = ref MemoryMarshal.GetReference(input); + for (i = 1; i + 8 <= width; i += 8) + { + var a0 = Vector128.Create(Unsafe.As(ref Unsafe.Add(ref srcRef, i)), 0); + Vector128 a1 = Sse2.Add(a0.AsByte(), last.AsByte()); + Vector128 a2 = Sse2.ShiftLeftLogical128BitLane(a1, 1); + Vector128 a3 = Sse2.Add(a1, a2); + Vector128 a4 = Sse2.ShiftLeftLogical128BitLane(a3, 2); + Vector128 a5 = Sse2.Add(a3, a4); + Vector128 a6 = Sse2.ShiftLeftLogical128BitLane(a5, 4); + Vector128 a7 = Sse2.Add(a5, a6); + + ref byte outputRef = ref Unsafe.Add(ref MemoryMarshal.GetReference(dst), i); + Unsafe.As>(ref outputRef) = a7.GetLower(); + last = Sse2.ShiftRightLogical(a7.AsInt64(), 56).AsInt32(); + } + + for (; i < width; ++i) + { + dst[i] = (byte)(input[i] + dst[i - 1]); + } + } + else +#endif { - byte val = (byte)(pred + input[i]); - pred = val; - dst[i] = val; + byte pred = (byte)(prev == null ? 0 : prev[0]); + + for (int i = 0; i < width; i++) + { + byte val = (byte)(pred + input[i]); + pred = val; + dst[i] = val; + } } } diff --git a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs index ebb198a2d..e7782b0ef 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Memory; From 559edde971aab8340ec215c106fa0333cd4e09e5 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Thu, 6 Jan 2022 22:41:54 +0100 Subject: [PATCH 464/511] Don't throw when Position is out of range for BufferedReadStream --- src/ImageSharp/IO/BufferedReadStream.cs | 1 - .../IO/BufferedReadStreamTests.cs | 24 +++++++++++++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs index acba3eff0..4ab7f312b 100644 --- a/src/ImageSharp/IO/BufferedReadStream.cs +++ b/src/ImageSharp/IO/BufferedReadStream.cs @@ -87,7 +87,6 @@ namespace SixLabors.ImageSharp.IO set { Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.Position)); - Guard.MustBeLessThanOrEqualTo(value, this.Length, nameof(this.Position)); // Only reset readBufferIndex if we are out of bounds of our working buffer // otherwise we should simply move the value by the diff. diff --git a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs index 8e7321864..f968b16f0 100644 --- a/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs +++ b/tests/ImageSharp.Tests/IO/BufferedReadStreamTests.cs @@ -314,7 +314,7 @@ namespace SixLabors.ImageSharp.Tests.IO [Theory] [MemberData(nameof(BufferSizes))] - public void BufferedStreamThrowsOnBadPosition(int bufferSize) + public void BufferedStreamThrowsOnNegativePosition(int bufferSize) { this.configuration.StreamProcessingBufferSize = bufferSize; using (MemoryStream stream = this.CreateTestStream(bufferSize)) @@ -322,15 +322,14 @@ namespace SixLabors.ImageSharp.Tests.IO using (var reader = new BufferedReadStream(this.configuration, stream)) { Assert.Throws(() => reader.Position = -stream.Length); - Assert.Throws(() => reader.Position = stream.Length + 1); } } } - [Fact] - public void BufferedStreamCanSetPositionToEnd() + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanSetPositionToEnd(int bufferSize) { - var bufferSize = 8; this.configuration.StreamProcessingBufferSize = bufferSize; using (MemoryStream stream = this.CreateTestStream(bufferSize * 2)) { @@ -341,6 +340,21 @@ namespace SixLabors.ImageSharp.Tests.IO } } + [Theory] + [MemberData(nameof(BufferSizes))] + public void BufferedStreamCanSetPositionPastTheEnd(int bufferSize) + { + this.configuration.StreamProcessingBufferSize = bufferSize; + using (MemoryStream stream = this.CreateTestStream(bufferSize * 2)) + { + using (var reader = new BufferedReadStream(this.configuration, stream)) + { + reader.Position = reader.Length + 1; + Assert.Equal(stream.Length + 1, stream.Position); + } + } + } + private MemoryStream CreateTestStream(int length) { var buffer = new byte[length]; From 760785e75e3817c61bc49edf280b025571eb96d6 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 7 Jan 2022 11:33:38 +0100 Subject: [PATCH 465/511] Fix casing of test images file names --- tests/ImageSharp.Tests/TestImages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 7e396d107..d564866e4 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -220,7 +220,7 @@ namespace SixLabors.ImageSharp.Tests public const string Lossless = "Jpg/baseline/lossless.jpg"; public const string Winter444_Interleaved = "Jpg/baseline/winter444_interleaved.jpg"; public const string Metadata = "Jpg/baseline/Metadata-test-file.jpg"; - public const string ExtendedXmp = "Jpg/baseline/Extended-XMP.jpg"; + public const string ExtendedXmp = "Jpg/baseline/extended-xmp.jpg"; public static readonly string[] All = { From e3ef796be274d4cb9effeaec6665bf3c4bdaccdb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 7 Jan 2022 11:42:50 +0100 Subject: [PATCH 466/511] Fix casing for Webp test image --- tests/ImageSharp.Tests/TestImages.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index d564866e4..3fb07e12d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -628,7 +628,7 @@ namespace SixLabors.ImageSharp.Tests public const string Earth = "Webp/earth_lossy.webp"; public const string WithExif = "Webp/exif_lossy.webp"; public const string WithIccp = "Webp/lossy_with_iccp.webp"; - public const string WithXmp = "webp/xmp_lossy.webp"; + public const string WithXmp = "Webp/xmp_lossy.webp"; public const string BikeSmall = "Webp/bike_lossless_small.webp"; // Lossy images without macroblock filtering. From dae65033634fd814f9b2491737d51fdd0db2a243 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 7 Jan 2022 15:19:26 +0100 Subject: [PATCH 467/511] Add tests for horizontal and vertical filter without hardware intrinsics --- .../Formats/WebP/WebpDecoderTests.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs index 22342e612..1c92fdf33 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs @@ -19,6 +19,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp private static MagickReferenceDecoder ReferenceDecoder => new(); + private static string TestImageLossyHorizontalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedHorizontalFilter); + + private static string TestImageLossyVerticalFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.AlphaCompressedVerticalFilter); + private static string TestImageLossySimpleFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.SimpleFilter02); private static string TestImageLossyComplexFilterPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, Lossy.BikeComplexFilter); @@ -365,6 +369,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp }); #if SUPPORTS_RUNTIME_INTRINSICS + private static void RunDecodeLossyWithHorizontalFilter() + { + var provider = TestImageProvider.File(TestImageLossyHorizontalFilterPath); + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + + private static void RunDecodeLossyWithVerticalFilter() + { + var provider = TestImageProvider.File(TestImageLossyVerticalFilterPath); + using (Image image = provider.GetImage(WebpDecoder)) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, ReferenceDecoder); + } + } + private static void RunDecodeLossyWithSimpleFilterTest() { var provider = TestImageProvider.File(TestImageLossySimpleFilterPath); @@ -385,6 +409,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp } } + [Fact] + public void DecodeLossyWithHorizontalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithHorizontalFilter, HwIntrinsics.DisableHWIntrinsic); + + [Fact] + public void DecodeLossyWithVerticalFilter_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithVerticalFilter, HwIntrinsics.DisableHWIntrinsic); + [Fact] public void DecodeLossyWithSimpleFilterTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunDecodeLossyWithSimpleFilterTest, HwIntrinsics.DisableHWIntrinsic); From 1694a5bf84f00856833b40c1660090834af3bf51 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 7 Jan 2022 19:43:04 +0300 Subject: [PATCH 468/511] Added sanity check for generated huffman codes --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 11 +++++++++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 7c6b2700d..592860643 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -94,6 +94,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder code++; } + // 'code' is now 1 more than the last code used for codelength 'si' + // in the valid worst possible case 'code' would have the least + // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) + // but it must still fit in 'si' bits since no huffman code can be equal to all 1s + // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield + // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) + if (code >= (1 << si)) + { + JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); + } + code <<= 1; si++; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 73af42afd..08d1aa146 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1110,13 +1110,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // Types 0..1 DC..AC if (tableType > 1) { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}"); + JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}."); } // Max tables of each type if (tableIndex > 3) { - JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}"); + JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); } stream.Read(huffmanDataSpan, 0, 16); From 8b01f139d431a0d00477d2ec1aff51f3efb22618 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 7 Jan 2022 20:01:02 +0300 Subject: [PATCH 469/511] Removed debug checks --- .../Jpeg/Components/Decoder/HuffmanTable.cs | 147 ++++++++---------- 1 file changed, 69 insertions(+), 78 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 592860643..26df55738 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -58,101 +58,92 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); // TODO: testing code - Span huffSize = new char[257]; - Span huffCode = new uint[257]; + Span huffSize = stackalloc char[257]; + Span huffCode = stackalloc uint[257]; - fixed (void* maxCodePtr = this.MaxCode, valOffsetPtr = this.ValOffset, lookaheadSizePtr = this.LookaheadSize, lookaheadValuePtr = this.LookaheadValue) + // Figure C.1: make table of Huffman code length for each symbol + int p = 0; + for (int j = 1; j <= 16; j++) { - // TODO: testing spans - var maxCodeSpan = new Span(maxCodePtr, 18); - var valOffsetSpan = new Span(valOffsetPtr, 19); - var lookaheadSizeSpan = new Span(lookaheadSizePtr, JpegConstants.Huffman.LookupSize); - var lookaheadValueSpan = new Span(lookaheadValuePtr, JpegConstants.Huffman.LookupSize); - - // Figure C.1: make table of Huffman code length for each symbol - int p = 0; - for (int j = 1; j <= 16; j++) + int i = codeLengths[j]; + while (i-- != 0) { - int i = codeLengths[j]; - while (i-- != 0) - { - huffSize[p++] = (char)j; - } + huffSize[p++] = (char)j; } + } - huffSize[p] = (char)0; + huffSize[p] = (char)0; - // Figure C.2: generate the codes themselves - uint code = 0; - int si = huffSize[0]; - p = 0; - while (huffSize[p] != 0) + // Figure C.2: generate the codes themselves + uint code = 0; + int si = huffSize[0]; + p = 0; + while (huffSize[p] != 0) + { + while (huffSize[p] == si) { - while (huffSize[p] == si) - { - huffCode[p++] = code; - code++; - } - - // 'code' is now 1 more than the last code used for codelength 'si' - // in the valid worst possible case 'code' would have the least - // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) - // but it must still fit in 'si' bits since no huffman code can be equal to all 1s - // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield - // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) - if (code >= (1 << si)) - { - JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); - } + huffCode[p++] = code; + code++; + } - code <<= 1; - si++; + // 'code' is now 1 more than the last code used for codelength 'si' + // in the valid worst possible case 'code' would have the least + // significant bit set to 1, e.g. 1111(0) +1 => 1111(1) + // but it must still fit in 'si' bits since no huffman code can be equal to all 1s + // if last code is all ones, e.g. 1111(1), then incrementing it by 1 would yield + // a new code which occupies one extra bit, e.g. 1111(1) +1 => (1)1111(0) + if (code >= (1 << si)) + { + JpegThrowHelper.ThrowInvalidImageContentException("Bad huffman table."); } - // Figure F.15: generate decoding tables for bit-sequential decoding - p = 0; - for (int j = 1; j <= 16; j++) + code <<= 1; + si++; + } + + // Figure F.15: generate decoding tables for bit-sequential decoding + p = 0; + for (int j = 1; j <= 16; j++) + { + if (codeLengths[j] != 0) { - if (codeLengths[j] != 0) - { - valOffsetSpan[j] = p - (int)huffCode[p]; - p += codeLengths[j]; - maxCodeSpan[j] = huffCode[p - 1]; // Maximum code of length l - maxCodeSpan[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify - maxCodeSpan[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; - } - else - { - maxCodeSpan[j] = 0; - } + this.ValOffset[j] = p - (int)huffCode[p]; + p += codeLengths[j]; + this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l + this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify + this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; + } + else + { + this.MaxCode[j] = 0; } + } - valOffsetSpan[18] = 0; - maxCodeSpan[17] = ulong.MaxValue; // Ensures huff decode terminates + this.ValOffset[18] = 0; + this.MaxCode[17] = ulong.MaxValue; // Ensures huff decode terminates - // Compute lookahead tables to speed up decoding. - // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; - // then we iterate through the Huffman codes that are short enough and - // fill in all the entries that correspond to bit sequences starting - // with that code. - ref byte lookupSizeRef = ref lookaheadSizeSpan[0]; - Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); + // Compute lookahead tables to speed up decoding. + // First we set all the table entries to JpegConstants.Huffman.SlowBits, indicating "too long"; + // then we iterate through the Huffman codes that are short enough and + // fill in all the entries that correspond to bit sequences starting + // with that code. + ref byte lookupSizeRef = ref this.LookaheadSize[0]; + Unsafe.InitBlockUnaligned(ref lookupSizeRef, JpegConstants.Huffman.SlowBits, JpegConstants.Huffman.LookupSize); - p = 0; - for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) + p = 0; + for (int length = 1; length <= JpegConstants.Huffman.LookupBits; length++) + { + int jShift = JpegConstants.Huffman.LookupBits - length; + for (int i = 1; i <= codeLengths[length]; i++, p++) { - int jShift = JpegConstants.Huffman.LookupBits - length; - for (int i = 1; i <= codeLengths[length]; i++, p++) + // length = current code's length, p = its index in huffCode[] & Values[]. + // Generate left-justified code followed by all possible bit sequences + int lookBits = (int)(huffCode[p] << jShift); + for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { - // length = current code's length, p = its index in huffCode[] & Values[]. - // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(huffCode[p] << jShift); - for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) - { - lookaheadSizeSpan[lookBits] = (byte)length; - lookaheadValueSpan[lookBits] = this.Values[p]; - lookBits++; - } + this.LookaheadSize[lookBits] = (byte)length; + this.LookaheadValue[lookBits] = this.Values[p]; + lookBits++; } } } From 3354ae68d7046cadf3d9e07110f2725aa87475c8 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Fri, 7 Jan 2022 20:01:49 +0300 Subject: [PATCH 470/511] Reduced stackallock pressure by changing char to byte --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 26df55738..c20ea1e58 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); // TODO: testing code - Span huffSize = stackalloc char[257]; + Span huffSize = stackalloc byte[257]; Span huffCode = stackalloc uint[257]; // Figure C.1: make table of Huffman code length for each symbol @@ -68,11 +68,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int i = codeLengths[j]; while (i-- != 0) { - huffSize[p++] = (char)j; + huffSize[p++] = (byte)j; } } - huffSize[p] = (char)0; + huffSize[p] = 0; // Figure C.2: generate the codes themselves uint code = 0; From 1ee34bacb5c94259b5d9429a36f82e64db3c052d Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 7 Jan 2022 20:20:49 +0100 Subject: [PATCH 471/511] Prevent memory allocations --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- .../Formats/Jpeg/JpegDecoderCore.cs | 55 ++++++++++++------- .../Formats/Jpeg/JpegEncoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 2 +- .../Tiff/TiffEncoderEntriesCollector.cs | 2 +- .../Formats/Webp/BitWriter/Vp8BitWriter.cs | 2 +- .../Formats/Webp/BitWriter/Vp8LBitWriter.cs | 2 +- .../Metadata/Profiles/XMP/XmpProfile.cs | 17 +++--- .../Formats/Tiff/TiffMetadataTests.cs | 6 +- .../Formats/WebP/WebpMetaDataTests.cs | 2 +- .../Metadata/ImageFrameMetadataTests.cs | 2 +- .../Metadata/Profiles/XMP/XmpProfileTests.cs | 12 ++-- 12 files changed, 63 insertions(+), 43 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 3f51fa639..17dd03cce 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -339,7 +339,7 @@ namespace SixLabors.ImageSharp.Formats.Gif // Application Extension: XMP Profile. if (xmpProfile != null) { - var xmpExtension = new GifXmpApplicationExtension(xmpProfile.ToByteArray()); + var xmpExtension = new GifXmpApplicationExtension(xmpProfile.Data); this.WriteExtension(xmpExtension, stream); } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 61c5d7c66..7f076211b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -681,9 +681,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessApp1Marker(BufferedReadStream stream, int remaining) { - const int Exif00 = 6; - const int XmpNsLength = 29; - if (remaining < Exif00 || this.IgnoreMetadata) + const int ExifMarkerLength = 6; + const int XmpMarkerLength = 29; + if (remaining < ExifMarkerLength || this.IgnoreMetadata) { // Skip the application header length stream.Skip(remaining); @@ -695,38 +695,55 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); } - byte[] profile = new byte[remaining]; - stream.Read(profile, 0, remaining); + // XMP marker is the longest, so read at least that many bytes into temp. + stream.Read(this.temp, 0, ExifMarkerLength); - if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker)) { + remaining -= ExifMarkerLength; this.isExif = true; + byte[] profile = new byte[remaining]; + stream.Read(profile, 0, remaining); + if (this.exifData is null) { - // The first 6 bytes (Exif00) will be skipped, because this is Jpeg specific - this.exifData = profile.AsSpan(Exif00).ToArray(); + this.exifData = profile; } else { // If the EXIF information exceeds 64K, it will be split over multiple APP1 markers - this.ExtendProfile(ref this.exifData, profile.AsSpan(Exif00).ToArray()); + this.ExtendProfile(ref this.exifData, profile); } + + remaining = 0; } - if (ProfileResolver.IsProfile(profile, ProfileResolver.XmpMarker)) + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) { - this.isXmp = true; - if (this.xmpData is null) + stream.Read(this.temp, 0, XmpMarkerLength - ExifMarkerLength); + remaining -= XmpMarkerLength; + if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength))) { - // The first 29 bytes will be skipped, because this is Jpeg specific - this.xmpData = profile.AsSpan(XmpNsLength).ToArray(); - } - else - { - // If the XMP information exceeds 64K, it will be split over multiple APP1 markers - this.ExtendProfile(ref this.xmpData, profile.AsSpan(XmpNsLength).ToArray()); + this.isXmp = true; + byte[] profile = new byte[remaining]; + stream.Read(profile, 0, remaining); + + if (this.xmpData is null) + { + this.xmpData = profile; + } + else + { + // If the XMP information exceeds 64K, it will be split over multiple APP1 markers + this.ExtendProfile(ref this.xmpData, profile); + } + + remaining = 0; } } + + // Skip over any remaining bytes of this header. + stream.Skip(remaining); } /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs index b1125c8e4..a3cff8f31 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs @@ -485,7 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg const int Max = 65533; const int MaxData = Max - XmpOverheadLength; - byte[] data = xmpProfile.ToByteArray(); + byte[] data = xmpProfile.Data; if (data is null || data.Length == 0) { diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 3bb63a24b..cc51d78ec 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -673,7 +673,7 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - var xmpData = meta.XmpProfile.ToByteArray(); + var xmpData = meta.XmpProfile.Data; if (xmpData.Length == 0) { diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 8be029788..e54d029ab 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte) { - Value = xmpProfile.ToByteArray() + Value = xmpProfile.Data }; this.Collector.Add(xmp); diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs index 26942472f..4e91bedb0 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8BitWriter.cs @@ -427,7 +427,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { isVp8X = true; riffSize += ExtendedFileChunkSize; - xmpBytes = xmpProfile.ToByteArray(); + xmpBytes = xmpProfile.Data; riffSize += this.MetadataChunkSize(xmpBytes); } diff --git a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs index d4d4f196c..d41224f90 100644 --- a/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs +++ b/src/ImageSharp/Formats/Webp/BitWriter/Vp8LBitWriter.cs @@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Formats.Webp.BitWriter { isVp8X = true; riffSize += ExtendedFileChunkSize; - xmpBytes = xmpProfile.ToByteArray(); + xmpBytes = xmpProfile.Data; riffSize += this.MetadataChunkSize(xmpBytes); } diff --git a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs index 144d93774..8fba243ce 100644 --- a/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/XMP/XmpProfile.cs @@ -14,8 +14,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// public sealed class XmpProfile : IDeepCloneable { - private byte[] data; - /// /// Initializes a new instance of the class. /// @@ -28,7 +26,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// Initializes a new instance of the class. ///
/// The UTF8 encoded byte array to read the XMP profile from. - public XmpProfile(byte[] data) => this.data = data; + public XmpProfile(byte[] data) => this.Data = data; /// /// Initializes a new instance of the class @@ -39,16 +37,21 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp { Guard.NotNull(other, nameof(other)); - this.data = other.ToByteArray(); + this.Data = other.Data; } + /// + /// Gets the XMP raw data byte array. + /// + internal byte[] Data { get; private set; } + /// /// Gets the raw XML document containing the XMP profile. /// /// The public XDocument GetDocument() { - byte[] byteArray = this.ToByteArray(); + byte[] byteArray = this.Data; if (byteArray is null) { return null; @@ -75,8 +78,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Xmp /// The public byte[] ToByteArray() { - byte[] result = new byte[this.data.Length]; - this.data.AsSpan().CopyTo(result); + byte[] result = new byte[this.Data.Length]; + this.Data.AsSpan().CopyTo(result); return result; } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 8f5a5f76f..6a47a9577 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); - Assert.Equal(2599, rootFrameMetaData.XmpProfile.ToByteArray().Length); + Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); } } @@ -164,7 +164,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(32, rootFrame.Width); Assert.Equal(32, rootFrame.Height); Assert.NotNull(rootFrame.Metadata.XmpProfile); - Assert.Equal(2599, rootFrame.Metadata.XmpProfile.ToByteArray().Length); + Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Data.Length); ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.NotNull(xmpProfileInput); Assert.NotNull(encodedImageXmpProfile); - Assert.Equal(xmpProfileInput.ToByteArray(), encodedImageXmpProfile.ToByteArray()); + Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs index 79c4ee57a..7fba86b4f 100644 --- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs +++ b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs @@ -79,7 +79,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp else { Assert.NotNull(image.Metadata.XmpProfile); - Assert.NotEmpty(image.Metadata.XmpProfile.ToByteArray()); + Assert.NotEmpty(image.Metadata.XmpProfile.Data); } } diff --git a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs index e5918a9ea..dd8ae3d5a 100644 --- a/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs +++ b/tests/ImageSharp.Tests/Metadata/ImageFrameMetadataTests.cs @@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata Assert.False(metaData.ExifProfile.Equals(clone.ExifProfile)); Assert.True(metaData.ExifProfile.Values.Count == clone.ExifProfile.Values.Count); Assert.False(ReferenceEquals(metaData.XmpProfile, clone.XmpProfile)); - Assert.True(metaData.XmpProfile.ToByteArray().Length.Equals(clone.XmpProfile.ToByteArray().Length)); + Assert.True(metaData.XmpProfile.Data.Equals(clone.XmpProfile.Data)); Assert.False(metaData.GetGifMetadata().Equals(clone.GetGifMetadata())); Assert.False(metaData.IccProfile.Equals(clone.IccProfile)); Assert.False(metaData.IptcProfile.Equals(clone.IptcProfile)); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs index f1c5edc54..81dad699a 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/XMP/XmpProfileTests.cs @@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -153,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -171,7 +171,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -189,7 +189,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } [Fact] @@ -225,7 +225,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Xmp // assert XmpProfile actual = reloadedImage.Metadata.XmpProfile ?? reloadedImage.Frames.RootFrame.Metadata.XmpProfile; XmpProfileContainsExpectedValues(actual); - Assert.Equal(original.ToByteArray(), actual.ToByteArray()); + Assert.Equal(original.Data, actual.Data); } private static void XmpProfileContainsExpectedValues(XmpProfile xmp) From 499ddf8defe7c1de77e635360554ad42a09301fb Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 09:50:22 +0300 Subject: [PATCH 472/511] More readable and maybe even faster code --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index c20ea1e58..9ef29962e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -57,7 +57,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - // TODO: testing code Span huffSize = stackalloc byte[257]; Span huffCode = stackalloc uint[257]; @@ -65,11 +64,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int p = 0; for (int j = 1; j <= 16; j++) { - int i = codeLengths[j]; - while (i-- != 0) - { - huffSize[p++] = (byte)j; - } + int count = codeLengths[j]; + huffSize.Slice(p, count).Fill((byte)j); + p += count; } huffSize[p] = 0; From 51127b6b5624bbe82814448472e641b46f263c1f Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 11:32:23 +0300 Subject: [PATCH 473/511] Removed 257 bytes of stack memory pressure --- .../Jpeg/Components/Decoder/HuffmanTable.cs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 9ef29962e..c69949de4 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -57,27 +57,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffSize = stackalloc byte[257]; Span huffCode = stackalloc uint[257]; - // Figure C.1: make table of Huffman code length for each symbol - int p = 0; - for (int j = 1; j <= 16; j++) - { - int count = codeLengths[j]; - huffSize.Slice(p, count).Fill((byte)j); - p += count; - } - - huffSize[p] = 0; - - // Figure C.2: generate the codes themselves + // Generate codes uint code = 0; - int si = huffSize[0]; - p = 0; - while (huffSize[p] != 0) + int si = 1; + int p = 0; + for (int i = 1; i <= 16; i++) { - while (huffSize[p] == si) + int count = codeLengths[i]; + for (int j = 0; j < count; j++) { huffCode[p++] = code; code++; From e7ee0fdac4c3ab94bc345e17356879487fdcf195 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 11:49:47 +0300 Subject: [PATCH 474/511] [WIP] outer scope memory propagation --- .../Jpeg/Components/Decoder/HuffmanScanDecoder.cs | 4 ++-- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 9 +++++---- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 3 ++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index 20c5d4a9b..c06f3f2a0 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -728,10 +728,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Code lengths. /// Code values. [MethodImpl(InliningOptions.ShortMethod)] - public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values) + public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables; - tables[index] = new HuffmanTable(codeLengths, values); + tables[index] = new HuffmanTable(codeLengths, values, workspace); } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index c69949de4..7e69c4d94 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -51,13 +51,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// /// Initializes a new instance of the struct. /// - /// The code lengths - /// The huffman values - public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values) + /// The code lengths. + /// The huffman values. + /// The spare workspace memory, must be provided by the caller. + public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffCode = stackalloc uint[257]; + Span huffCode = workspace; // Generate codes uint code = 0; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 08d1aa146..adb0ad4b0 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1150,7 +1150,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg tableType, tableIndex, codeLengthsSpan, - huffmanValuesSpan); + huffmanValuesSpan, + stackalloc uint[257]); } } } From bd4eb34b48a533e0e5b932ad751195cd8787ff0d Mon Sep 17 00:00:00 2001 From: jz5 Date: Sat, 8 Jan 2022 18:28:40 +0900 Subject: [PATCH 475/511] internal constructor to public constructor --- src/ImageSharp/Formats/PixelTypeInfo.cs | 2 +- src/ImageSharp/Metadata/ImageMetadata.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs index 718b05e33..1bc717cbb 100644 --- a/src/ImageSharp/Formats/PixelTypeInfo.cs +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats /// /// Color depth, in number of bits per pixel. /// Tthe pixel alpha transparency behavior. - internal PixelTypeInfo(int bitsPerPixel, PixelAlphaRepresentation? alpha = null) + public PixelTypeInfo(int bitsPerPixel, PixelAlphaRepresentation? alpha = null) { this.BitsPerPixel = bitsPerPixel; this.AlphaRepresentation = alpha; diff --git a/src/ImageSharp/Metadata/ImageMetadata.cs b/src/ImageSharp/Metadata/ImageMetadata.cs index 425fd9b47..450f1b6ae 100644 --- a/src/ImageSharp/Metadata/ImageMetadata.cs +++ b/src/ImageSharp/Metadata/ImageMetadata.cs @@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Metadata /// /// Initializes a new instance of the class. /// - internal ImageMetadata() + public ImageMetadata() { this.horizontalResolution = DefaultHorizontalResolution; this.verticalResolution = DefaultVerticalResolution; From 2216980c2f55e41fca5e389a14b0905b6c476fd3 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 8 Jan 2022 12:39:09 +0300 Subject: [PATCH 476/511] exif ifd writing fixes --- .../Metadata/Profiles/Exif/ExifWriter.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 82f4aac17..2fb192449 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -46,7 +46,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif public byte[] GetData() { const uint startIndex = 0; - uint length; IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); @@ -56,13 +55,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif return Array.Empty(); } - uint ifdLength = this.GetLength(this.ifdValues) + 4U; + uint ifdLength = this.GetLength(this.ifdValues); uint exifLength = this.GetLength(this.exifValues); uint gpsLength = this.GetLength(this.gpsValues); - length = ifdLength + exifLength + gpsLength; + uint length = ifdLength + exifLength + gpsLength; - if (length == 4U) + if (length == 0) { return Array.Empty(); } @@ -70,9 +69,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif // two bytes for the byte Order marker 'II' or 'MM', followed by the number 42 (0x2A) and a 0, making 4 bytes total length += (uint)ExifConstants.LittleEndianByteOrderMarker.Length; - length += 4 + 4; + // first IFD offset + length += 4; - var result = new byte[length]; + byte[] result = new byte[length]; int i = 0; @@ -80,15 +80,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif ExifConstants.LittleEndianByteOrderMarker.CopyTo(result.AsSpan(start: i)); i += ExifConstants.LittleEndianByteOrderMarker.Length; - uint ifdOffset = ((uint)i - startIndex) + 4U; - uint thumbnailOffset = ifdOffset + ifdLength + exifLength + gpsLength; + uint ifdOffset = (uint)i - startIndex + 4U; exifOffset?.TrySetValue(ifdOffset + ifdLength); gpsOffset?.TrySetValue(ifdOffset + ifdLength + exifLength); i = WriteUInt32(ifdOffset, result, i); i = this.WriteHeaders(this.ifdValues, result, i); - i = WriteUInt32(thumbnailOffset, result, i); i = this.WriteData(startIndex, this.ifdValues, result, i); if (exifLength > 0) @@ -103,8 +101,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif i = this.WriteData(startIndex, this.gpsValues, result, i); } - WriteUInt32(0, result, i); - return result; } @@ -263,7 +259,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif { uint valueLength = GetLength(value); - length += 2 + 2 + 4 + 4; + length += 12; if (valueLength > 4) { @@ -271,6 +267,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif } } + // next IFD offset + length += 4; + return length; } @@ -361,6 +360,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif newOffset += 4; } + // next IFD offset + newOffset = WriteUInt32(0, destination, newOffset); + return newOffset; } From c944d410e3e2351084fb560311eb6682ce326604 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 8 Jan 2022 12:39:40 +0300 Subject: [PATCH 477/511] test fixes --- .../ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 72d07190e..e144ece6c 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -420,7 +420,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif Assert.Equal(2, profile.Values.Count(v => (ExifTagValue)(ushort)v.Tag == ExifTagValue.DateTime)); byte[] bytes = profile.ToByteArray(); - Assert.Equal(527, bytes.Length); + Assert.Equal(531, bytes.Length); var profile2 = new ExifProfile(bytes); Assert.Equal(25, profile2.Values.Count); From b79b409de3f77acddb2889104535ec90e8b19f90 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 8 Jan 2022 14:22:40 +0300 Subject: [PATCH 478/511] add test --- .../Profiles/Exif/ExifProfileTests.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index e144ece6c..028f1967d 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -487,6 +487,28 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif return profile; } + [Fact] + public void IfdStructure() + { + var exif = new ExifProfile(); + exif.SetValue(ExifTag.XPAuthor, Encoding.GetEncoding("UCS-2").GetBytes("Dan Petitt")); + + byte[] actualBytes = exif.ToByteArray(); + + // Assert + int ifdOffset = ExifConstants.LittleEndianByteOrderMarker.Length; + Assert.Equal(8, actualBytes[ifdOffset]); + Assert.Equal(0, actualBytes[ifdOffset + 1]); + Assert.Equal(0, actualBytes[ifdOffset + 2]); + Assert.Equal(0, actualBytes[ifdOffset + 3]); + + int nextIfdPointerOffset = ExifConstants.LittleEndianByteOrderMarker.Length + 4 + 2 + 12; + Assert.Equal(0, actualBytes[nextIfdPointerOffset]); + Assert.Equal(0, actualBytes[nextIfdPointerOffset + 1]); + Assert.Equal(0, actualBytes[nextIfdPointerOffset + 2]); + Assert.Equal(0, actualBytes[nextIfdPointerOffset + 3]); + } + internal static ExifProfile GetExifProfile() { using Image image = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateRgba32Image(); From 2f664e5edb35cf4b17fbbcc44c7cfac445e86590 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 8 Jan 2022 14:37:35 +0300 Subject: [PATCH 479/511] minor improve test --- .../Metadata/Profiles/Exif/ExifProfileTests.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 028f1967d..4e258d847 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.IO; using System.Linq; @@ -493,20 +494,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif var exif = new ExifProfile(); exif.SetValue(ExifTag.XPAuthor, Encoding.GetEncoding("UCS-2").GetBytes("Dan Petitt")); - byte[] actualBytes = exif.ToByteArray(); + Span actualBytes = exif.ToByteArray(); // Assert int ifdOffset = ExifConstants.LittleEndianByteOrderMarker.Length; - Assert.Equal(8, actualBytes[ifdOffset]); - Assert.Equal(0, actualBytes[ifdOffset + 1]); - Assert.Equal(0, actualBytes[ifdOffset + 2]); - Assert.Equal(0, actualBytes[ifdOffset + 3]); + + Assert.Equal(8U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(ifdOffset, 4))); int nextIfdPointerOffset = ExifConstants.LittleEndianByteOrderMarker.Length + 4 + 2 + 12; - Assert.Equal(0, actualBytes[nextIfdPointerOffset]); - Assert.Equal(0, actualBytes[nextIfdPointerOffset + 1]); - Assert.Equal(0, actualBytes[nextIfdPointerOffset + 2]); - Assert.Equal(0, actualBytes[nextIfdPointerOffset + 3]); + Assert.Equal(0U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(nextIfdPointerOffset, 4))); } internal static ExifProfile GetExifProfile() From f356c618d67c9476aa4c6b4b5871e3cd464a3056 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sat, 8 Jan 2022 12:59:15 +0100 Subject: [PATCH 480/511] Renaming the hasProfile fields --- .../Formats/Jpeg/JpegDecoderCore.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 7f076211b..18f8dcc56 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Whether the image has an EXIF marker. /// - private bool isExif; + private bool hasExif; /// /// Contains exif data. @@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Whether the image has an ICC marker. /// - private bool isIcc; + private bool hasIcc; /// /// Contains ICC data. @@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Whether the image has a IPTC data. /// - private bool isIptc; + private bool hasIptc; /// /// Contains IPTC data. @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// /// Whether the image has a XMP data. /// - private bool isXmp; + private bool hasXmp; /// /// Contains XMP data. @@ -552,7 +552,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void InitExifProfile() { - if (this.isExif) + if (this.hasExif) { this.Metadata.ExifProfile = new ExifProfile(this.exifData); } @@ -563,7 +563,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void InitIccProfile() { - if (this.isIcc) + if (this.hasIcc) { var profile = new IccProfile(this.iccData); if (profile.CheckIsValid()) @@ -578,7 +578,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void InitIptcProfile() { - if (this.isIptc) + if (this.hasIptc) { var profile = new IptcProfile(this.iptcData); this.Metadata.IptcProfile = profile; @@ -590,7 +590,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// private void InitXmpProfile() { - if (this.isXmp) + if (this.hasXmp) { var profile = new XmpProfile(this.xmpData); this.Metadata.XmpProfile = profile; @@ -608,7 +608,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg this.Metadata.VerticalResolution = this.jFif.YDensity; this.Metadata.ResolutionUnits = this.jFif.DensityUnits; } - else if (this.isExif) + else if (this.hasExif) { double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution); double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution); @@ -701,7 +701,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (ProfileResolver.IsProfile(this.temp, ProfileResolver.ExifMarker)) { remaining -= ExifMarkerLength; - this.isExif = true; + this.hasExif = true; byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); @@ -724,7 +724,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg remaining -= XmpMarkerLength; if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(ExifMarkerLength))) { - this.isXmp = true; + this.hasXmp = true; byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); @@ -767,7 +767,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) { - this.isIcc = true; + this.hasIcc = true; byte[] profile = new byte[remaining]; stream.Read(profile, 0, remaining); @@ -826,7 +826,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg int dataStartIdx = 2 + resourceBlockNameLength + 4; if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) { - this.isIptc = true; + this.hasIptc = true; this.iptcData = blockDataSpan.Slice(dataStartIdx, resourceDataSize).ToArray(); break; } From 556c0a7c4f62a216b99d572d448ebca908711d3d Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 8 Jan 2022 15:09:05 +0300 Subject: [PATCH 481/511] cleanup, add empty profile writer test --- src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs | 5 ----- .../Metadata/Profiles/Exif/ExifProfileTests.cs | 12 +++++++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs index 2fb192449..e2ed56954 100644 --- a/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs +++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs @@ -50,11 +50,6 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif IExifValue exifOffset = GetOffsetValue(this.ifdValues, this.exifValues, ExifTag.SubIFDOffset); IExifValue gpsOffset = GetOffsetValue(this.ifdValues, this.gpsValues, ExifTag.GPSIFDOffset); - if (this.ifdValues.Count == 0 && this.exifValues.Count == 0 && this.gpsValues.Count == 0) - { - return Array.Empty(); - } - uint ifdLength = this.GetLength(this.ifdValues); uint exifLength = this.GetLength(this.exifValues); uint gpsLength = this.GetLength(this.gpsValues); diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 4e258d847..0210086f5 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -87,6 +87,17 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif new ExifProfile(Array.Empty()); } + [Fact] + public void EmptyWriter() + { + var profile = new ExifProfile() { Parts = ExifParts.GpsTags }; + profile.SetValue(ExifTag.Copyright, "Copyright text"); + + byte[] bytes = profile.ToByteArray(); + Assert.NotNull(bytes); + Assert.Empty(bytes); + } + [Fact] public void ConstructorCopy() { @@ -498,7 +509,6 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif // Assert int ifdOffset = ExifConstants.LittleEndianByteOrderMarker.Length; - Assert.Equal(8U, BinaryPrimitives.ReadUInt32LittleEndian(actualBytes.Slice(ifdOffset, 4))); int nextIfdPointerOffset = ExifConstants.LittleEndianByteOrderMarker.Length + 4 + 2 + 12; From 92cc9fbb1c9c00961a399ef14cd9609ec3af567c Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sat, 8 Jan 2022 15:19:18 +0300 Subject: [PATCH 482/511] fake changes --- .../ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 0210086f5..7fc3ff6f1 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -94,6 +94,7 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif profile.SetValue(ExifTag.Copyright, "Copyright text"); byte[] bytes = profile.ToByteArray(); + Assert.NotNull(bytes); Assert.Empty(bytes); } From 46226bfe79aeb8372cf367c5628313e1bbf1fe8c Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sat, 8 Jan 2022 22:41:01 +0300 Subject: [PATCH 483/511] Reduced memory pressure, removed unnecessary buffer --- .../Formats/Jpeg/JpegDecoderCore.cs | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index adb0ad4b0..4c6772a6f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1095,12 +1095,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) { - int length = remaining; + const int codeLengthsByteSize = 16; + const int codeValuesMaxByteSize = 256; + const int tableWorkspaceByteSize = 257 * sizeof(uint); + const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + tableWorkspaceByteSize; - using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) + int length = remaining; + using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(17)) { Span huffmanDataSpan = huffmanData.GetSpan(); - ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan); for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)stream.ReadByte(); @@ -1119,40 +1122,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); } - stream.Read(huffmanDataSpan, 0, 16); + stream.Read(huffmanDataSpan, 1, 16); - using (IMemoryOwner codeLengths = this.Configuration.MemoryAllocator.Allocate(17, AllocationOptions.Clean)) + int codeLengthSum = 0; + for (int j = 1; j < 17; j++) { - Span codeLengthsSpan = codeLengths.GetSpan(); - ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengthsSpan); - int codeLengthSum = 0; - - for (int j = 1; j < 17; j++) - { - codeLengthSum += Unsafe.Add(ref codeLengthsRef, j) = Unsafe.Add(ref huffmanDataRef, j - 1); - } + codeLengthSum += huffmanDataSpan[j]; + } - length -= 17; + length -= 17; - if (codeLengthSum > 256 || codeLengthSum > length) - { - JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); - } + if (codeLengthSum > 256 || codeLengthSum > length) + { + JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); + } - using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) - { - Span huffmanValuesSpan = huffmanValues.GetSpan(); - stream.Read(huffmanValuesSpan, 0, codeLengthSum); + using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) + { + Span huffmanValuesSpan = huffmanValues.GetSpan(); + stream.Read(huffmanValuesSpan, 0, codeLengthSum); - i += 17 + codeLengthSum; + i += 17 + codeLengthSum; - this.scanDecoder.BuildHuffmanTable( - tableType, - tableIndex, - codeLengthsSpan, - huffmanValuesSpan, - stackalloc uint[257]); - } + this.scanDecoder.BuildHuffmanTable( + tableType, + tableIndex, + huffmanDataSpan, + huffmanValuesSpan, + stackalloc uint[257]); } } } From 5e1eb1ad5d8b3e00eae35f7106a59e2938ea1b34 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 9 Jan 2022 03:47:34 +0300 Subject: [PATCH 484/511] Removed stack pressure, pooling single buffer for entire huffman tables parsing --- .../Components/Decoder/HuffmanScanDecoder.cs | 1 + .../Jpeg/Components/Decoder/HuffmanTable.cs | 12 +++---- .../Formats/Jpeg/JpegDecoderCore.cs | 36 +++++++++---------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs index c06f3f2a0..2ae3ae86b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs @@ -727,6 +727,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// Table index. /// Code lengths. /// Code values. + /// The provided spare workspace memory, can be dirty. [MethodImpl(InliningOptions.ShortMethod)] public void BuildHuffmanTable(int type, int index, ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 7e69c4d94..90a966d53 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -53,13 +53,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder ///
/// The code lengths. /// The huffman values. - /// The spare workspace memory, must be provided by the caller. + /// The provided spare workspace memory, can be dirty. public HuffmanTable(ReadOnlySpan codeLengths, ReadOnlySpan values, Span workspace) { Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); - Span huffCode = workspace; - // Generate codes uint code = 0; int si = 1; @@ -69,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder int count = codeLengths[i]; for (int j = 0; j < count; j++) { - huffCode[p++] = code; + workspace[p++] = code; code++; } @@ -94,9 +92,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { if (codeLengths[j] != 0) { - this.ValOffset[j] = p - (int)huffCode[p]; + this.ValOffset[j] = p - (int)workspace[p]; p += codeLengths[j]; - this.MaxCode[j] = huffCode[p - 1]; // Maximum code of length l + this.MaxCode[j] = workspace[p - 1]; // Maximum code of length l this.MaxCode[j] <<= JpegConstants.Huffman.RegisterSize - j; // Left justify this.MaxCode[j] |= (1ul << (JpegConstants.Huffman.RegisterSize - j)) - 1; } @@ -125,7 +123,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder { // length = current code's length, p = its index in huffCode[] & Values[]. // Generate left-justified code followed by all possible bit sequences - int lookBits = (int)(huffCode[p] << jShift); + int lookBits = (int)(workspace[p] << jShift); for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) { this.LookaheadSize[lookBits] = (byte)length; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 4c6772a6f..a2cccc37f 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1095,15 +1095,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// The remaining bytes in the segment block. private void ProcessDefineHuffmanTablesMarker(BufferedReadStream stream, int remaining) { - const int codeLengthsByteSize = 16; + const int codeLengthsByteSize = 17; const int codeValuesMaxByteSize = 256; - const int tableWorkspaceByteSize = 257 * sizeof(uint); + const int tableWorkspaceByteSize = 256 * sizeof(uint); const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + tableWorkspaceByteSize; int length = remaining; - using (IMemoryOwner huffmanData = this.Configuration.MemoryAllocator.Allocate(17)) + using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(totalBufferSize)) { - Span huffmanDataSpan = huffmanData.GetSpan(); + Span bufferSpan = buffer.GetSpan(); + Span huffmanLegthsSpan = buffer.Slice(0, codeLengthsByteSize); + Span huffmanValuesSpan = buffer.Slice(codeLengthsByteSize, codeValuesMaxByteSize); + Span tableWorkspace = MemoryMarshal.Cast(buffer.Slice(codeLengthsByteSize + codeValuesMaxByteSize)); + for (int i = 2; i < remaining;) { byte huffmanTableSpec = (byte)stream.ReadByte(); @@ -1122,12 +1126,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}."); } - stream.Read(huffmanDataSpan, 1, 16); + stream.Read(huffmanLegthsSpan, 1, 16); int codeLengthSum = 0; for (int j = 1; j < 17; j++) { - codeLengthSum += huffmanDataSpan[j]; + codeLengthSum += huffmanLegthsSpan[j]; } length -= 17; @@ -1137,20 +1141,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length."); } - using (IMemoryOwner huffmanValues = this.Configuration.MemoryAllocator.Allocate(256, AllocationOptions.Clean)) - { - Span huffmanValuesSpan = huffmanValues.GetSpan(); - stream.Read(huffmanValuesSpan, 0, codeLengthSum); + stream.Read(huffmanValuesSpan, 0, codeLengthSum); - i += 17 + codeLengthSum; + i += 17 + codeLengthSum; - this.scanDecoder.BuildHuffmanTable( - tableType, - tableIndex, - huffmanDataSpan, - huffmanValuesSpan, - stackalloc uint[257]); - } + this.scanDecoder.BuildHuffmanTable( + tableType, + tableIndex, + huffmanLegthsSpan, + huffmanValuesSpan.Slice(0, codeLengthSum), + tableWorkspace); } } } From 7bb1a507c20074f9777c30a682a817240925ef11 Mon Sep 17 00:00:00 2001 From: Dmitry Pentin Date: Sun, 9 Jan 2022 05:39:00 +0300 Subject: [PATCH 485/511] Docs, faster span slice method --- .../Formats/Jpeg/Components/Decoder/HuffmanTable.cs | 5 +++++ src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs index 90a966d53..bee5e0229 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs @@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder [StructLayout(LayoutKind.Sequential)] internal unsafe struct HuffmanTable { + /// + /// Memory workspace buffer size used in ctor. + /// + public const int WorkspaceByteSize = 256 * sizeof(uint); + /// /// Derived from the DHT marker. Contains the symbols, in order of incremental code length. /// diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a2cccc37f..a21f1a71d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -1097,16 +1097,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { const int codeLengthsByteSize = 17; const int codeValuesMaxByteSize = 256; - const int tableWorkspaceByteSize = 256 * sizeof(uint); - const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + tableWorkspaceByteSize; + const int totalBufferSize = codeLengthsByteSize + codeValuesMaxByteSize + HuffmanTable.WorkspaceByteSize; int length = remaining; using (IMemoryOwner buffer = this.Configuration.MemoryAllocator.Allocate(totalBufferSize)) { Span bufferSpan = buffer.GetSpan(); - Span huffmanLegthsSpan = buffer.Slice(0, codeLengthsByteSize); - Span huffmanValuesSpan = buffer.Slice(codeLengthsByteSize, codeValuesMaxByteSize); - Span tableWorkspace = MemoryMarshal.Cast(buffer.Slice(codeLengthsByteSize + codeValuesMaxByteSize)); + Span huffmanLegthsSpan = bufferSpan.Slice(0, codeLengthsByteSize); + Span huffmanValuesSpan = bufferSpan.Slice(codeLengthsByteSize, codeValuesMaxByteSize); + Span tableWorkspace = MemoryMarshal.Cast(bufferSpan.Slice(codeLengthsByteSize + codeValuesMaxByteSize)); for (int i = 2; i < remaining;) { From 76d9412416427985fe1f27e590f3f89c110691a5 Mon Sep 17 00:00:00 2001 From: mkehoe Date: Sat, 8 Jan 2022 21:56:39 -0800 Subject: [PATCH 486/511] Fix for Jpeg Metadata Decoding with unknown APP13 profile --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 73af42afd..77e1301cd 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -788,6 +788,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } } + else + { + // If the profile is unknown skip over the rest of it. + stream.Skip(remaining); + } } /// From dd31636bd278d7e90b5c80e2c560b66115743d12 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 9 Jan 2022 16:06:15 +0100 Subject: [PATCH 487/511] Correct missing frame counter check --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 17dd03cce..a21b050a8 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -330,7 +330,7 @@ namespace SixLabors.ImageSharp.Formats.Gif private void WriteApplicationExtensions(Stream stream, int frameCount, ushort repeatCount, XmpProfile xmpProfile) { // Application Extension: Loop repeat count. - if (repeatCount != 1) + if (frameCount > 1 && repeatCount != 1) { var loopingExtension = new GifNetscapeLoopingApplicationExtension(repeatCount); this.WriteExtension(loopingExtension, stream); From d879dd82f0f5e815a0ac92d29d6ecf89154de70f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Sun, 9 Jan 2022 16:06:33 +0100 Subject: [PATCH 488/511] Another allocation optimization --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index cc51d78ec..a0f86fd33 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -680,10 +680,11 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - byte[] payload = new byte[xmpData.Length + PngConstants.XmpKeyword.Length + 5]; + using IMemoryOwner owner = this.memoryAllocator.Allocate(xmpData.Length + PngConstants.XmpKeyword.Length + 5); + Span payload = owner.GetSpan(); PngConstants.XmpKeyword.CopyTo(payload); int bytesWritten = PngConstants.XmpKeyword.Length + iTxtHeaderSize; - xmpData.CopyTo(payload.AsSpan(bytesWritten)); + xmpData.CopyTo(payload.Slice(bytesWritten)); this.WriteChunk(stream, PngChunkType.InternationalText, payload); } From 3a8e5a216fdf1f698e568cb159d1b74f36d04e73 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jan 2022 11:12:48 +1100 Subject: [PATCH 489/511] Only decode App0 1x. Fix #1932 --- .../Formats/Jpeg/Components/Decoder/JFifMarker.cs | 10 +++------- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 4 +++- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 9 +++++---- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Jpg/issues/issue-1932-app0-resolution.jpg | 3 +++ 5 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs index c7b71f75a..a95e6c16c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JFifMarker.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -103,26 +103,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder /// public bool Equals(JFifMarker other) - { - return this.MajorVersion == other.MajorVersion + => this.MajorVersion == other.MajorVersion && this.MinorVersion == other.MinorVersion && this.DensityUnits == other.DensityUnits && this.XDensity == other.XDensity && this.YDensity == other.YDensity; - } /// public override bool Equals(object obj) => obj is JFifMarker other && this.Equals(other); /// public override int GetHashCode() - { - return HashCode.Combine( + => HashCode.Combine( this.MajorVersion, this.MinorVersion, this.DensityUnits, this.XDensity, this.YDensity); - } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index a21f1a71d..5f01c0468 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -625,7 +625,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) { // We can only decode JFif identifiers. - if (remaining < JFifMarker.Length) + // Some bad images contain multiple App0 markers (Issue 1932) so we check to see + // if it's already been read. + if (remaining < JFifMarker.Length || (!this.jFif.Equals(default))) { // Skip the application header length stream.Skip(remaining); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 7b3e20aa2..0ef5090cc 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg // TODO: A JPEGsnoop & metadata expert should review if the Exif/Icc expectations are correct. // I'm seeing several entries with Exif-related names in images where we do not decode an exif profile. (- Anton) public static readonly TheoryData MetadataTestData = - new TheoryData + new() { { false, TestImages.Jpeg.Progressive.Progress, 24, false, false }, { false, TestImages.Jpeg.Progressive.Fb, 24, false, true }, @@ -42,15 +42,16 @@ 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 }, - { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch } + { TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }, + { TestImages.Jpeg.Issues.MultipleApp01932, 400, 400, PixelResolutionUnit.PixelsPerInch } }; public static readonly TheoryData QualityFiles = - new TheoryData + new() { { TestImages.Jpeg.Baseline.Calliphora, 80 }, { TestImages.Jpeg.Progressive.Fb, 75 }, diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index deed0b240..3c3c12c91 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -257,6 +257,7 @@ namespace SixLabors.ImageSharp.Tests public const string IdentifyMultiFrame1211 = "Jpg/issues/issue-1221-identify-multi-frame.jpg"; public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; + public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg b/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg new file mode 100644 index 000000000..42a90c68c --- /dev/null +++ b/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b9d1a583e5c8c0a1a7362d06befd82883e1dc4231129ddb11d11d8c428691ff5 +size 1126332 From 228b3c8bc9f0c8381bc8165dd00552211487b51d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jan 2022 11:16:17 +1100 Subject: [PATCH 490/511] Update JpegDecoderCore.cs --- src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 5f01c0468..2eb2e986a 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -625,7 +625,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg private void ProcessApplicationHeaderMarker(BufferedReadStream stream, int remaining) { // We can only decode JFif identifiers. - // Some bad images contain multiple App0 markers (Issue 1932) so we check to see + // Some images contain multiple JFIF markers (Issue 1932) so we check to see // if it's already been read. if (remaining < JFifMarker.Length || (!this.jFif.Equals(default))) { From c05fd216d18b98a35fceaba1fa7078e6bf7b59cb Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 10 Jan 2022 04:26:04 +0100 Subject: [PATCH 491/511] Explicitly control lifetime of memory owner --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index a0f86fd33..c428f52e4 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -680,12 +680,15 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - using IMemoryOwner owner = this.memoryAllocator.Allocate(xmpData.Length + PngConstants.XmpKeyword.Length + 5); - Span payload = owner.GetSpan(); - PngConstants.XmpKeyword.CopyTo(payload); - int bytesWritten = PngConstants.XmpKeyword.Length + iTxtHeaderSize; - xmpData.CopyTo(payload.Slice(bytesWritten)); - this.WriteChunk(stream, PngChunkType.InternationalText, payload); + int payloadLength = xmpData.Length + PngConstants.XmpKeyword.Length + 5; + using (IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength)) + { + Span payload = owner.GetSpan(); + PngConstants.XmpKeyword.CopyTo(payload); + int bytesWritten = PngConstants.XmpKeyword.Length + iTxtHeaderSize; + xmpData.CopyTo(payload.Slice(bytesWritten)); + this.WriteChunk(stream, PngChunkType.InternationalText, payload); + } } /// From c6540799e319919bbbea91f36f578f1cb517f7a3 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 10 Jan 2022 05:58:50 +0100 Subject: [PATCH 492/511] Zero out the iTxt header --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index c428f52e4..da6878051 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -680,12 +680,21 @@ namespace SixLabors.ImageSharp.Formats.Png return; } - int payloadLength = xmpData.Length + PngConstants.XmpKeyword.Length + 5; + int payloadLength = xmpData.Length + PngConstants.XmpKeyword.Length + iTxtHeaderSize; using (IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength)) { Span payload = owner.GetSpan(); PngConstants.XmpKeyword.CopyTo(payload); - int bytesWritten = PngConstants.XmpKeyword.Length + iTxtHeaderSize; + int bytesWritten = PngConstants.XmpKeyword.Length; + + // Write the iTxt header (all zeros in this case) + payload[bytesWritten++] = 0; + payload[bytesWritten++] = 0; + payload[bytesWritten++] = 0; + payload[bytesWritten++] = 0; + payload[bytesWritten++] = 0; + + // And the XMP data itself xmpData.CopyTo(payload.Slice(bytesWritten)); this.WriteChunk(stream, PngChunkType.InternationalText, payload); } From f140107e8d6ba6652c1b3d34d16e16953fe79f6b Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jan 2022 16:24:46 +1100 Subject: [PATCH 493/511] Use a scheduled job for codecov --- .github/workflows/build-and-test.yml | 10 ---- .github/workflows/code-coverage.yml | 81 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/code-coverage.yml diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5189f0435..1172c0252 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -44,10 +44,6 @@ jobs: framework: net5.0 runtime: -x64 codecov: false - - os: ubuntu-latest - framework: netcoreapp3.1 - runtime: -x64 - codecov: true - os: macos-latest framework: netcoreapp3.1 runtime: -x64 @@ -153,12 +149,6 @@ jobs: name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip path: tests/Images/ActualOutput/ - - name: Codecov Update - uses: codecov/codecov-action@v1 - if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') - with: - flags: unittests - Publish: needs: [Build] diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml new file mode 100644 index 000000000..08312d94a --- /dev/null +++ b/.github/workflows/code-coverage.yml @@ -0,0 +1,81 @@ +name: CodeCoverage + +on: + schedule: + # 2AM every Tuesday/Thursday + - cron: '0 2 * * 2,4' +jobs: + Build: + strategy: + matrix: + options: + - os: ubuntu-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: true + + runs-on: ${{matrix.options.os}} + + steps: + - name: Git Config + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: recursive + + # See https://github.com/actions/checkout/issues/165#issuecomment-657673315 + - name: Git Create LFS FileList + run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id + + - name: Git Setup LFS Cache + uses: actions/cache@v2 + id: lfs-cache + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 + + - name: Git Pull LFS + run: git lfs pull + + - name: NuGet Install + uses: NuGet/setup-nuget@v1 + + - name: NuGet Setup Cache + uses: actions/cache@v2 + id: nuget-cache + with: + path: ~/.nuget + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: DotNet Build + shell: pwsh + run: ./ci-build.ps1 "${{matrix.options.framework}}" + env: + SIXLABORS_TESTING: True + + - name: DotNet Test + shell: pwsh + run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" + env: + SIXLABORS_TESTING: True + XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit + + - name: Export Failed Output + uses: actions/upload-artifact@v2 + if: failure() + with: + name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip + path: tests/Images/ActualOutput/ + + - name: Codecov Update + uses: codecov/codecov-action@v1 + if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') + with: + flags: unittests From 4904f325fa27c59518afe87ddba8a17d44c5db41 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Mon, 10 Jan 2022 06:29:38 +0100 Subject: [PATCH 494/511] Use memory allocator for all text chunks --- src/ImageSharp/Formats/Png/PngEncoderCore.cs | 69 +++++++++++++------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index da6878051..c443c0fcf 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -739,21 +739,33 @@ namespace SixLabors.ImageSharp.Formats.Png byte[] translatedKeyword = PngConstants.TranslatedEncoding.GetBytes(textData.TranslatedKeyword); byte[] languageTag = PngConstants.LanguageEncoding.GetBytes(textData.LanguageTag); - Span outputBytes = new byte[keywordBytes.Length + textBytes.Length + - translatedKeyword.Length + languageTag.Length + 5]; - keywordBytes.CopyTo(outputBytes); - if (textData.Value.Length > this.options.TextCompressionThreshold) + int payloadLength = keywordBytes.Length + textBytes.Length + translatedKeyword.Length + languageTag.Length + 5; + using (IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength)) { - // Indicate that the text is compressed. - outputBytes[keywordBytes.Length + 1] = 1; - } + Span outputBytes = owner.GetSpan(); + keywordBytes.CopyTo(outputBytes); + int bytesWritten = keywordBytes.Length; + outputBytes[bytesWritten++] = 0; + if (textData.Value.Length > this.options.TextCompressionThreshold) + { + // Indicate that the text is compressed. + outputBytes[bytesWritten++] = 1; + } + else + { + outputBytes[bytesWritten++] = 0; + } - int keywordStart = keywordBytes.Length + 3; - languageTag.CopyTo(outputBytes.Slice(keywordStart)); - int translatedKeywordStart = keywordStart + languageTag.Length + 1; - translatedKeyword.CopyTo(outputBytes.Slice(translatedKeywordStart)); - textBytes.CopyTo(outputBytes.Slice(translatedKeywordStart + translatedKeyword.Length + 1)); - this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes.ToArray()); + outputBytes[bytesWritten++] = 0; + languageTag.CopyTo(outputBytes.Slice(bytesWritten)); + bytesWritten += languageTag.Length; + outputBytes[bytesWritten++] = 0; + translatedKeyword.CopyTo(outputBytes.Slice(bytesWritten)); + bytesWritten += translatedKeyword.Length; + outputBytes[bytesWritten++] = 0; + textBytes.CopyTo(outputBytes.Slice(bytesWritten)); + this.WriteChunk(stream, PngChunkType.InternationalText, outputBytes); + } } else { @@ -762,19 +774,32 @@ namespace SixLabors.ImageSharp.Formats.Png // Write zTXt chunk. byte[] compressedData = this.GetCompressedTextBytes(PngConstants.Encoding.GetBytes(textData.Value)); - Span outputBytes = new byte[textData.Keyword.Length + compressedData.Length + 2]; - PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); - compressedData.CopyTo(outputBytes.Slice(textData.Keyword.Length + 2)); - this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes.ToArray()); + int payloadLength = textData.Keyword.Length + compressedData.Length + 2; + using (IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength)) + { + Span outputBytes = owner.GetSpan(); + PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); + int bytesWritten = textData.Keyword.Length; + outputBytes[bytesWritten++] = 0; + outputBytes[bytesWritten++] = 0; + compressedData.CopyTo(outputBytes.Slice(bytesWritten)); + this.WriteChunk(stream, PngChunkType.CompressedText, outputBytes.ToArray()); + } } else { // Write tEXt chunk. - Span outputBytes = new byte[textData.Keyword.Length + textData.Value.Length + 1]; - PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); - PngConstants.Encoding.GetBytes(textData.Value) - .CopyTo(outputBytes.Slice(textData.Keyword.Length + 1)); - this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray()); + int payloadLength = textData.Keyword.Length + textData.Value.Length + 1; + using (IMemoryOwner owner = this.memoryAllocator.Allocate(payloadLength)) + { + Span outputBytes = owner.GetSpan(); + PngConstants.Encoding.GetBytes(textData.Keyword).CopyTo(outputBytes); + int bytesWritten = textData.Keyword.Length; + outputBytes[bytesWritten++] = 0; + PngConstants.Encoding.GetBytes(textData.Value) + .CopyTo(outputBytes.Slice(bytesWritten)); + this.WriteChunk(stream, PngChunkType.Text, outputBytes.ToArray()); + } } } } From 7e100c34bba31b0935f9d079164e2ae52fd3998e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jan 2022 16:41:17 +1100 Subject: [PATCH 495/511] Save solution --- ImageSharp.sln | 1 + 1 file changed, 1 insertion(+) diff --git a/ImageSharp.sln b/ImageSharp.sln index f16f98ac5..17d293b43 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -551,6 +551,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{C0D7754B-5277-438E-ABEB-2BA34401B5A7}" ProjectSection(SolutionItems) = preProject .github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml + .github\workflows\code-coverage.yml = .github\workflows\code-coverage.yml EndProjectSection EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedInfrastructure", "shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.shproj", "{68A8CC40-6AED-4E96-B524-31B1158FDEEA}" From b7e0c218c02bf45e0ec37503f40e3570e1fd8450 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 10 Jan 2022 19:24:21 +1100 Subject: [PATCH 496/511] Use 2 spaces for yml --- .github/workflows/build-and-test.yml | 376 +++++++++++++-------------- .github/workflows/code-coverage.yml | 128 ++++----- 2 files changed, 252 insertions(+), 252 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 1172c0252..a6d0ef0dc 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -1,192 +1,192 @@ name: Build on: - push: - branches: - - master - tags: - - "v*" - pull_request: - branches: - - master + push: + branches: + - master + tags: + - "v*" + pull_request: + branches: + - master jobs: - Build: - strategy: - matrix: - options: - - os: ubuntu-latest - framework: net6.0 - sdk: 6.0.x - sdk-preview: true - runtime: -x64 - codecov: false - - os: macos-latest - framework: net6.0 - sdk: 6.0.x - sdk-preview: true - runtime: -x64 - codecov: false - - os: windows-latest - framework: net6.0 - sdk: 6.0.x - sdk-preview: true - runtime: -x64 - codecov: false - - os: ubuntu-latest - framework: net5.0 - runtime: -x64 - codecov: false - - os: macos-latest - framework: net5.0 - runtime: -x64 - codecov: false - - os: windows-latest - framework: net5.0 - runtime: -x64 - codecov: false - - os: macos-latest - framework: netcoreapp3.1 - runtime: -x64 - codecov: false - - os: windows-latest - framework: netcoreapp3.1 - runtime: -x64 - codecov: false - - os: windows-latest - framework: netcoreapp2.1 - runtime: -x64 - codecov: false - - os: windows-latest - framework: net472 - runtime: -x64 - codecov: false - - os: windows-latest - framework: net472 - runtime: -x86 - codecov: false - - runs-on: ${{matrix.options.os}} - - steps: - - name: Git Config - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true - - - name: Git Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: recursive - - # See https://github.com/actions/checkout/issues/165#issuecomment-657673315 - - name: Git Create LFS FileList - run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id - - - name: Git Setup LFS Cache - uses: actions/cache@v2 - id: lfs-cache - with: - path: .git/lfs - key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 - - - name: Git Pull LFS - run: git lfs pull - - - name: NuGet Install - uses: NuGet/setup-nuget@v1 - - - name: NuGet Setup Cache - uses: actions/cache@v2 - id: nuget-cache - with: - path: ~/.nuget - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} - restore-keys: ${{ runner.os }}-nuget- - - - name: DotNet Setup Preview - if: ${{ matrix.options.sdk-preview == true }} - uses: actions/setup-dotnet@v1 - with: - dotnet-version: ${{ matrix.options.sdk }} - include-prerelease: true - - - name: DotNet Build - if: ${{ matrix.options.sdk-preview != true }} - shell: pwsh - run: ./ci-build.ps1 "${{matrix.options.framework}}" - env: - SIXLABORS_TESTING: True - - - name: DotNet Build Preview - if: ${{ matrix.options.sdk-preview == true }} - shell: pwsh - run: ./ci-build.ps1 "${{matrix.options.framework}}" - env: - SIXLABORS_TESTING_PREVIEW: True - - - name: DotNet Test - if: ${{ matrix.options.sdk-preview != true }} - shell: pwsh - run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" - env: - SIXLABORS_TESTING: True - XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - - - name: DotNet Test Preview - if: ${{ matrix.options.sdk-preview == true }} - shell: pwsh - run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" - env: - SIXLABORS_TESTING_PREVIEW: True - XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - - - name: Export Failed Output - uses: actions/upload-artifact@v2 - if: failure() - with: - name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip - path: tests/Images/ActualOutput/ - - Publish: - needs: [Build] - - runs-on: ubuntu-latest - - if: (github.event_name == 'push') - - steps: - - name: Git Config - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true - - - name: Git Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: recursive - - - name: NuGet Install - uses: NuGet/setup-nuget@v1 - - - name: NuGet Setup Cache - uses: actions/cache@v2 - id: nuget-cache - with: - path: ~/.nuget - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} - restore-keys: ${{ runner.os }}-nuget- - - - name: DotNet Pack - shell: pwsh - run: ./ci-pack.ps1 - - - name: MyGet Publish - shell: pwsh - run: | - dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package - dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json - # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org + Build: + strategy: + matrix: + options: + - os: ubuntu-latest + framework: net6.0 + sdk: 6.0.x + sdk-preview: true + runtime: -x64 + codecov: false + - os: macos-latest + framework: net6.0 + sdk: 6.0.x + sdk-preview: true + runtime: -x64 + codecov: false + - os: windows-latest + framework: net6.0 + sdk: 6.0.x + sdk-preview: true + runtime: -x64 + codecov: false + - os: ubuntu-latest + framework: net5.0 + runtime: -x64 + codecov: false + - os: macos-latest + framework: net5.0 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net5.0 + runtime: -x64 + codecov: false + - os: macos-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: netcoreapp2.1 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net472 + runtime: -x64 + codecov: false + - os: windows-latest + framework: net472 + runtime: -x86 + codecov: false + + runs-on: ${{matrix.options.os}} + + steps: + - name: Git Config + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: recursive + + # See https://github.com/actions/checkout/issues/165#issuecomment-657673315 + - name: Git Create LFS FileList + run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id + + - name: Git Setup LFS Cache + uses: actions/cache@v2 + id: lfs-cache + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 + + - name: Git Pull LFS + run: git lfs pull + + - name: NuGet Install + uses: NuGet/setup-nuget@v1 + + - name: NuGet Setup Cache + uses: actions/cache@v2 + id: nuget-cache + with: + path: ~/.nuget + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: DotNet Setup Preview + if: ${{ matrix.options.sdk-preview == true }} + uses: actions/setup-dotnet@v1 + with: + dotnet-version: ${{ matrix.options.sdk }} + include-prerelease: true + + - name: DotNet Build + if: ${{ matrix.options.sdk-preview != true }} + shell: pwsh + run: ./ci-build.ps1 "${{matrix.options.framework}}" + env: + SIXLABORS_TESTING: True + + - name: DotNet Build Preview + if: ${{ matrix.options.sdk-preview == true }} + shell: pwsh + run: ./ci-build.ps1 "${{matrix.options.framework}}" + env: + SIXLABORS_TESTING_PREVIEW: True + + - name: DotNet Test + if: ${{ matrix.options.sdk-preview != true }} + shell: pwsh + run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" + env: + SIXLABORS_TESTING: True + XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit + + - name: DotNet Test Preview + if: ${{ matrix.options.sdk-preview == true }} + shell: pwsh + run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" + env: + SIXLABORS_TESTING_PREVIEW: True + XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit + + - name: Export Failed Output + uses: actions/upload-artifact@v2 + if: failure() + with: + name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip + path: tests/Images/ActualOutput/ + + Publish: + needs: [Build] + + runs-on: ubuntu-latest + + if: (github.event_name == 'push') + + steps: + - name: Git Config + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true + + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: recursive + + - name: NuGet Install + uses: NuGet/setup-nuget@v1 + + - name: NuGet Setup Cache + uses: actions/cache@v2 + id: nuget-cache + with: + path: ~/.nuget + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} + restore-keys: ${{ runner.os }}-nuget- + + - name: DotNet Pack + shell: pwsh + run: ./ci-pack.ps1 + + - name: MyGet Publish + shell: pwsh + run: | + dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package + dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json + # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 08312d94a..2b14f2a4b 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -3,79 +3,79 @@ name: CodeCoverage on: schedule: # 2AM every Tuesday/Thursday - - cron: '0 2 * * 2,4' + - cron: "0 2 * * 2,4" jobs: - Build: - strategy: - matrix: - options: - - os: ubuntu-latest - framework: netcoreapp3.1 - runtime: -x64 - codecov: true + Build: + strategy: + matrix: + options: + - os: ubuntu-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: true - runs-on: ${{matrix.options.os}} + runs-on: ${{matrix.options.os}} - steps: - - name: Git Config - shell: bash - run: | - git config --global core.autocrlf false - git config --global core.longpaths true + steps: + - name: Git Config + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.longpaths true - - name: Git Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - submodules: recursive - - # See https://github.com/actions/checkout/issues/165#issuecomment-657673315 - - name: Git Create LFS FileList - run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id + - name: Git Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: recursive - - name: Git Setup LFS Cache - uses: actions/cache@v2 - id: lfs-cache - with: - path: .git/lfs - key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 + # See https://github.com/actions/checkout/issues/165#issuecomment-657673315 + - name: Git Create LFS FileList + run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id - - name: Git Pull LFS - run: git lfs pull + - name: Git Setup LFS Cache + uses: actions/cache@v2 + id: lfs-cache + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 - - name: NuGet Install - uses: NuGet/setup-nuget@v1 + - name: Git Pull LFS + run: git lfs pull - - name: NuGet Setup Cache - uses: actions/cache@v2 - id: nuget-cache - with: - path: ~/.nuget - key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} - restore-keys: ${{ runner.os }}-nuget- + - name: NuGet Install + uses: NuGet/setup-nuget@v1 - - name: DotNet Build - shell: pwsh - run: ./ci-build.ps1 "${{matrix.options.framework}}" - env: - SIXLABORS_TESTING: True + - name: NuGet Setup Cache + uses: actions/cache@v2 + id: nuget-cache + with: + path: ~/.nuget + key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} + restore-keys: ${{ runner.os }}-nuget- - - name: DotNet Test - shell: pwsh - run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" - env: - SIXLABORS_TESTING: True - XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit + - name: DotNet Build + shell: pwsh + run: ./ci-build.ps1 "${{matrix.options.framework}}" + env: + SIXLABORS_TESTING: True - - name: Export Failed Output - uses: actions/upload-artifact@v2 - if: failure() - with: - name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip - path: tests/Images/ActualOutput/ + - name: DotNet Test + shell: pwsh + run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" + env: + SIXLABORS_TESTING: True + XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit - - name: Codecov Update - uses: codecov/codecov-action@v1 - if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') - with: - flags: unittests + - name: Export Failed Output + uses: actions/upload-artifact@v2 + if: failure() + with: + name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip + path: tests/Images/ActualOutput/ + + - name: Codecov Update + uses: codecov/codecov-action@v1 + if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') + with: + flags: unittests From 0ec80030bfd167a0a07fc169415760fbdafa2f0e Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 11 Jan 2022 00:30:12 +1100 Subject: [PATCH 497/511] Update issue-1932-app0-resolution.jpg --- tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg b/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg index 42a90c68c..7f14e808e 100644 --- a/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg +++ b/tests/Images/Input/Jpg/issues/issue-1932-app0-resolution.jpg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b9d1a583e5c8c0a1a7362d06befd82883e1dc4231129ddb11d11d8c428691ff5 -size 1126332 +oid sha256:3654c48003b85c1110bad8c31d2f94eaf4dcfe488698246b3ead4b54715d8d18 +size 1325 From 92f47fc628a44e11a2553ead6304cded078711e9 Mon Sep 17 00:00:00 2001 From: jz5 Date: Mon, 10 Jan 2022 22:55:19 +0900 Subject: [PATCH 498/511] Update the PixelTypeInfo constructor to use separate overloads with and without the PixelAlphaRepresentation (not nullable) parameter. --- src/ImageSharp/Formats/PixelTypeInfo.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/PixelTypeInfo.cs b/src/ImageSharp/Formats/PixelTypeInfo.cs index 1bc717cbb..afa3b19a2 100644 --- a/src/ImageSharp/Formats/PixelTypeInfo.cs +++ b/src/ImageSharp/Formats/PixelTypeInfo.cs @@ -15,8 +15,17 @@ namespace SixLabors.ImageSharp.Formats /// Initializes a new instance of the class. /// /// Color depth, in number of bits per pixel. - /// Tthe pixel alpha transparency behavior. - public PixelTypeInfo(int bitsPerPixel, PixelAlphaRepresentation? alpha = null) + public PixelTypeInfo(int bitsPerPixel) + { + this.BitsPerPixel = bitsPerPixel; + } + + /// + /// Initializes a new instance of the class. + /// + /// Color depth, in number of bits per pixel. + /// The pixel alpha transparency behavior. + public PixelTypeInfo(int bitsPerPixel, PixelAlphaRepresentation alpha) { this.BitsPerPixel = bitsPerPixel; this.AlphaRepresentation = alpha; From 7d0d2e313febd6f0e517f9c9b778c28170ee8b17 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 11 Jan 2022 11:09:45 +1100 Subject: [PATCH 499/511] Add missing framework test --- .github/workflows/build-and-test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a6d0ef0dc..6d31e8c53 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -44,6 +44,10 @@ jobs: framework: net5.0 runtime: -x64 codecov: false + - os: ubuntu-latest + framework: netcoreapp3.1 + runtime: -x64 + codecov: false - os: macos-latest framework: netcoreapp3.1 runtime: -x64 From 90f256c97b5631cc3f6841a1b8f7be9d62a65a68 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 11 Jan 2022 15:25:37 +1100 Subject: [PATCH 500/511] Remove public setter and use ConcurrentBag.Clear() --- src/ImageSharp/Configuration.cs | 16 ++++++------ src/ImageSharp/Formats/ImageFormatManager.cs | 26 +++++++------------- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index ca83b0764..241946b6d 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -122,25 +122,25 @@ namespace SixLabors.ImageSharp public ReadOrigin ReadOrigin { get; set; } = ReadOrigin.Current; /// - /// Gets or sets the that is currently in use. + /// Gets or the that is currently in use. /// - public ImageFormatManager ImageFormatsManager { get; set; } = new ImageFormatManager(); + public ImageFormatManager ImageFormatsManager { get; } = new ImageFormatManager(); /// - /// Gets or sets the that is currently in use. - /// Defaults to . + /// 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 /// /// /// 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. /// - /// If an allocator has to be dropped for some reason, + /// If an allocator has to be dropped for some reason, /// shall be invoked after disposing all associated instances. /// public MemoryAllocator MemoryAllocator @@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp /// Creates a shallow copy of the . /// /// A new configuration instance. - public Configuration Clone() => new Configuration + public Configuration Clone() => new() { MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, StreamProcessingBufferSize = this.StreamProcessingBufferSize, @@ -216,7 +216,7 @@ namespace SixLabors.ImageSharp /// . ///
/// The default configuration of . - internal static Configuration CreateDefaultInstance() => new Configuration( + internal static Configuration CreateDefaultInstance() => new( new PngConfigurationModule(), new JpegConfigurationModule(), new GifConfigurationModule(), diff --git a/src/ImageSharp/Formats/ImageFormatManager.cs b/src/ImageSharp/Formats/ImageFormatManager.cs index 2e1874592..d927b87d1 100644 --- a/src/ImageSharp/Formats/ImageFormatManager.cs +++ b/src/ImageSharp/Formats/ImageFormatManager.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System; @@ -17,27 +17,27 @@ namespace SixLabors.ImageSharp.Formats /// Used for locking against as there is no ConcurrentSet type. /// ///
- private static readonly object HashLock = new object(); + private static readonly object HashLock = new(); /// /// The list of supported keyed to mime types. /// - private readonly ConcurrentDictionary mimeTypeEncoders = new ConcurrentDictionary(); + private readonly ConcurrentDictionary mimeTypeEncoders = new(); /// /// The list of supported keyed to mime types. /// - private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); + private readonly ConcurrentDictionary mimeTypeDecoders = new(); /// /// The list of supported s. /// - private readonly HashSet imageFormats = new HashSet(); + private readonly HashSet imageFormats = new(); /// /// The list of supported s. /// - private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); + private readonly ConcurrentBag imageFormatDetectors = new(); /// /// Initializes a new instance of the class. @@ -113,9 +113,7 @@ namespace SixLabors.ImageSharp.Formats /// The mime-type to discover /// The if found; otherwise null public IImageFormat FindFormatByMimeType(string mimeType) - { - return this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); - } + => this.imageFormats.FirstOrDefault(x => x.MimeTypes.Contains(mimeType, StringComparer.OrdinalIgnoreCase)); /// /// Sets a specific image encoder as the encoder for a specific image format. @@ -146,10 +144,7 @@ namespace SixLabors.ImageSharp.Formats /// /// Removes all the registered image format detectors. /// - public void ClearImageFormatDetectors() - { - this.imageFormatDetectors = new ConcurrentBag(); - } + public void ClearImageFormatDetectors() => this.imageFormatDetectors.Clear(); /// /// Adds a new detector for detecting mime types. @@ -193,9 +188,6 @@ namespace SixLabors.ImageSharp.Formats /// /// Sets the max header size. /// - private void SetMaxHeaderSize() - { - this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); - } + private void SetMaxHeaderSize() => this.MaxHeaderSize = this.imageFormatDetectors.Max(x => x.HeaderSize); } } From c4d14c05c983e1b89caa038ae968699ffd57f315 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 11 Jan 2022 15:36:30 +1100 Subject: [PATCH 501/511] Update Configuration.cs --- src/ImageSharp/Configuration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 241946b6d..3e021dda8 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -124,7 +124,7 @@ namespace SixLabors.ImageSharp /// /// Gets or the that is currently in use. /// - public ImageFormatManager ImageFormatsManager { get; } = new ImageFormatManager(); + public ImageFormatManager ImageFormatsManager { get; private set; } = new ImageFormatManager(); /// /// Gets or sets the that is currently in use. From 7d39dfc7c30e899c54ac309df5c9fe8ebe6d9127 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 11 Jan 2022 16:50:40 +1100 Subject: [PATCH 502/511] NET472 doesn't have Clear. --- src/ImageSharp/Formats/ImageFormatManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/ImageFormatManager.cs b/src/ImageSharp/Formats/ImageFormatManager.cs index d927b87d1..f3fde403d 100644 --- a/src/ImageSharp/Formats/ImageFormatManager.cs +++ b/src/ImageSharp/Formats/ImageFormatManager.cs @@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats /// /// The list of supported s. /// - private readonly ConcurrentBag imageFormatDetectors = new(); + private ConcurrentBag imageFormatDetectors = new(); /// /// Initializes a new instance of the class. @@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.Formats /// /// Removes all the registered image format detectors. /// - public void ClearImageFormatDetectors() => this.imageFormatDetectors.Clear(); + public void ClearImageFormatDetectors() => this.imageFormatDetectors = new(); /// /// Adds a new detector for detecting mime types. From d8db6539d507486e93b13bbcad12150e8bc817a4 Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Sat, 15 Jan 2022 16:11:30 +0100 Subject: [PATCH 503/511] Apply suggestions from code review 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/AlphaDecoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index 39ff0dbbb..24104369e 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -314,14 +314,14 @@ namespace SixLabors.ImageSharp.Formats.Webp #if SUPPORTS_RUNTIME_INTRINSICS if (Sse2.IsSupported) { - dst[0] = (byte)(input[0] + (prev == null ? 0 : prev[0])); + dst[0] = (byte)(input[0] + (prev.IsEmpty ? 0 : prev[0])); if (width <= 1) { return; } - int i; - var last = Vector128.Create(dst[0], 0, 0, 0); + nint i; + var last = Vector128.Zero.WithElement(0, dst[0]); ref byte srcRef = ref MemoryMarshal.GetReference(input); for (i = 1; i + 8 <= width; i += 8) { @@ -347,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.Webp else #endif { - byte pred = (byte)(prev == null ? 0 : prev[0]); + byte pred = (byte)(prev.IsEmpty ? 0 : prev[0]); for (int i = 0; i < width; i++) { @@ -369,7 +369,7 @@ namespace SixLabors.ImageSharp.Formats.Webp #if SUPPORTS_RUNTIME_INTRINSICS if (Avx2.IsSupported) { - int i; + nint i; int maxPos = width & ~31; for (i = 0; i < maxPos; i += 32) { From 6fdf643d55c4140580c9a6810d77ff2f096868d8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 15 Jan 2022 16:24:15 +0100 Subject: [PATCH 504/511] Fix build errors --- src/ImageSharp/Formats/Webp/AlphaDecoder.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs index 24104369e..b1e155c1d 100644 --- a/src/ImageSharp/Formats/Webp/AlphaDecoder.cs +++ b/src/ImageSharp/Formats/Webp/AlphaDecoder.cs @@ -321,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Webp } nint i; - var last = Vector128.Zero.WithElement(0, dst[0]); + Vector128 last = Vector128.Zero.WithElement(0, dst[0]); ref byte srcRef = ref MemoryMarshal.GetReference(input); for (i = 1; i + 8 <= width; i += 8) { @@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Webp for (; i < width; ++i) { - dst[i] = (byte)(input[i] + dst[i - 1]); + dst[(int)i] = (byte)(input[(int)i] + dst[(int)i - 1]); } } else @@ -360,7 +360,7 @@ namespace SixLabors.ImageSharp.Formats.Webp private static void VerticalUnfilter(Span prev, Span input, Span dst, int width) { - if (prev == null) + if (prev.IsEmpty) { HorizontalUnfilter(null, input, dst, width); } @@ -382,7 +382,7 @@ namespace SixLabors.ImageSharp.Formats.Webp for (; i < width; i++) { - dst[i] = (byte)(prev[i] + input[i]); + dst[(int)i] = (byte)(prev[(int)i] + input[(int)i]); } } else @@ -398,7 +398,7 @@ namespace SixLabors.ImageSharp.Formats.Webp private static void GradientUnfilter(Span prev, Span input, Span dst, int width) { - if (prev == null) + if (prev.IsEmpty) { HorizontalUnfilter(null, input, dst, width); } From 38b5da4e19359cb8353ba5d7a5ebcb0d87dab568 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 15 Jan 2022 20:18:42 +0100 Subject: [PATCH 505/511] Ignore invalid iptc tag values #1942 --- .../Metadata/Profiles/IPTC/IptcProfile.cs | 41 ++++--- .../Profiles/IPTC/IptcTagExtensions.cs | 103 +++++++++--------- .../Metadata/Profiles/IPTC/IptcValue.cs | 2 +- 3 files changed, 71 insertions(+), 75 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index e31842c53..d07cb11c9 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -16,6 +16,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc { private Collection values; + private const byte IptcTagMarkerByte = 0x1c; + + private const uint MaxStandardDataTagSize = 0x7FFF; + /// /// Initializes a new instance of the class. /// @@ -78,7 +82,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc } /// - public IptcProfile DeepClone() => new IptcProfile(this); + public IptcProfile DeepClone() => new(this); /// /// Returns all values with the specified tag. @@ -207,7 +211,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc throw new ArgumentException("iptc tag is not a time or date type"); } - var formattedDate = tag.IsDate() + string formattedDate = tag.IsDate() ? dateTimeOffset.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture) : dateTimeOffset.ToString("HHmmsszzzz", System.Globalization.CultureInfo.InvariantCulture) .Replace(":", string.Empty); @@ -231,7 +235,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc /// public void UpdateData() { - var length = 0; + int length = 0; foreach (IptcValue value in this.Values) { length += value.Length + 5; @@ -242,7 +246,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc int i = 0; foreach (IptcValue value in this.Values) { - this.Data[i++] = 28; + this.Data[i++] = IptcTagMarkerByte; this.Data[i++] = 2; this.Data[i++] = (byte)value.Tag; this.Data[i++] = (byte)(value.Length >> 8); @@ -264,34 +268,29 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc this.values = new Collection(); - if (this.Data == null || this.Data[0] != 0x1c) + if (this.Data == null || this.Data[0] != IptcTagMarkerByte) { return; } - int i = 0; - while (i + 4 < this.Data.Length) + int offset = 0; + while (offset + 4 < this.Data.Length) { - if (this.Data[i++] != 28) - { - continue; - } - - i++; - - var tag = (IptcTag)this.Data[i++]; + bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte; + byte recordType = this.Data[offset++]; + var tag = (IptcTag)this.Data[offset++]; - int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2)); - i += 2; + uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); + offset += 2; - var iptcData = new byte[count]; - if ((count > 0) && (i + count <= this.Data.Length)) + if (isValidTagMarker && byteCount > 0 && (offset + byteCount <= this.Data.Length)) { - Buffer.BlockCopy(this.Data, i, iptcData, 0, count); + var iptcData = new byte[byteCount]; + Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); this.values.Add(new IptcValue(tag, iptcData, false)); } - i += count; + offset += (int)byteCount; } } } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs index b670591df..4b18add74 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs @@ -13,60 +13,57 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc /// /// The tag to check the max length for. /// The maximum length. - public static int MaxLength(this IptcTag tag) + public static int MaxLength(this IptcTag tag) => tag switch { - return tag switch - { - IptcTag.RecordVersion => 2, - IptcTag.ObjectType => 67, - IptcTag.ObjectAttribute => 68, - IptcTag.Name => 64, - IptcTag.EditStatus => 64, - IptcTag.EditorialUpdate => 2, - IptcTag.Urgency => 1, - IptcTag.SubjectReference => 236, - IptcTag.Category => 3, - IptcTag.SupplementalCategories => 32, - IptcTag.FixtureIdentifier => 32, - IptcTag.Keywords => 64, - IptcTag.LocationCode => 3, - IptcTag.LocationName => 64, - IptcTag.ReleaseDate => 8, - IptcTag.ReleaseTime => 11, - IptcTag.ExpirationDate => 8, - IptcTag.ExpirationTime => 11, - IptcTag.SpecialInstructions => 256, - IptcTag.ActionAdvised => 2, - IptcTag.ReferenceService => 10, - IptcTag.ReferenceDate => 8, - IptcTag.ReferenceNumber => 8, - IptcTag.CreatedDate => 8, - IptcTag.CreatedTime => 11, - IptcTag.DigitalCreationDate => 8, - IptcTag.DigitalCreationTime => 11, - IptcTag.OriginatingProgram => 32, - IptcTag.ProgramVersion => 10, - IptcTag.ObjectCycle => 1, - IptcTag.Byline => 32, - IptcTag.BylineTitle => 32, - IptcTag.City => 32, - IptcTag.SubLocation => 32, - IptcTag.ProvinceState => 32, - IptcTag.CountryCode => 3, - IptcTag.Country => 64, - IptcTag.OriginalTransmissionReference => 32, - IptcTag.Headline => 256, - IptcTag.Credit => 32, - IptcTag.Source => 32, - IptcTag.CopyrightNotice => 128, - IptcTag.Contact => 128, - IptcTag.Caption => 2000, - IptcTag.CaptionWriter => 32, - IptcTag.ImageType => 2, - IptcTag.ImageOrientation => 1, - _ => 256 - }; - } + IptcTag.RecordVersion => 2, + IptcTag.ObjectType => 67, + IptcTag.ObjectAttribute => 68, + IptcTag.Name => 64, + IptcTag.EditStatus => 64, + IptcTag.EditorialUpdate => 2, + IptcTag.Urgency => 1, + IptcTag.SubjectReference => 236, + IptcTag.Category => 3, + IptcTag.SupplementalCategories => 32, + IptcTag.FixtureIdentifier => 32, + IptcTag.Keywords => 64, + IptcTag.LocationCode => 3, + IptcTag.LocationName => 64, + IptcTag.ReleaseDate => 8, + IptcTag.ReleaseTime => 11, + IptcTag.ExpirationDate => 8, + IptcTag.ExpirationTime => 11, + IptcTag.SpecialInstructions => 256, + IptcTag.ActionAdvised => 2, + IptcTag.ReferenceService => 10, + IptcTag.ReferenceDate => 8, + IptcTag.ReferenceNumber => 8, + IptcTag.CreatedDate => 8, + IptcTag.CreatedTime => 11, + IptcTag.DigitalCreationDate => 8, + IptcTag.DigitalCreationTime => 11, + IptcTag.OriginatingProgram => 32, + IptcTag.ProgramVersion => 10, + IptcTag.ObjectCycle => 1, + IptcTag.Byline => 32, + IptcTag.BylineTitle => 32, + IptcTag.City => 32, + IptcTag.SubLocation => 32, + IptcTag.ProvinceState => 32, + IptcTag.CountryCode => 3, + IptcTag.Country => 64, + IptcTag.OriginalTransmissionReference => 32, + IptcTag.Headline => 256, + IptcTag.Credit => 32, + IptcTag.Source => 32, + IptcTag.CopyrightNotice => 128, + IptcTag.Contact => 128, + IptcTag.Caption => 2000, + IptcTag.CaptionWriter => 32, + IptcTag.ImageType => 2, + IptcTag.ImageOrientation => 1, + _ => 256 + }; /// /// Determines if the given tag can be repeated according to the specification. diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs index 9e409ca06..5ba81bea7 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc byte[] valueBytes; if (this.Strict && value.Length > maxLength) { - var cappedValue = value.Substring(0, maxLength); + string cappedValue = value.Substring(0, maxLength); valueBytes = this.encoding.GetBytes(cappedValue); // It is still possible that the bytes of the string exceed the limit. From c476caace62e230a2bce92b97646bdddbb06c64a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 15 Jan 2022 20:43:20 +0100 Subject: [PATCH 506/511] Add test for issue #1942 --- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 12 ++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 1 + .../Input/Jpg/issues/Issue1942InvalidIptcTag.jpg | 3 +++ 3 files changed, 16 insertions(+) create mode 100644 tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 0ef5090cc..53c81631d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -288,5 +288,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); Assert.Equal(72, imageInfo.Metadata.VerticalResolution); }); + + [Theory] + [WithFile(TestImages.Jpeg.Issues.InvalidIptcTag, PixelTypes.Rgba32)] + public void Decode_WithInvalidIptcTag_DoesNotThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => + { + using Image image = provider.GetImage(JpegDecoder); + }); + Assert.Null(ex); + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 0860bb5ae..bce22799d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -261,6 +261,7 @@ namespace SixLabors.ImageSharp.Tests public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; + public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg b/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg new file mode 100644 index 000000000..8b1926128 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c9db428c4d9d7d1aea6778f263d8deaeeabdcfa63c77ef6ce36ab0e47b364dd +size 93374 From 559feb26dfef6ba2f4b7d05d7108d5e98f0e48c2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 16 Jan 2022 22:29:22 +0100 Subject: [PATCH 507/511] Break reading iptc data when Extended data set tag is encountered --- src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index d07cb11c9..945d5d103 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -282,6 +282,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); offset += 2; + if (byteCount > MaxStandardDataTagSize) + { + // Extended data set tag's are not supported. + break; + } if (isValidTagMarker && byteCount > 0 && (offset + byteCount <= this.Data.Length)) { From 805f9d014fd32a32be0d63d2e2e5866f344e0064 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 17 Jan 2022 20:19:14 +0100 Subject: [PATCH 508/511] Skip IPTC entry's which have no valid record number --- src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index 945d5d103..e88f54419 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -277,8 +277,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc while (offset + 4 < this.Data.Length) { bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte; - byte recordType = this.Data[offset++]; + byte recordNumber = this.Data[offset++]; + bool isValidRecordNumber = recordNumber is >= 1 and <= 9; var tag = (IptcTag)this.Data[offset++]; + bool isValidEntry = isValidTagMarker && isValidRecordNumber; uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); offset += 2; @@ -288,7 +290,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc break; } - if (isValidTagMarker && byteCount > 0 && (offset + byteCount <= this.Data.Length)) + if (isValidEntry && byteCount > 0 && (offset + byteCount <= this.Data.Length)) { var iptcData = new byte[byteCount]; Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); From 300ad3c36842c5867d211e3814db33857c714f0c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 17 Jan 2022 20:31:14 +0100 Subject: [PATCH 509/511] Add Standard DataSet Tag description --- .../Metadata/Profiles/IPTC/IptcProfile.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index e88f54419..b456c66bb 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -246,6 +246,23 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc int i = 0; foreach (IptcValue value in this.Values) { + // Standard DataSet Tag + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | Octet Pos | Name | Description | + // +==========-+================+=================================================================================+ + // | 1 | Tag Marker | Is the tag marker that initiates the start of a DataSet 0x1c. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 2 | Record Number | Octet 2 is the binary representation of the record number. Note that the | + // | | | envelope record number is always 1, and that the application records are | + // | | | numbered 2 through 6, the pre-object descriptor record is 7, the object record | + // | | | is 8, and the post - object descriptor record is 9. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 3 | DataSet Number | Octet 3 is the binary representation of the DataSet number. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 4 and 5 | Data Field | Octets 4 and 5, taken together, are the binary count of the number of octets in | + // | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of| + // | | | octet 4(most significant bit) always will be 0. | + // +-----------+----------------+---------------------------------------------------------------------------------+ this.Data[i++] = IptcTagMarkerByte; this.Data[i++] = 2; this.Data[i++] = (byte)value.Tag; From 02cc77360d0c7ad08e254f96c9204596358ebc6b Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Mon, 17 Jan 2022 20:37:13 +0100 Subject: [PATCH 510/511] Change while condition to: offset < this.Data.Length - 4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Günther Foidl --- src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index b456c66bb..4eb0409db 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -291,7 +291,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc } int offset = 0; - while (offset + 4 < this.Data.Length) + while (offset < this.Data.Length - 4) { bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte; byte recordNumber = this.Data[offset++]; From 6f495f1642e580ac6e4e0b401dddf1533744e30d Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Mon, 17 Jan 2022 20:40:30 +0100 Subject: [PATCH 511/511] Review suggestion from gfoidl: Change condition to (offset <= this.Data.Length - byteCount) --- src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index 4eb0409db..610e52d1c 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -307,7 +307,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc break; } - if (isValidEntry && byteCount > 0 && (offset + byteCount <= this.Data.Length)) + if (isValidEntry && byteCount > 0 && (offset <= this.Data.Length - byteCount)) { var iptcData = new byte[byteCount]; Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount);